NIH! NIH! NIH!

This commit is contained in:
ed 2020-05-11 01:38:30 +02:00
parent c15ecb6c8e
commit 316e3abfab
12 changed files with 779 additions and 101 deletions

View file

@ -834,7 +834,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 +844,25 @@ 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
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 +875,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:

View file

@ -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,6 +77,9 @@ 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;
@ -116,8 +120,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 +186,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 +231,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.2em 0 1em 1em;
box-shadow: 0 0 .5em rgba(0, 0, 0, 0.3);
background: #f7f7f7;
} }
#mn a { #mn a {
color: #444; color: #444;
background: none; background: none;
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 +275,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,8 +309,7 @@ blink {
html.dark #toc li { html.dark #toc li {
border-width: 0; border-width: 0;
} }
html.dark #m a, html.dark #m a {
html.dark #mh a {
background: #057; background: #057;
} }
html.dark #m h1 a, html.dark #m h4 a, html.dark #m h1 a, html.dark #m h4 a,
@ -322,26 +360,44 @@ blink {
html.dark #mn a { html.dark #mn a {
color: #ccc; 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 { #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 +417,22 @@ 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 #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 } #toc { width: 30em }
#mw { margin-left: 32em } #mw { left: 30.5em }
} }
@media print { @media print {
a { a {

View file

@ -4,32 +4,61 @@
<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="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="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="toc"></div>
<div id="mtw">
<textarea id="mt">{{ md }}</textarea>
</div>
<div id="mw"> <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 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> </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
</textarea>
</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 +70,19 @@ var link_md_as_html = false; // TODO (does nothing)
toggle(); toggle();
})(); })();
// TODO babel or es5 shims (fix Object.defineProperty too)
// https://stackoverflow.com/questions/48803257/
if (!String.startsWith) {
String.prototype.startsWith = function(search, rawPos) {
var pos = rawPos > 0 ? rawPos|0 : 0;
return this.substring(pos, pos + search.length) === search;
};
}
</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>

View file

@ -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_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 function hesc(txt) {
return txt.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
// 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,7 +21,7 @@ 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); var dec = hesc(decodeURIComponent(n[a]));
nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>'); nav.push('<a href="/' + loc.join('/') + '">' + dec + '</a>');
} }
@ -36,13 +35,10 @@ function convert_markdown(md_text) {
gfm: true gfm: true
}); });
var html = marked(md_text); var html = marked(md_text);
dom_doc.innerHTML = html; dom_pre.innerHTML = html;
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 = dom_pre.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,9 +57,32 @@ 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);
} }
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() { 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 id_seen = {}; // taken IDs
@ -71,7 +90,7 @@ function init_toc() {
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 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]);
@ -79,16 +98,7 @@ function init_toc() {
for (var a = 0, aa = manip_nodes.length; a < aa; a++) { for (var a = 0, aa = manip_nodes.length; a < aa; a++) {
var elm = manip_nodes[a]; var elm = manip_nodes[a];
var m = re.exec(elm.tagName); var m = re.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) {
@ -127,17 +137,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 +208,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;
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() { function onscroll() {
clearTimeout(final);
timer_active = false;
toc.refresh(); 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() { window.onresize = onresize;
// long timeout: scroll ended window.onscroll = onscroll;
clearTimeout(final); dom_wrap.onscroll = onscroll;
final = setTimeout(onscroll, 100);
// short timeout: continuous updates onresize();
if (timer_active) 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);
window.onscroll = ev_onscroll; redraw();
window.onresize = ev_onscroll; };
})();
if (window.localStorage && localStorage.getItem('hidenav') == 1)
dom_navtgl.onclick();

75
copyparty/web/md2.css Normal file
View file

@ -0,0 +1,75 @@
#toc {
display: none;
}
#mtw {
display: block;
position: fixed;
left: 0;
bottom: 0;
width: calc(100% - 58em);
}
#mw {
left: calc(100% - 57em);
}
#mp {
position: relative;
}
#mt, #mtr {
width: 100%;
height: 100%;
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;
}
#helpbox {
display: none;
position: fixed;
background: #f7f7f7;
box-shadow: 0 .5em 2em #777;
border-radius: .4em;
padding: 2em;
top: 4em;
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;
}
*/

