mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
NIH! NIH! NIH!
This commit is contained in:
parent
c15ecb6c8e
commit
316e3abfab
|
@ -834,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:
|
||||
|
@ -844,18 +844,25 @@ 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
|
||||
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": "",
|
||||
|
@ -868,9 +875,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:
|
||||
|
|
|
@ -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;
|
||||
|
@ -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: */
|
||||
|
@ -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,8 +309,7 @@ blink {
|
|||
html.dark #toc li {
|
||||
border-width: 0;
|
||||
}
|
||||
html.dark #m a,
|
||||
html.dark #mh a {
|
||||
html.dark #m a {
|
||||
background: #057;
|
||||
}
|
||||
html.dark #m h1 a, html.dark #m h4 a,
|
||||
|
@ -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,61 @@
|
|||
<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="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
|
||||
|
||||
</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 +70,19 @@ var link_md_as_html = false; // TODO (does nothing)
|
|||
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 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,7 @@ var dom_md = document.getElementById('mt');
|
|||
if (a > 0)
|
||||
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>');
|
||||
}
|
||||
|
@ -36,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')
|
||||
|
@ -61,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
|
||||
|
@ -71,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]);
|
||||
|
@ -79,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) {
|
||||
|
@ -127,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);
|
||||
}
|
||||
|
@ -209,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();
|
||||
|
|
75
copyparty/web/md2.css
Normal file
75
copyparty/web/md2.css
Normal 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
388
copyparty/web/md2.js
Normal 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';
|
||||
};
|
||||
};
|
|
@ -21,7 +21,6 @@ html, body {
|
|||
#mn {
|
||||
font-weight: normal;
|
||||
margin: 1.3em 0 .7em 1em;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
#mn a {
|
||||
color: #444;
|
||||
|
|
|
@ -53,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>
|
|
@ -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 &&
|
||||
|
@ -35,6 +38,7 @@ 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
|
||||
|
||||
|
@ -103,6 +107,11 @@ while IFS= read -r x; do sed -ri 's/\.full\.(js|css)/.\1/g' "$x"; done
|
|||
[ $no_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
|
||||
args=(--owner=1000 --group=1000)
|
||||
[ "$OSTYPE" = msys ] &&
|
||||
|
|
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue