mirror of
https://github.com/9001/copyparty.git
synced 2025-10-27 10:03:36 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dab3407beb | ||
|
|
592987a54a | ||
|
|
8dca8326f7 | ||
|
|
633481fae3 | ||
|
|
e7b99e6fb7 | ||
|
|
2a6a3aedd0 | ||
|
|
866c74c841 | ||
|
|
dad92bde26 | ||
|
|
a994e034f7 | ||
|
|
2801c04f2e | ||
|
|
316e3abfab | ||
|
|
c15ecb6c8e | ||
|
|
ee96005026 | ||
|
|
5b55d05a20 | ||
|
|
2f09c62c4e | ||
|
|
1cc8b873d4 | ||
|
|
15d5859750 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -11,14 +11,12 @@ dist/
|
||||
sfx/
|
||||
.venv/
|
||||
|
||||
# sublime
|
||||
# ide
|
||||
*.sublime-workspace
|
||||
|
||||
# winmerge
|
||||
*.bak
|
||||
|
||||
# other licenses
|
||||
contrib/
|
||||
|
||||
# deps
|
||||
copyparty/web/deps
|
||||
# derived
|
||||
copyparty/web/deps/
|
||||
srv/
|
||||
|
||||
@@ -104,10 +104,8 @@ in the `scripts` folder:
|
||||
|
||||
roughly sorted by priority
|
||||
|
||||
* sortable browser columns
|
||||
* up2k handle filename too long
|
||||
* up2k fails on empty files? alert then stuck
|
||||
* unexpected filepath on dupe up2k
|
||||
* drop onto folders
|
||||
* look into android thumbnail cache file format
|
||||
* support pillow-simd
|
||||
|
||||
@@ -16,6 +16,8 @@ if platform.system() == "Windows":
|
||||
VT100 = not WINDOWS or WINDOWS >= [10, 0, 14393]
|
||||
# introduced in anniversary update
|
||||
|
||||
MACOS = platform.system() == "Darwin"
|
||||
|
||||
|
||||
class EnvParams(object):
|
||||
def __init__(self):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (0, 3, 0)
|
||||
CODENAME = "docuparty"
|
||||
BUILD_DT = (2020, 5, 6)
|
||||
VERSION = (0, 4, 0)
|
||||
CODENAME = "NIH"
|
||||
BUILD_DT = (2020, 5, 13)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -4,7 +4,6 @@ from __future__ import print_function, unicode_literals
|
||||
|
||||
import traceback
|
||||
|
||||
from .__init__ import PY2
|
||||
from .util import Pebkac, Queue
|
||||
|
||||
|
||||
|
||||
@@ -420,9 +420,11 @@ class HttpCli(object):
|
||||
vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
|
||||
self._assert_safe_rem(rem)
|
||||
|
||||
sanitized = sanitize_fn(new_dir)
|
||||
|
||||
if not nullwrite:
|
||||
fdir = os.path.join(vfs.realpath, rem)
|
||||
fn = os.path.join(fdir, sanitize_fn(new_dir))
|
||||
fn = os.path.join(fdir, sanitized)
|
||||
|
||||
if not os.path.isdir(fsenc(fdir)):
|
||||
raise Pebkac(500, "parent folder does not exist")
|
||||
@@ -435,7 +437,7 @@ class HttpCli(object):
|
||||
except:
|
||||
raise Pebkac(500, "mkdir failed, check the logs")
|
||||
|
||||
vpath = "{}/{}".format(self.vpath, new_dir).lstrip("/")
|
||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||
html = self.conn.tpl_msg.render(
|
||||
h2='<a href="/{}">go to /{}</a>'.format(
|
||||
quotep(vpath), html_escape(vpath, quote=False)
|
||||
@@ -457,9 +459,11 @@ class HttpCli(object):
|
||||
if not new_file.endswith(".md"):
|
||||
new_file += ".md"
|
||||
|
||||
sanitized = sanitize_fn(new_file)
|
||||
|
||||
if not nullwrite:
|
||||
fdir = os.path.join(vfs.realpath, rem)
|
||||
fn = os.path.join(fdir, sanitize_fn(new_file))
|
||||
fn = os.path.join(fdir, sanitized)
|
||||
|
||||
if os.path.exists(fsenc(fn)):
|
||||
raise Pebkac(500, "that file exists already")
|
||||
@@ -467,7 +471,7 @@ class HttpCli(object):
|
||||
with open(fsenc(fn), "wb") as f:
|
||||
f.write(b"`GRUNNUR`\n")
|
||||
|
||||
vpath = "{}/{}".format(self.vpath, new_file).lstrip("/")
|
||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||
html = self.conn.tpl_msg.render(
|
||||
h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
|
||||
quotep(vpath), html_escape(vpath, quote=False)
|
||||
@@ -600,10 +604,10 @@ class HttpCli(object):
|
||||
self.reply(response.encode("utf-8"))
|
||||
return True
|
||||
|
||||
fn = os.path.join(vfs.realpath, rem)
|
||||
fp = os.path.join(vfs.realpath, rem)
|
||||
srv_lastmod = -1
|
||||
try:
|
||||
st = os.stat(fsenc(fn))
|
||||
st = os.stat(fsenc(fp))
|
||||
srv_lastmod = st.st_mtime
|
||||
srv_lastmod3 = int(srv_lastmod * 1000)
|
||||
except OSError as ex:
|
||||
@@ -631,16 +635,22 @@ class HttpCli(object):
|
||||
return True
|
||||
|
||||
# TODO another hack re: pending permissions rework
|
||||
os.rename(fn, "{}.bak-{:.3f}.md".format(fn[:-3], srv_lastmod))
|
||||
mdir, mfile = os.path.split(fp)
|
||||
mfile2 = "{}.{:.3f}.md".format(mfile[:-3], srv_lastmod)
|
||||
try:
|
||||
os.mkdir(os.path.join(mdir, ".hist"))
|
||||
except:
|
||||
pass
|
||||
os.rename(fp, os.path.join(mdir, ".hist", mfile2))
|
||||
|
||||
p_field, _, p_data = next(self.parser.gen)
|
||||
if p_field != "body":
|
||||
raise Pebkac(400, "expected body, got {}".format(p_field))
|
||||
|
||||
with open(fn, "wb") as f:
|
||||
with open(fp, "wb") as f:
|
||||
sz, sha512, _ = hashcopy(self.conn, p_data, f)
|
||||
|
||||
new_lastmod = os.stat(fsenc(fn)).st_mtime
|
||||
new_lastmod = os.stat(fsenc(fp)).st_mtime
|
||||
new_lastmod3 = int(new_lastmod * 1000)
|
||||
sha512 = sha512[:56]
|
||||
|
||||
@@ -824,7 +834,7 @@ class HttpCli(object):
|
||||
|
||||
def tx_md(self, fs_path):
|
||||
logmsg = "{:4} {} ".format("", self.req)
|
||||
if "edit" in self.uparam:
|
||||
if "edit2" in self.uparam:
|
||||
html_path = "web/mde.html"
|
||||
template = self.conn.tpl_mde
|
||||
else:
|
||||
@@ -834,18 +844,26 @@ class HttpCli(object):
|
||||
html_path = os.path.join(E.mod, html_path)
|
||||
|
||||
st = os.stat(fsenc(fs_path))
|
||||
sz_md = st.st_size
|
||||
# sz_md = st.st_size
|
||||
ts_md = st.st_mtime
|
||||
|
||||
st = os.stat(fsenc(html_path))
|
||||
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_lastmod, do_send = self._chk_lastmod(file_ts)
|
||||
self.out_headers["Last-Modified"] = file_lastmod
|
||||
self.out_headers["Cache-Control"] = "no-cache"
|
||||
status = 200 if do_send else 304
|
||||
|
||||
targs = {
|
||||
"edit": "edit" in self.uparam,
|
||||
"title": html_escape(self.vpath, quote=False),
|
||||
"lastmod": int(ts_md * 1000),
|
||||
"md": "",
|
||||
@@ -858,9 +876,7 @@ class HttpCli(object):
|
||||
self.log(logmsg)
|
||||
return True
|
||||
|
||||
with open(fsenc(fs_path), "rb") as f:
|
||||
md = f.read()
|
||||
|
||||
# TODO jinja2 can stream this right?
|
||||
targs["md"] = md.decode("utf-8", "replace")
|
||||
html = template.render(**targs).encode("utf-8")
|
||||
try:
|
||||
@@ -909,12 +925,30 @@ class HttpCli(object):
|
||||
fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname)
|
||||
vfs_ls.extend(vfs_virt.keys())
|
||||
|
||||
# check for old versions of files,
|
||||
hist = {} # [num-backups, most-recent, hist-path]
|
||||
histdir = os.path.join(fsroot, ".hist")
|
||||
ptn = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[^\.]+)$")
|
||||
try:
|
||||
for hfn in os.listdir(histdir):
|
||||
m = ptn.match(hfn)
|
||||
if not m:
|
||||
continue
|
||||
|
||||
fn = m.group(1) + m.group(3)
|
||||
n, ts, _ = hist.get(fn, [0, 0, ""])
|
||||
hist[fn] = [n + 1, max(ts, float(m.group(2))), hfn]
|
||||
except:
|
||||
pass
|
||||
|
||||
dirs = []
|
||||
files = []
|
||||
for fn in exclude_dotfiles(vfs_ls):
|
||||
base = ""
|
||||
href = fn
|
||||
if self.absolute_urls and vpath:
|
||||
href = "/" + vpath + "/" + fn
|
||||
base = "/" + vpath + "/"
|
||||
href = base + fn
|
||||
|
||||
if fn in vfs_virt:
|
||||
fspath = vfs_virt[fn].realpath
|
||||
@@ -931,6 +965,10 @@ class HttpCli(object):
|
||||
if is_dir:
|
||||
margin = "DIR"
|
||||
href += "/"
|
||||
elif fn in hist:
|
||||
margin = '<a href="{}.hist/{}">#{}</a>'.format(
|
||||
base, html_escape(hist[fn][2], quote=True), hist[fn][0]
|
||||
)
|
||||
else:
|
||||
margin = "-"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import time
|
||||
import socket
|
||||
import threading
|
||||
|
||||
from .__init__ import E
|
||||
from .__init__ import E, MACOS
|
||||
from .httpconn import HttpConn
|
||||
from .authsrv import AuthSrv
|
||||
|
||||
@@ -75,9 +75,11 @@ class HttpSrv(object):
|
||||
sck.shutdown(socket.SHUT_RDWR)
|
||||
sck.close()
|
||||
except (OSError, socket.error) as ex:
|
||||
self.log(
|
||||
"%s %s" % addr, "shut_rdwr err:\n {}\n {}".format(repr(sck), ex),
|
||||
)
|
||||
if not MACOS:
|
||||
self.log(
|
||||
"%s %s" % addr,
|
||||
"shut_rdwr err:\n {}\n {}".format(repr(sck), ex),
|
||||
)
|
||||
if ex.errno not in [10038, 107, 57, 9]:
|
||||
# 10038 No longer considered a socket
|
||||
# 107 Transport endpoint not connected
|
||||
|
||||
@@ -8,7 +8,7 @@ import threading
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
|
||||
from .__init__ import PY2, WINDOWS, VT100
|
||||
from .__init__ import PY2, WINDOWS, MACOS, VT100
|
||||
from .tcpsrv import TcpSrv
|
||||
from .up2k import Up2k
|
||||
from .util import mp
|
||||
@@ -111,6 +111,8 @@ class SvcHub(object):
|
||||
return msg
|
||||
elif vmin < 3:
|
||||
return msg
|
||||
elif MACOS:
|
||||
return "multiprocessing is wonky on mac osx;"
|
||||
else:
|
||||
msg = "need python 2.7 or 3.3+ for multiprocessing;"
|
||||
if not PY2 and vmin < 3:
|
||||
@@ -133,7 +135,7 @@ class SvcHub(object):
|
||||
|
||||
if mp.cpu_count() <= 1:
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
# support vscode debugger (bonus: same behavior as on windows)
|
||||
mp.set_start_method("spawn", True)
|
||||
|
||||
@@ -24,7 +24,7 @@ class TcpSrv(object):
|
||||
ip = "127.0.0.1"
|
||||
eps = {ip: "local only"}
|
||||
if self.args.i != ip:
|
||||
eps = self.detect_interfaces(self.args.i) or eps
|
||||
eps = self.detect_interfaces(self.args.i) or {self.args.i: "external"}
|
||||
|
||||
for ip, desc in sorted(eps.items(), key=lambda x: x[1]):
|
||||
self.log(
|
||||
|
||||
@@ -13,7 +13,7 @@ import threading
|
||||
from copy import deepcopy
|
||||
|
||||
from .__init__ import WINDOWS
|
||||
from .util import Pebkac, Queue, fsenc
|
||||
from .util import Pebkac, Queue, fsenc, sanitize_fn
|
||||
|
||||
|
||||
class Up2k(object):
|
||||
@@ -48,6 +48,7 @@ class Up2k(object):
|
||||
self.r_hash = re.compile("^[0-9a-zA-Z_-]{43}$")
|
||||
|
||||
def handle_json(self, cj):
|
||||
cj["name"] = sanitize_fn(cj["name"])
|
||||
wark = self._get_wark(cj)
|
||||
now = time.time()
|
||||
with self.mutex:
|
||||
|
||||
@@ -356,7 +356,30 @@ def undot(path):
|
||||
|
||||
|
||||
def sanitize_fn(fn):
|
||||
return fn.replace("\\", "/").split("/")[-1].strip()
|
||||
fn = fn.replace("\\", "/").split("/")[-1]
|
||||
|
||||
if WINDOWS:
|
||||
for bad, good in [
|
||||
["<", "<"],
|
||||
[">", ">"],
|
||||
[":", ":"],
|
||||
['"', """],
|
||||
["/", "/"],
|
||||
["\\", "\"],
|
||||
["|", "|"],
|
||||
["?", "?"],
|
||||
["*", "*"],
|
||||
]:
|
||||
fn = fn.replace(bad, good)
|
||||
|
||||
bad = ["con", "prn", "aux", "nul"]
|
||||
for n in range(1, 10):
|
||||
bad += "com{0} lpt{0}".format(n).split(" ")
|
||||
|
||||
if fn.lower() in bad:
|
||||
fn = "_" + fn
|
||||
|
||||
return fn.strip()
|
||||
|
||||
|
||||
def exclude_dotfiles(filepaths):
|
||||
|
||||
@@ -68,7 +68,7 @@ a {
|
||||
}
|
||||
#files thead th:last-child {
|
||||
background: #444;
|
||||
border-radius: .7em 0 0 0;
|
||||
border-radius: .7em .7em 0 0;
|
||||
}
|
||||
#files thead th:first-child {
|
||||
background: #222;
|
||||
|
||||
@@ -34,6 +34,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
|
||||
}
|
||||
document.body.style.fontSize = '0.8em';
|
||||
document.body.style.padding = '0 1em 1em 1em';
|
||||
hcroak(html.join('\n'));
|
||||
};
|
||||
|
||||
@@ -78,6 +79,39 @@ function ev(e) {
|
||||
}
|
||||
|
||||
|
||||
function sortTable(table, col) {
|
||||
var tb = table.tBodies[0], // use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
|
||||
th = table.tHead.rows[0].cells,
|
||||
tr = Array.prototype.slice.call(tb.rows, 0),
|
||||
i, reverse = th[col].className == 'sort1' ? -1 : 1;
|
||||
for (var a = 0, thl = th.length; a < thl; a++)
|
||||
th[a].className = '';
|
||||
th[col].className = 'sort' + reverse;
|
||||
var stype = th[col].getAttribute('sort');
|
||||
tr = tr.sort(function (a, b) {
|
||||
var v1 = a.cells[col].textContent.trim();
|
||||
var v2 = b.cells[col].textContent.trim();
|
||||
if (stype == 'int') {
|
||||
v1 = parseInt(v1.replace(/,/g, ''));
|
||||
v2 = parseInt(v2.replace(/,/g, ''));
|
||||
return reverse * (v1 - v2);
|
||||
}
|
||||
return reverse * (v1.localeCompare(v2));
|
||||
});
|
||||
for (i = 0; i < tr.length; ++i) tb.appendChild(tr[i]);
|
||||
}
|
||||
function makeSortable(table) {
|
||||
var th = table.tHead, i;
|
||||
th && (th = th.rows[0]) && (th = th.cells);
|
||||
if (th) i = th.length;
|
||||
else return; // if no `<thead>` then do nothing
|
||||
while (--i >= 0) (function (i) {
|
||||
th[i].addEventListener('click', function () { sortTable(table, i) });
|
||||
}(i));
|
||||
}
|
||||
makeSortable(o('files'));
|
||||
|
||||
|
||||
// extract songs + add play column
|
||||
var mp = (function () {
|
||||
var tracks = [];
|
||||
|
||||
@@ -4,10 +4,11 @@ html, body {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
#mtw {
|
||||
display: none;
|
||||
}
|
||||
#mw {
|
||||
width: 48.5em;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 6em;
|
||||
}
|
||||
pre, code, a {
|
||||
color: #480;
|
||||
@@ -76,6 +77,9 @@ h2 {
|
||||
padding-left: .4em;
|
||||
margin-top: 3em;
|
||||
}
|
||||
h3 {
|
||||
border-bottom: .1em solid #999;
|
||||
}
|
||||
h1 a, h3 a, h5 a,
|
||||
h2 a, h4 a, h6 a {
|
||||
color: inherit;
|
||||
@@ -84,19 +88,19 @@ h2 a, h4 a, h6 a {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#m ul,
|
||||
#m ol {
|
||||
#mp ul,
|
||||
#mp ol {
|
||||
border-left: .3em solid #ddd;
|
||||
}
|
||||
#m>ul,
|
||||
#m>ol {
|
||||
border-color: #bbb;
|
||||
}
|
||||
#m ul>li {
|
||||
#mp ul>li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
#m ul>li,
|
||||
#m ol>li {
|
||||
#mp ul>li,
|
||||
#mp ol>li {
|
||||
margin: .7em 0;
|
||||
}
|
||||
p>em,
|
||||
@@ -116,8 +120,9 @@ small {
|
||||
opacity: .8;
|
||||
}
|
||||
#toc {
|
||||
width: 48.5em;
|
||||
margin: 0 auto;
|
||||
margin: 0 1em;
|
||||
-ms-scroll-chaining: none;
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
#toc ul {
|
||||
padding-left: 1em;
|
||||
@@ -181,10 +186,24 @@ blink {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen {
|
||||
html, body {
|
||||
margin: 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 {
|
||||
color: #fff;
|
||||
@@ -212,15 +231,23 @@ blink {
|
||||
padding: .5em 0;
|
||||
}
|
||||
#mn {
|
||||
font-weight: normal;
|
||||
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.2em 0 1em 1em;
|
||||
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.3);
|
||||
background: #f7f7f7;
|
||||
}
|
||||
#mn a {
|
||||
color: #444;
|
||||
background: none;
|
||||
margin: 0 0 0 -.2em;
|
||||
padding: 0 0 0 .4em;
|
||||
padding: .3em 0 .3em .4em;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
/* ie: */
|
||||
@@ -239,8 +266,8 @@ blink {
|
||||
height: 1.05em;
|
||||
margin: -.2em .3em -.2em -.4em;
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
border-width: .05em .05em 0 0;
|
||||
border: 1px solid rgba(0,0,0,0.2);
|
||||
border-width: .2em .2em 0 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
#mn a:hover {
|
||||
@@ -248,7 +275,19 @@ blink {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#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 +309,12 @@ blink {
|
||||
html.dark #toc li {
|
||||
border-width: 0;
|
||||
}
|
||||
html.dark #m a,
|
||||
html.dark #mh a {
|
||||
html.dark #mp a {
|
||||
background: #057;
|
||||
}
|
||||
html.dark #m h1 a, html.dark #m h4 a,
|
||||
html.dark #m h2 a, html.dark #m h5 a,
|
||||
html.dark #m h3 a, html.dark #m h6 a {
|
||||
html.dark #mp h1 a, html.dark #mp h4 a,
|
||||
html.dark #mp h2 a, html.dark #mp h5 a,
|
||||
html.dark #mp h3 a, html.dark #mp h6 a {
|
||||
color: inherit;
|
||||
background: none;
|
||||
}
|
||||
@@ -286,8 +324,8 @@ blink {
|
||||
background: #1a1a1a;
|
||||
border: .07em solid #333;
|
||||
}
|
||||
html.dark #m ul,
|
||||
html.dark #m ol {
|
||||
html.dark #mp ul,
|
||||
html.dark #mp ol {
|
||||
border-color: #444;
|
||||
}
|
||||
html.dark #m>ul,
|
||||
@@ -322,26 +360,44 @@ blink {
|
||||
html.dark #mn a {
|
||||
color: #ccc;
|
||||
}
|
||||
html.dark #mn {
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
html.dark #mn,
|
||||
html.dark #mh {
|
||||
background: #222;
|
||||
}
|
||||
html.dark #mh a {
|
||||
color: #ccc;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 64em) {
|
||||
|
||||
@media screen and (min-width: 70em) {
|
||||
#mw {
|
||||
margin-left: 14em;
|
||||
margin-left: calc(100% - 50em);
|
||||
position: fixed;
|
||||
overflow-y: auto;
|
||||
left: 14em;
|
||||
left: calc(100% - 57em);
|
||||
max-width: none;
|
||||
bottom: 0;
|
||||
scrollbar-color: #eb0 #f7f7f7;
|
||||
}
|
||||
#toc {
|
||||
width: 13em;
|
||||
width: calc(100% - 52.3em);
|
||||
width: calc(100% - 57.3em);
|
||||
max-width: 30em;
|
||||
background: #eee;
|
||||
position: fixed;
|
||||
overflow-y: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
bottom: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-shadow: 0 0 1em #ccc;
|
||||
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 {
|
||||
border-left: .3em solid #ccc;
|
||||
@@ -361,13 +417,22 @@ blink {
|
||||
|
||||
html.dark #toc {
|
||||
background: #282828;
|
||||
border-top: 1px solid #2c2c2c;
|
||||
box-shadow: 0 0 1em #181818;
|
||||
}
|
||||
html.dark #toc,
|
||||
html.dark #mw {
|
||||
scrollbar-color: #b80 #282828;
|
||||
}
|
||||
html.dark #mn.undocked {
|
||||
box-shadow: 0 0 .5em #555;
|
||||
border: none;
|
||||
background: #0a0a0a;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 84em) {
|
||||
@media screen and (min-width: 87.5em) {
|
||||
#toc { width: 30em }
|
||||
#mw { margin-left: 32em }
|
||||
#mw { left: 30.5em }
|
||||
}
|
||||
@media print {
|
||||
a {
|
||||
|
||||
@@ -4,32 +4,73 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||
<link href="/.cpr/md.css" rel="stylesheet">
|
||||
{%- if edit %}
|
||||
<link href="/.cpr/md2.css" rel="stylesheet">
|
||||
{%- endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="mn"></div>
|
||||
<div id="mn">navbar</div>
|
||||
<div id="mh">
|
||||
<a id="lightswitch" href="#">go dark</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 id="toc"></div>
|
||||
<div id="mtw">
|
||||
<textarea id="mt">{{ md }}</textarea>
|
||||
</div>
|
||||
<div id="mw">
|
||||
<div id="mh">
|
||||
<a id="lightswitch" href="#">go dark</a> //
|
||||
<a id="edit" href="?edit">edit this</a>
|
||||
</div>
|
||||
<div id="ml">
|
||||
<div style="text-align:center;margin:5em 0">
|
||||
<div style="font-size:2em;margin:1em 0">Loading</div>
|
||||
if you're still reading this, check that javascript is allowed
|
||||
</div>
|
||||
</div>
|
||||
<div id="m">
|
||||
<textarea id="mt" style="display:none">{{ md }}</textarea>
|
||||
</div>
|
||||
<div id="mp"></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>
|
||||
{%- endif %}
|
||||
|
||||
<script>
|
||||
|
||||
var link_md_as_html = false; // TODO (does nothing)
|
||||
var last_modified = {{ lastmod }};
|
||||
|
||||
(function () {
|
||||
var btn = document.getElementById("lightswitch");
|
||||
var toggle = function () {
|
||||
var toggle = function (e) {
|
||||
if (e) e.preventDefault();
|
||||
var dark = !document.documentElement.getAttribute("class");
|
||||
document.documentElement.setAttribute("class", dark ? "dark" : "");
|
||||
btn.innerHTML = "go " + (dark ? "light" : "dark");
|
||||
@@ -41,7 +82,17 @@ var link_md_as_html = false; // TODO (does nothing)
|
||||
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 src="/.cpr/deps/marked.full.js"></script>
|
||||
<script src="/.cpr/md.js"></script>
|
||||
{%- if edit %}
|
||||
<script src="/.cpr/md2.js"></script>
|
||||
{%- endif %}
|
||||
</body></html>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
/*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_wrap = document.getElementById('mw');
|
||||
var dom_head = document.getElementById('mh');
|
||||
var dom_hbar = document.getElementById('mh');
|
||||
var dom_nav = document.getElementById('mn');
|
||||
var dom_doc = document.getElementById('m');
|
||||
var dom_md = document.getElementById('mt');
|
||||
var dom_pre = document.getElementById('mp');
|
||||
var dom_src = document.getElementById('mt');
|
||||
var dom_navtgl = document.getElementById('navtoggle');
|
||||
|
||||
// add toolbar buttons
|
||||
function hesc(txt) {
|
||||
return txt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
// add navbar
|
||||
(function () {
|
||||
var n = document.location + '';
|
||||
n = n.substr(n.indexOf('//') + 2).split('?')[0].split('/');
|
||||
@@ -22,7 +21,9 @@ var dom_md = document.getElementById('mt');
|
||||
if (a > 0)
|
||||
loc.push(n[a]);
|
||||
|
||||
nav.push('<a href="/' + loc.join('/') + '">' + n[a] + '</a>');
|
||||
var dec = hesc(decodeURIComponent(n[a]));
|
||||
|
||||
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
|
||||
}
|
||||
dom_nav.innerHTML = nav.join('');
|
||||
})();
|
||||
@@ -34,13 +35,10 @@ function convert_markdown(md_text) {
|
||||
gfm: true
|
||||
});
|
||||
var html = marked(md_text);
|
||||
dom_doc.innerHTML = html;
|
||||
|
||||
var loader = document.getElementById('ml');
|
||||
loader.parentNode.removeChild(loader);
|
||||
dom_pre.innerHTML = html;
|
||||
|
||||
// todo-lists (should probably be a marked extension)
|
||||
var nodes = dom_doc.getElementsByTagName('input');
|
||||
var nodes = dom_pre.getElementsByTagName('input');
|
||||
for (var a = nodes.length - 1; a >= 0; a--) {
|
||||
var dom_box = nodes[a];
|
||||
if (dom_box.getAttribute('type') !== 'checkbox')
|
||||
@@ -59,9 +57,32 @@ function convert_markdown(md_text) {
|
||||
'<span class="todo_' + clas + '">' + char + '</span>' +
|
||||
html.substr(html.indexOf('>') + 1);
|
||||
}
|
||||
|
||||
var manip_nodes = dom_pre.getElementsByTagName('*');
|
||||
for (var a = manip_nodes.length - 1; a >= 0; a--) {
|
||||
var el = manip_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('');
|
||||
}
|
||||
}
|
||||
|
||||
function init_toc() {
|
||||
var loader = document.getElementById('ml');
|
||||
loader.parentNode.removeChild(loader);
|
||||
|
||||
var anchors = []; // list of toc entries, complex objects
|
||||
var anchor = null; // current toc node
|
||||
var id_seen = {}; // taken IDs
|
||||
@@ -69,7 +90,7 @@ function init_toc() {
|
||||
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 = [];
|
||||
for (var a = 0, aa = manip_nodes_dyn.length; a < aa; a++)
|
||||
manip_nodes.push(manip_nodes_dyn[a]);
|
||||
@@ -77,16 +98,7 @@ function init_toc() {
|
||||
for (var a = 0, aa = manip_nodes.length; a < aa; a++) {
|
||||
var elm = manip_nodes[a];
|
||||
var m = re.exec(elm.tagName);
|
||||
|
||||
var is_header =
|
||||
m !== null;
|
||||
|
||||
var is_precode =
|
||||
!is_header &&
|
||||
elm.tagName == 'PRE' &&
|
||||
elm.childNodes.length === 1 &&
|
||||
elm.childNodes[0].tagName == 'CODE';
|
||||
|
||||
var is_header = m !== null;
|
||||
if (is_header) {
|
||||
var nlv = m[1];
|
||||
while (lv < nlv) {
|
||||
@@ -125,17 +137,6 @@ function init_toc() {
|
||||
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)
|
||||
anchor.kids.push(elm);
|
||||
}
|
||||
@@ -207,41 +208,77 @@ function init_toc() {
|
||||
|
||||
|
||||
// "main" :p
|
||||
convert_markdown(dom_md.value);
|
||||
convert_markdown(dom_src.value);
|
||||
var toc = init_toc();
|
||||
|
||||
|
||||
// scroll handler
|
||||
(function () {
|
||||
var timer_active = false;
|
||||
var final = null;
|
||||
var redraw = (function () {
|
||||
var sbs = false;
|
||||
function onresize() {
|
||||
sbs = window.matchMedia('(min-width: 64em)').matches;
|
||||
var y = (dom_hbar.offsetTop + dom_hbar.offsetHeight) + 'px';
|
||||
if (sbs) {
|
||||
dom_toc.style.top = y;
|
||||
dom_wrap.style.top = y;
|
||||
dom_toc.style.marginTop = '0';
|
||||
}
|
||||
onscroll();
|
||||
}
|
||||
|
||||
function onscroll() {
|
||||
clearTimeout(final);
|
||||
timer_active = false;
|
||||
toc.refresh();
|
||||
|
||||
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();
|
||||
|
||||
function ev_onscroll() {
|
||||
// long timeout: scroll ended
|
||||
clearTimeout(final);
|
||||
final = setTimeout(onscroll, 100);
|
||||
window.onresize = onresize;
|
||||
window.onscroll = onscroll;
|
||||
dom_wrap.onscroll = onscroll;
|
||||
|
||||
// short timeout: continuous updates
|
||||
if (timer_active)
|
||||
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;
|
||||
|
||||
timer_active = true;
|
||||
setTimeout(onscroll, 10);
|
||||
};
|
||||
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';
|
||||
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);
|
||||
|
||||
window.onscroll = ev_onscroll;
|
||||
window.onresize = ev_onscroll;
|
||||
})();
|
||||
redraw();
|
||||
};
|
||||
|
||||
if (window.localStorage && localStorage.getItem('hidenav') == 1)
|
||||
dom_navtgl.onclick();
|
||||
|
||||
102
copyparty/web/md2.css
Normal file
102
copyparty/web/md2.css
Normal file
@@ -0,0 +1,102 @@
|
||||
#toc {
|
||||
display: none;
|
||||
}
|
||||
#mtw {
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: calc(100% - 58em);
|
||||
}
|
||||
#mw {
|
||||
left: calc(100% - 57em);
|
||||
}
|
||||
|
||||
|
||||
/* single-screen */
|
||||
#mtw.preview,
|
||||
#mw.editor {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
#mw.preview,
|
||||
#mtw.editor {
|
||||
z-index: 3;
|
||||
}
|
||||
#mtw.single,
|
||||
#mw.single {
|
||||
left: calc((100% - 58em) / 2);
|
||||
margin: 0;
|
||||
}
|
||||
#mtw.single {
|
||||
width: 57em;
|
||||
}
|
||||
|
||||
|
||||
#mp {
|
||||
position: relative;
|
||||
}
|
||||
#mt, #mtr {
|
||||
width: 100%;
|
||||
height: calc(100% - 5px);
|
||||
color: #444;
|
||||
background: #f7f7f7;
|
||||
border: 1px solid #999;
|
||||
font-family: 'consolas', monospace, monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word; /*ie*/
|
||||
overflow-y: scroll;
|
||||
line-height: 1.3em;
|
||||
font-size: .9em;
|
||||
position: relative;
|
||||
}
|
||||
html.dark #mt {
|
||||
color: #eee;
|
||||
background: #222;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
#mtr {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
/* dbg:
|
||||
#mt {
|
||||
opacity: .5;
|
||||
}
|
||||
*/
|
||||
609
copyparty/web/md2.js
Normal file
609
copyparty/web/md2.js
Normal file
@@ -0,0 +1,609 @@
|
||||
// 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;
|
||||
})();
|
||||
|
||||
|
||||
// replace it with the real deal in the console
|
||||
var dbg = function () { };
|
||||
// dbg = console.log
|
||||
|
||||
|
||||
// line->scrollpos maps
|
||||
var map_src = [];
|
||||
var map_pre = [];
|
||||
function genmap(dom) {
|
||||
var ret = [];
|
||||
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);
|
||||
|
||||
ret.push(parent_y + n.offsetTop);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// input handler
|
||||
var action_stack = null;
|
||||
var nlines = 0;
|
||||
(function () {
|
||||
dom_src.oninput = function (e) {
|
||||
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);
|
||||
|
||||
var sb = document.getElementById('save');
|
||||
var cl = (sb.getAttribute('class') + '').replace(/ disabled/, "");
|
||||
if (src == server_md)
|
||||
cl += ' disabled';
|
||||
|
||||
sb.setAttribute('class', cl);
|
||||
if (action_stack)
|
||||
action_stack.push();
|
||||
}
|
||||
dom_src.oninput();
|
||||
})();
|
||||
|
||||
|
||||
// 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 = (dom_src.offsetWidth - 4) + '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;
|
||||
dom_src.oninput();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// returns car/cdr (selection bounds) and n1/n2 (grown to full lines)
|
||||
function linebounds(just_car) {
|
||||
var car = dom_src.selectionStart,
|
||||
cdr = dom_src.selectionEnd;
|
||||
|
||||
dbg(car, cdr);
|
||||
|
||||
if (just_car)
|
||||
cdr = car;
|
||||
|
||||
var md = dom_src.value,
|
||||
n1 = Math.max(car, 0),
|
||||
n2 = Math.min(cdr, md.length - 1);
|
||||
|
||||
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);
|
||||
try {
|
||||
dom_src.oninput();
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
|
||||
|
||||
// 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(!shift),
|
||||
ln = s.md.substring(s.n1, s.n2),
|
||||
m = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/.exec(ln),
|
||||
home = s.n1 + m[0].length,
|
||||
car = (s.car == home) ? s.n1 : home,
|
||||
cdr = shift ? s.cdr : car;
|
||||
|
||||
if (car > cdr)
|
||||
car = [cdr, cdr = car][0];
|
||||
|
||||
dom_src.setSelectionRange(car, cdr);
|
||||
}
|
||||
|
||||
|
||||
// autoindent
|
||||
function md_newline() {
|
||||
var s = linebounds(true),
|
||||
ln = s.md.substring(s.n1, s.n2),
|
||||
m = /^[ \t#>+-]*(\* )?([0-9]+\. +)?/.exec(ln);
|
||||
|
||||
s.pre = s.md.substring(0, s.car) + '\n' + m[0];
|
||||
s.sel = '';
|
||||
s.post = s.md.substring(s.car);
|
||||
s.car = s.cdr = s.pre.length;
|
||||
setsel(s);
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 undos = [];
|
||||
var redos = [];
|
||||
var sched_txt = '';
|
||||
var sched_timer = null;
|
||||
var ignore = false;
|
||||
var ref = dom_src.value;
|
||||
|
||||
function diff(from, to) {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
function undiff(from, change) {
|
||||
return {
|
||||
txt: from.substring(0, change.car) + change.txt + from.substring(change.cdr),
|
||||
cursor: change.car + change.txt.length
|
||||
};
|
||||
}
|
||||
|
||||
function apply(src, dst) {
|
||||
dbg('undos(%d) redos(%d)', undos.length, redos.length);
|
||||
|
||||
if (src.length === 0)
|
||||
return false;
|
||||
|
||||
var state = undiff(ref, src.pop()),
|
||||
change = diff(ref, state.txt);
|
||||
|
||||
if (change === null)
|
||||
return false;
|
||||
|
||||
dst.push(change);
|
||||
ref = state.txt;
|
||||
ignore = true; // just some browsers
|
||||
dom_src.value = ref;
|
||||
dom_src.setSelectionRange(state.cursor, state.cursor);
|
||||
ignore = true; // all browsers
|
||||
dom_src.oninput();
|
||||
return true;
|
||||
}
|
||||
|
||||
function schedule_push() {
|
||||
if (ignore) {
|
||||
ignore = false;
|
||||
return;
|
||||
}
|
||||
redos = [];
|
||||
sched_txt = dom_src.value;
|
||||
clearTimeout(sched_timer);
|
||||
sched_timer = setTimeout(push, 500);
|
||||
}
|
||||
|
||||
function undo() {
|
||||
return apply(undos, redos);
|
||||
}
|
||||
|
||||
function redo() {
|
||||
return apply(redos, undos);
|
||||
}
|
||||
|
||||
function push() {
|
||||
var change = diff(ref, sched_txt, dom_src.selectionStart);
|
||||
if (change !== null)
|
||||
undos.push(change);
|
||||
|
||||
ref = sched_txt;
|
||||
dbg('undos(%d) redos(%d)', undos.length, redos.length);
|
||||
if (undos.length > 0)
|
||||
dbg(undos.slice(-1)[0]);
|
||||
if (redos.length > 0)
|
||||
dbg(redos.slice(-1)[0]);
|
||||
}
|
||||
|
||||
return {
|
||||
push: push,
|
||||
undo: undo,
|
||||
redo: redo,
|
||||
push: schedule_push,
|
||||
_undos: undos,
|
||||
_redos: redos,
|
||||
_ref: ref
|
||||
}
|
||||
})();
|
||||
@@ -21,7 +21,6 @@ html, body {
|
||||
#mn {
|
||||
font-weight: normal;
|
||||
margin: 1.3em 0 .7em 1em;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
#mn a {
|
||||
color: #444;
|
||||
@@ -44,8 +43,8 @@ html, body {
|
||||
height: 1.05em;
|
||||
margin: -.2em .3em -.2em -.4em;
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(0,0,0,0.3);
|
||||
border-width: .05em .05em 0 0;
|
||||
border: 1px solid rgba(0,0,0,0.2);
|
||||
border-width: .2em .2em 0 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
#mn a:hover {
|
||||
|
||||
@@ -13,7 +13,9 @@ var dom_md = document.getElementById('mt');
|
||||
if (a > 0)
|
||||
loc.push(n[a]);
|
||||
|
||||
nav.push('<a href="/' + loc.join('/') + '">' + n[a] + '</a>');
|
||||
var dec = decodeURIComponent(n[a]).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
|
||||
}
|
||||
dom_nav.innerHTML = nav.join('');
|
||||
})();
|
||||
@@ -51,7 +53,8 @@ var mde = (function () {
|
||||
"save": "Ctrl-S"
|
||||
},
|
||||
insertTexts: ["[](", ")"],
|
||||
tabSize: 4,
|
||||
indentWithTabs: false,
|
||||
tabSize: 2,
|
||||
toolbar: tbar,
|
||||
previewClass: 'mdo',
|
||||
onToggleFullScreen: set_jumpto,
|
||||
|
||||
@@ -13,6 +13,7 @@ h1 {
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin: 2em 0 .4em 0;
|
||||
padding: 0 0 .2em 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
li {
|
||||
margin: 1em 0;
|
||||
@@ -24,4 +25,29 @@ a {
|
||||
border-bottom: 1px solid #aaa;
|
||||
border-radius: .2em;
|
||||
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>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- script src="/.cpr/splash.js"></script -->
|
||||
</body>
|
||||
<script>
|
||||
|
||||
if (window.localStorage && localStorage.getItem('darkmode') == 1)
|
||||
document.documentElement.setAttribute("class", "dark");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -34,6 +34,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
|
||||
}
|
||||
document.body.style.fontSize = '0.8em';
|
||||
document.body.style.padding = '0 1em 1em 1em';
|
||||
hcroak(html.join('\n'));
|
||||
};
|
||||
|
||||
@@ -211,7 +212,8 @@ function up2k_init(have_crypto) {
|
||||
// handle user intent to use the basic uploader instead
|
||||
o('u2nope').onclick = function (e) {
|
||||
e.preventDefault();
|
||||
un2k();
|
||||
setmsg('');
|
||||
goto('bup');
|
||||
};
|
||||
|
||||
if (!String.prototype.format) {
|
||||
|
||||
@@ -66,5 +66,5 @@
|
||||
</table>
|
||||
|
||||
<p id="u2foot"></p>
|
||||
<p>( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope" onclick="javascript:goto('bup');">basic uploader</a>)</p>
|
||||
<p>( if you don't need lastmod timestamps, resumable uploads or progress bars just use the <a href="#" id="u2nope">basic uploader</a>)</p>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,9 @@ echo
|
||||
#
|
||||
# `no-ogv` saves ~500k by removing the opus/vorbis audio codecs
|
||||
# (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 &&
|
||||
@@ -21,6 +24,7 @@ command -v gfind >/dev/null && {
|
||||
sed() { gsed "$@"; }
|
||||
find() { gfind "$@"; }
|
||||
sort() { gsort "$@"; }
|
||||
unexpand() { gunexpand "$@"; }
|
||||
}
|
||||
|
||||
[ -e copyparty/__main__.py ] || cd ..
|
||||
@@ -35,9 +39,15 @@ while [ ! -z "$1" ]; do
|
||||
[ "$1" = clean ] && clean=1 && shift && continue
|
||||
[ "$1" = re ] && repack=1 && shift && continue
|
||||
[ "$1" = no-ogv ] && no_ogv=1 && shift && continue
|
||||
[ "$1" = no-cm ] && no_cm=1 && shift && continue
|
||||
break
|
||||
done
|
||||
|
||||
tmv() {
|
||||
touch -r "$1" t
|
||||
mv t "$1"
|
||||
}
|
||||
|
||||
rm -rf sfx/*
|
||||
mkdir -p sfx build
|
||||
cd sfx
|
||||
@@ -62,7 +72,15 @@ cd sfx
|
||||
|
||||
tar -zxf $f
|
||||
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
|
||||
echo collecting source
|
||||
@@ -98,10 +116,27 @@ rm -f copyparty/web/deps/*.full.*
|
||||
|
||||
# it's fine dw
|
||||
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 ] &&
|
||||
rm -rf copyparty/web/deps/{dynamicaudio,ogv}* copyparty/web/browser.js
|
||||
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
|
||||
args=(--owner=1000 --group=1000)
|
||||
@@ -131,20 +166,3 @@ chmod 755 $sfx_out.*
|
||||
printf "done:\n"
|
||||
printf " %s\n" "$(realpath $sfx_out)."{sh,py}
|
||||
# rm -rf *
|
||||
|
||||
# -rw-r--r-- 1 ed ed 811271 May 5 14:35 tar.bz2
|
||||
# -rw-r--r-- 1 ed ed 732016 May 5 14:35 tar.xz
|
||||
|
||||
# -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():
|
||||
ver, chk = read_py(sys.executable)
|
||||
if chk:
|
||||
if chk or PY2:
|
||||
return [[chk, ver, 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:***
|
||||
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