388
copyparty/web/md2.js Normal file
View file

@ -0,0 +1,388 @@
// server state
var server_md = dom_src.value;
// dom nodes
var dom_swrap = document.getElementById('mtw');
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 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 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);
}
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);
console.log(document.body.clientWidth + 'x' + document.body.clientHeight);
};
window.onresize = onresize;
window.onscroll = null;
dom_wrap.onscroll = null;
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 == 'disabled') {
alert('there is nothing to save');
return;
}
var force = save_cls == 'force-save';
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;
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 [before,selection,after]
function getsel() {
var car = dom_src.selectionStart;
var cdr = dom_src.selectionEnd;
console.log(car, cdr);
var txt = dom_src.value;
car = Math.max(car, 0);
cdr = Math.min(cdr, txt.length - 1);
if (car < cdr && txt[car] == '\n')
car++;
if (car < cdr && txt[cdr - 1] == '\n')
cdr -= 2;
car = txt.lastIndexOf('\n', car - 1) + 1;
cdr = txt.indexOf('\n', cdr);
if (cdr < car)
cdr = txt.length;
return [
txt.substring(0, car),
txt.substring(car, cdr),
txt.substring(cdr)
];
}
// place modified getsel into markdown
function setsel(a, b, c) {
dom_src.value = [a, b, c].join('');
dom_src.setSelectionRange(a.length, a.length + b.length);
dom_src.oninput();
}
// indent/dedent
function md_indent(dedent) {
var r = getsel(),
pre = r[0],
sel = r[1],
post = r[2];
if (dedent)
sel = sel.replace(/^ /, "").replace(/\n /g, "\n");
else
sel = ' ' + sel.replace(/\n/g, '\n ');
setsel(pre, sel, post);
}
// header
function md_header(dedent) {
var r = getsel(),
pre = r[0],
sel = r[1],
post = r[2];
if (dedent)
sel = sel.replace(/^#/, "").replace(/^ +/, "");
else
sel = sel.replace(/^(#*) ?/, "#$1 ");
setsel(pre, sel, post);
}
// hotkeys
(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;
}
}
}
document.onkeydown = keydown;
})();
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';
};
};

View file

@ -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;

View file

@ -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,

View file

@ -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;
}

View file

@ -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>

View file

@ -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 &&
@ -35,6 +38,7 @@ 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
@ -103,6 +107,11 @@ while IFS= read -r x; do sed -ri 's/\.full\.(js|css)/.\1/g' "$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*
sed -ri '/edit2">edit \(fancy/d' copyparty/web/md.html
}
echo creating tar echo creating tar
args=(--owner=1000 --group=1000) args=(--owner=1000 --group=1000)
[ "$OSTYPE" = msys ] && [ "$OSTYPE" = msys ] &&

View file

@ -1,3 +1,33 @@
### hello world
```
[72....................................................................]
[80............................................................................]
```
* foo
```
[72....................................................................]
[80............................................................................]
```
* bar
```
[72....................................................................]
[80............................................................................]
```
a123456789b123456789c123456789d123456789e123456789f123456789g123456789h123456789i123456789j123456789k123456789l123456789m123456789n123456789o123456789p123456789q123456789r123456789s123456789t123456789u123456789v123456789w123456789x123456789y123456789z123456789
<foo> &nbsp; bar &amp; <span>baz</span>
<a href="?foo=bar&baz=qwe&amp;rty">?foo=bar&baz=qwe&amp;rty</a>
<!-- hidden -->
```
<foo> &nbsp; bar &amp; <span>baz</span>
<a href="?foo=bar&baz=qwe&amp;rty">?foo=bar&baz=qwe&amp;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