mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8b502a7235 | ||
| 
						 | 
					37567844af | ||
| 
						 | 
					2f6c4e0e34 | ||
| 
						 | 
					1c7cc4cb2b | ||
| 
						 | 
					f83db3648e | ||
| 
						 | 
					b164aa00d4 | ||
| 
						 | 
					a2d866d0c2 | ||
| 
						 | 
					2dfe4ac4c6 | ||
| 
						 | 
					db65d05cb5 | ||
| 
						 | 
					300c0194c7 | ||
| 
						 | 
					37a0d2b087 | ||
| 
						 | 
					a4959300ea | 
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -10,6 +10,8 @@
 | 
			
		||||
            "cwd": "${workspaceFolder}",
 | 
			
		||||
            "args": [
 | 
			
		||||
                //"-nw",
 | 
			
		||||
                "-ed",
 | 
			
		||||
                "-emp",
 | 
			
		||||
                "-a",
 | 
			
		||||
                "ed:wark",
 | 
			
		||||
                "-v",
 | 
			
		||||
 
 | 
			
		||||
@@ -87,16 +87,18 @@ the features you can opt to drop are
 | 
			
		||||
 | 
			
		||||
for the `re`pack to work, first run one of the sfx'es once to unpack it
 | 
			
		||||
 | 
			
		||||
**note:** you can also just download and run [scripts/copyparty-repack.sh](scripts/copyparty-repack.sh) -- this will grab the latest copyparty release from github and do a `no-ogv no-cm` repack; works on linux/macos (and windows with msys2 or WSL)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# install on android
 | 
			
		||||
 | 
			
		||||
install [Termux](https://termux.com/) (see [ocv.me/termux](https://ocv.me/termux/)) and then copy-paste this into Termux (long-tap) all at once:
 | 
			
		||||
```sh
 | 
			
		||||
apt update && apt -y full-upgrade && termux-setup-storage && apt -y install curl && cd && curl -L https://github.com/9001/copyparty/raw/master/scripts/copyparty-android.sh > copyparty-android.sh && chmod 755 copyparty-android.sh && ./copyparty-android.sh -h
 | 
			
		||||
apt update && apt -y full-upgrade && termux-setup-storage && apt -y install python && python -m ensurepip && python -m pip install -U copyparty
 | 
			
		||||
echo $?
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
after the initial setup (and restarting bash), you can launch copyparty at any time by running "copyparty" in Termux
 | 
			
		||||
after the initial setup, you can launch copyparty at any time by running `copyparty` anywhere in Termux
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# dev env setup
 | 
			
		||||
 
 | 
			
		||||
@@ -136,6 +136,7 @@ def main():
 | 
			
		||||
    ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume")
 | 
			
		||||
    ap.add_argument("-q", action="store_true", help="quiet")
 | 
			
		||||
    ap.add_argument("-ed", action="store_true", help="enable ?dots")
 | 
			
		||||
    ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
 | 
			
		||||
    ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
 | 
			
		||||
    ap.add_argument("-nih", action="store_true", help="no info hostname")
 | 
			
		||||
    ap.add_argument("-nid", action="store_true", help="no info disk-usage")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
VERSION = (0, 5, 4)
 | 
			
		||||
VERSION = (0, 5, 6)
 | 
			
		||||
CODENAME = "fuse jelly"
 | 
			
		||||
BUILD_DT = (2020, 11, 17)
 | 
			
		||||
BUILD_DT = (2020, 11, 29)
 | 
			
		||||
 | 
			
		||||
S_VERSION = ".".join(map(str, VERSION))
 | 
			
		||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
			
		||||
 
 | 
			
		||||
@@ -963,6 +963,7 @@ class HttpCli(object):
 | 
			
		||||
            "edit": "edit" in self.uparam,
 | 
			
		||||
            "title": html_escape(self.vpath),
 | 
			
		||||
            "lastmod": int(ts_md * 1000),
 | 
			
		||||
            "md_plug": "true" if self.args.emp else "false",
 | 
			
		||||
            "md": "",
 | 
			
		||||
        }
 | 
			
		||||
        sz_html = len(template.render(**targs).encode("utf-8"))
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <script src="/.cpr/util.js{{ ts }}"></script>
 | 
			
		||||
 | 
			
		||||
    {%- if can_read %}
 | 
			
		||||
    <script src="/.cpr/browser.js{{ ts }}"></script>
 | 
			
		||||
    {%- endif %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,75 +1,9 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// error handler for mobile devices
 | 
			
		||||
function hcroak(msg) {
 | 
			
		||||
	document.body.innerHTML = msg;
 | 
			
		||||
	window.onerror = undefined;
 | 
			
		||||
	throw 'fatal_err';
 | 
			
		||||
}
 | 
			
		||||
function croak(msg) {
 | 
			
		||||
	document.body.textContent = msg;
 | 
			
		||||
	window.onerror = undefined;
 | 
			
		||||
	throw msg;
 | 
			
		||||
}
 | 
			
		||||
function esc(txt) {
 | 
			
		||||
	return txt.replace(/[&"<>]/g, function (c) {
 | 
			
		||||
		return {
 | 
			
		||||
			'&': '&',
 | 
			
		||||
			'"': '"',
 | 
			
		||||
			'<': '<',
 | 
			
		||||
			'>': '>'
 | 
			
		||||
		}[c];
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
 | 
			
		||||
	window.onerror = undefined;
 | 
			
		||||
	var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
			
		||||
		esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
			
		||||
 | 
			
		||||
	if (error) {
 | 
			
		||||
		var find = ['desc', 'stack', 'trace'];
 | 
			
		||||
		for (var a = 0; a < find.length; a++)
 | 
			
		||||
			if (String(error[find[a]]) !== 'undefined')
 | 
			
		||||
				html.push('<h2>' + find[a] + '</h2>' +
 | 
			
		||||
					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'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
 | 
			
		||||
if (!String.prototype.endsWith) {
 | 
			
		||||
	String.prototype.endsWith = function (search, this_len) {
 | 
			
		||||
		if (this_len === undefined || this_len > this.length) {
 | 
			
		||||
			this_len = this.length;
 | 
			
		||||
		}
 | 
			
		||||
		return this.substring(this_len - search.length, this_len) === search;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// https://stackoverflow.com/a/950146
 | 
			
		||||
function import_js(url, cb) {
 | 
			
		||||
	var head = document.head || document.getElementsByTagName('head')[0];
 | 
			
		||||
	var script = document.createElement('script');
 | 
			
		||||
	script.type = 'text/javascript';
 | 
			
		||||
	script.src = url;
 | 
			
		||||
 | 
			
		||||
	script.onreadystatechange = cb;
 | 
			
		||||
	script.onload = cb;
 | 
			
		||||
 | 
			
		||||
	head.appendChild(script);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function o(id) {
 | 
			
		||||
	return document.getElementById(id);
 | 
			
		||||
}
 | 
			
		||||
window.onerror = vis_exh;
 | 
			
		||||
 | 
			
		||||
function dbg(msg) {
 | 
			
		||||
	o('path').innerHTML = msg;
 | 
			
		||||
	ebi('path').innerHTML = msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ev(e) {
 | 
			
		||||
@@ -78,40 +12,7 @@ function ev(e) {
 | 
			
		||||
	return 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].onclick = function () {
 | 
			
		||||
			sortTable(table, i);
 | 
			
		||||
		};
 | 
			
		||||
	}(i));
 | 
			
		||||
}
 | 
			
		||||
makeSortable(o('files'));
 | 
			
		||||
makeSortable(ebi('files'));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// extract songs + add play column
 | 
			
		||||
@@ -142,7 +43,7 @@ var mp = (function () {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (var a = 0, aa = tracks.length; a < aa; a++)
 | 
			
		||||
		o('trk' + a).onclick = ev_play;
 | 
			
		||||
		ebi('trk' + a).onclick = ev_play;
 | 
			
		||||
 | 
			
		||||
	ret.vol = localStorage.getItem('vol');
 | 
			
		||||
	if (ret.vol !== null)
 | 
			
		||||
@@ -199,7 +100,7 @@ var widget = (function () {
 | 
			
		||||
	ret.paused = function (paused) {
 | 
			
		||||
		if (was_paused != paused) {
 | 
			
		||||
			was_paused = paused;
 | 
			
		||||
			o('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
			
		||||
			ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	var click_handler = function (e) {
 | 
			
		||||
@@ -223,8 +124,8 @@ var widget = (function () {
 | 
			
		||||
// buffer/position bar
 | 
			
		||||
var pbar = (function () {
 | 
			
		||||
	var r = {};
 | 
			
		||||
	r.bcan = o('barbuf');
 | 
			
		||||
	r.pcan = o('barpos');
 | 
			
		||||
	r.bcan = ebi('barbuf');
 | 
			
		||||
	r.pcan = ebi('barpos');
 | 
			
		||||
	r.bctx = r.bcan.getContext('2d');
 | 
			
		||||
	r.pctx = r.pcan.getContext('2d');
 | 
			
		||||
 | 
			
		||||
@@ -289,7 +190,7 @@ var pbar = (function () {
 | 
			
		||||
// volume bar
 | 
			
		||||
var vbar = (function () {
 | 
			
		||||
	var r = {};
 | 
			
		||||
	r.can = o('pvol');
 | 
			
		||||
	r.can = ebi('pvol');
 | 
			
		||||
	r.ctx = r.can.getContext('2d');
 | 
			
		||||
 | 
			
		||||
	var bctx = r.ctx;
 | 
			
		||||
@@ -386,7 +287,7 @@ var vbar = (function () {
 | 
			
		||||
		else
 | 
			
		||||
			play(0);
 | 
			
		||||
	};
 | 
			
		||||
	o('bplay').onclick = function (e) {
 | 
			
		||||
	ebi('bplay').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		if (mp.au) {
 | 
			
		||||
			if (mp.au.paused)
 | 
			
		||||
@@ -397,15 +298,15 @@ var vbar = (function () {
 | 
			
		||||
		else
 | 
			
		||||
			play(0);
 | 
			
		||||
	};
 | 
			
		||||
	o('bprev').onclick = function (e) {
 | 
			
		||||
	ebi('bprev').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		bskip(-1);
 | 
			
		||||
	};
 | 
			
		||||
	o('bnext').onclick = function (e) {
 | 
			
		||||
	ebi('bnext').onclick = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		bskip(1);
 | 
			
		||||
	};
 | 
			
		||||
	o('barpos').onclick = function (e) {
 | 
			
		||||
	ebi('barpos').onclick = function (e) {
 | 
			
		||||
		if (!mp.au) {
 | 
			
		||||
			//dbg((new Date()).getTime());
 | 
			
		||||
			return play(0);
 | 
			
		||||
@@ -471,7 +372,7 @@ function ev_play(e) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function setclass(id, clas) {
 | 
			
		||||
	o(id).setAttribute('class', clas);
 | 
			
		||||
	ebi(id).setAttribute('class', clas);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -608,7 +509,7 @@ function show_modal(html) {
 | 
			
		||||
 | 
			
		||||
// hide fullscreen message
 | 
			
		||||
function unblocked() {
 | 
			
		||||
	var dom = o('blocked');
 | 
			
		||||
	var dom = ebi('blocked');
 | 
			
		||||
	if (dom)
 | 
			
		||||
		dom.parentNode.removeChild(dom);
 | 
			
		||||
}
 | 
			
		||||
@@ -620,8 +521,8 @@ function autoplay_blocked(tid) {
 | 
			
		||||
		'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
 | 
			
		||||
		'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
 | 
			
		||||
 | 
			
		||||
	var go = o('blk_go');
 | 
			
		||||
	var na = o('blk_na');
 | 
			
		||||
	var go = ebi('blk_go');
 | 
			
		||||
	var na = ebi('blk_na');
 | 
			
		||||
 | 
			
		||||
	var fn = mp.tracks[mp.au.tid].split(/\//).pop();
 | 
			
		||||
	fn = decodeURIComponent(fn.replace(/\+/g, ' '));
 | 
			
		||||
 
 | 
			
		||||
@@ -123,8 +123,11 @@ write markdown (most html is 🙆 too)
 | 
			
		||||
	
 | 
			
		||||
	<script>
 | 
			
		||||
 | 
			
		||||
var link_md_as_html = false;  // TODO (does nothing)
 | 
			
		||||
var last_modified = {{ lastmod }};
 | 
			
		||||
var md_opt = {
 | 
			
		||||
	link_md_as_html: false,
 | 
			
		||||
	allow_plugins: {{ md_plug }}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
    var btn = document.getElementById("lightswitch");
 | 
			
		||||
@@ -141,17 +144,11 @@ var last_modified = {{ lastmod }};
 | 
			
		||||
		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/util.js"></script>
 | 
			
		||||
	<script src="/.cpr/deps/marked.full.js"></script>
 | 
			
		||||
	<script src="/.cpr/md.js"></script>
 | 
			
		||||
	{%- if edit %}
 | 
			
		||||
		<script src="/.cpr/md2.js"></script>
 | 
			
		||||
	<script src="/.cpr/md2.js"></script>
 | 
			
		||||
	{%- endif %}
 | 
			
		||||
</body></html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
var dom_toc = document.getElementById('toc');
 | 
			
		||||
var dom_wrap = document.getElementById('mw');
 | 
			
		||||
var dom_hbar = document.getElementById('mh');
 | 
			
		||||
@@ -18,6 +20,10 @@ var dbg = function () { };
 | 
			
		||||
// dbg = console.log
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// plugins
 | 
			
		||||
var md_plug = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function hesc(txt) {
 | 
			
		||||
    return txt.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
 | 
			
		||||
}
 | 
			
		||||
@@ -30,7 +36,7 @@ function cls(dom, name, add) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function static(obj) {
 | 
			
		||||
function statify(obj) {
 | 
			
		||||
    return JSON.parse(JSON.stringify(obj));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -154,13 +160,110 @@ function copydom(src, dst, lv) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function md_plug_err(ex, js) {
 | 
			
		||||
    var errbox = document.getElementById('md_errbox');
 | 
			
		||||
    if (errbox)
 | 
			
		||||
        errbox.parentNode.removeChild(errbox);
 | 
			
		||||
 | 
			
		||||
    if (!ex)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    var msg = (ex + '').split('\n')[0];
 | 
			
		||||
    var ln = ex.lineNumber;
 | 
			
		||||
    var o = null;
 | 
			
		||||
    if (ln) {
 | 
			
		||||
        msg = "Line " + ln + ", " + msg;
 | 
			
		||||
        var lns = js.split('\n');
 | 
			
		||||
        if (ln < lns.length) {
 | 
			
		||||
            o = document.createElement('span');
 | 
			
		||||
            o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
 | 
			
		||||
            o.textContent = lns[ln - 1];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    errbox = document.createElement('div');
 | 
			
		||||
    errbox.setAttribute('id', 'md_errbox');
 | 
			
		||||
    errbox.style.cssText = 'position:absolute;top:0;left:0;padding:1em .5em;background:#2b2b2b;color:#fc5'
 | 
			
		||||
    errbox.textContent = msg;
 | 
			
		||||
    errbox.onclick = function () {
 | 
			
		||||
        alert('' + ex.stack);
 | 
			
		||||
    };
 | 
			
		||||
    if (o) {
 | 
			
		||||
        errbox.appendChild(o);
 | 
			
		||||
        errbox.style.padding = '.25em .5em';
 | 
			
		||||
    }
 | 
			
		||||
    dom_nav.appendChild(errbox);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        console.trace();
 | 
			
		||||
    }
 | 
			
		||||
    catch (ex2) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function load_plug(md_text, plug_type) {
 | 
			
		||||
    if (!md_opt.allow_plugins)
 | 
			
		||||
        return md_text;
 | 
			
		||||
 | 
			
		||||
    var find = '\n```copyparty_' + plug_type + '\n';
 | 
			
		||||
    var ofs = md_text.indexOf(find);
 | 
			
		||||
    if (ofs === -1)
 | 
			
		||||
        return md_text;
 | 
			
		||||
 | 
			
		||||
    var ofs2 = md_text.indexOf('\n```', ofs + 1);
 | 
			
		||||
    if (ofs2 == -1)
 | 
			
		||||
        return md_text;
 | 
			
		||||
 | 
			
		||||
    var js = md_text.slice(ofs + find.length, ofs2 + 1);
 | 
			
		||||
    var md = md_text.slice(0, ofs + 1) + md_text.slice(ofs2 + 4);
 | 
			
		||||
 | 
			
		||||
    var old_plug = md_plug[plug_type];
 | 
			
		||||
    if (!old_plug || old_plug[1] != js) {
 | 
			
		||||
        js = 'const x = { ' + js + ' }; x;';
 | 
			
		||||
        try {
 | 
			
		||||
            var x = eval(js);
 | 
			
		||||
        }
 | 
			
		||||
        catch (ex) {
 | 
			
		||||
            md_plug[plug_type] = null;
 | 
			
		||||
            md_plug_err(ex, js);
 | 
			
		||||
            return md;
 | 
			
		||||
        }
 | 
			
		||||
        if (x['ctor']) {
 | 
			
		||||
            x['ctor']();
 | 
			
		||||
            delete x['ctor'];
 | 
			
		||||
        }
 | 
			
		||||
        md_plug[plug_type] = [x, js];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return md;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function convert_markdown(md_text, dest_dom) {
 | 
			
		||||
    marked.setOptions({
 | 
			
		||||
    md_text = md_text.replace(/\r/g, '');
 | 
			
		||||
 | 
			
		||||
    md_plug_err(null);
 | 
			
		||||
    md_text = load_plug(md_text, 'pre');
 | 
			
		||||
    md_text = load_plug(md_text, 'post');
 | 
			
		||||
 | 
			
		||||
    var marked_opts = {
 | 
			
		||||
        //headerPrefix: 'h-',
 | 
			
		||||
        breaks: true,
 | 
			
		||||
        gfm: true
 | 
			
		||||
    });
 | 
			
		||||
    var md_html = marked(md_text);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    var ext = md_plug['pre'];
 | 
			
		||||
    if (ext)
 | 
			
		||||
        Object.assign(marked_opts, ext[0]);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        var md_html = marked(md_text, marked_opts);
 | 
			
		||||
    }
 | 
			
		||||
    catch (ex) {
 | 
			
		||||
        if (ext)
 | 
			
		||||
            md_plug_err(ex, ext[1]);
 | 
			
		||||
 | 
			
		||||
        throw ex;
 | 
			
		||||
    }
 | 
			
		||||
    var md_dom = new DOMParser().parseFromString(md_html, "text/html").body;
 | 
			
		||||
 | 
			
		||||
    var nodes = md_dom.getElementsByTagName('a');
 | 
			
		||||
@@ -209,7 +312,7 @@ function convert_markdown(md_text, dest_dom) {
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        var nline = parseInt(el.getAttribute('data-ln')) + 1;
 | 
			
		||||
        var lines = el.innerHTML.replace(/\r?\n<\/code>$/i, '</code>').split(/\r?\n/g);
 | 
			
		||||
        var lines = el.innerHTML.replace(/\n<\/code>$/i, '</code>').split(/\n/g);
 | 
			
		||||
        for (var b = 0; b < lines.length - 1; b++)
 | 
			
		||||
            lines[b] += '</code>\n<code data-ln="' + (nline + b) + '">';
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +345,24 @@ function convert_markdown(md_text, dest_dom) {
 | 
			
		||||
        el.innerHTML = '<a href="#' + id + '">' + el.innerHTML + '</a>';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ext = md_plug['post'];
 | 
			
		||||
    if (ext && ext[0].render)
 | 
			
		||||
        try {
 | 
			
		||||
            ext[0].render(md_dom);
 | 
			
		||||
        }
 | 
			
		||||
        catch (ex) {
 | 
			
		||||
            md_plug_err(ex, ext[1]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    copydom(md_dom, dest_dom, 0);
 | 
			
		||||
 | 
			
		||||
    if (ext && ext[0].render2)
 | 
			
		||||
        try {
 | 
			
		||||
            ext[0].render2(dest_dom);
 | 
			
		||||
        }
 | 
			
		||||
        catch (ex) {
 | 
			
		||||
            md_plug_err(ex, ext[1]);
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -281,7 +401,12 @@ function init_toc() {
 | 
			
		||||
 | 
			
		||||
            elm.childNodes[0].setAttribute('ctr', ctr.slice(0, lv).join('.'));
 | 
			
		||||
 | 
			
		||||
            html.push('<li>' + elm.innerHTML + '</li>');
 | 
			
		||||
            var elm2 = elm.cloneNode(true);
 | 
			
		||||
            elm2.childNodes[0].textContent = elm.textContent;
 | 
			
		||||
            while (elm2.childNodes.length > 1)
 | 
			
		||||
                elm2.removeChild(elm2.childNodes[1]);
 | 
			
		||||
 | 
			
		||||
            html.push('<li>' + elm2.innerHTML + '</li>');
 | 
			
		||||
 | 
			
		||||
            if (anchor != null)
 | 
			
		||||
                anchors.push(anchor);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// server state
 | 
			
		||||
var server_md = dom_src.value;
 | 
			
		||||
 | 
			
		||||
@@ -177,7 +180,7 @@ redraw = (function () {
 | 
			
		||||
        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)
 | 
			
		||||
            if (srcmap[a] == null || dstmap[a] == null)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (srcmap[a] > y) {
 | 
			
		||||
@@ -680,7 +683,8 @@ function fmt_table(e) {
 | 
			
		||||
    for (var col = 0; col < ncols; col++) {
 | 
			
		||||
        var max = 0;
 | 
			
		||||
        for (var row = 0; row < tab.length; row++)
 | 
			
		||||
            max = Math.max(max, tab[row][col].length);
 | 
			
		||||
            if (row != 1)
 | 
			
		||||
                max = Math.max(max, tab[row][col].length);
 | 
			
		||||
 | 
			
		||||
        var s = '';
 | 
			
		||||
        for (var n = 0; n < max; n++)
 | 
			
		||||
@@ -999,9 +1003,9 @@ action_stack = (function () {
 | 
			
		||||
        ref = newtxt;
 | 
			
		||||
        dbg('undos(%d) redos(%d)', hist.un.length, hist.re.length);
 | 
			
		||||
        if (hist.un.length > 0)
 | 
			
		||||
            dbg(static(hist.un.slice(-1)[0]));
 | 
			
		||||
            dbg(statify(hist.un.slice(-1)[0]));
 | 
			
		||||
        if (hist.re.length > 0)
 | 
			
		||||
            dbg(static(hist.re.slice(-1)[0]));
 | 
			
		||||
            dbg(statify(hist.re.slice(-1)[0]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,11 @@
 | 
			
		||||
	</div>
 | 
			
		||||
	<script>
 | 
			
		||||
 | 
			
		||||
var link_md_as_html = false;  // TODO (does nothing)
 | 
			
		||||
var last_modified = {{ lastmod }};
 | 
			
		||||
var md_opt = {
 | 
			
		||||
	link_md_as_html: false,
 | 
			
		||||
	allow_plugins: {{ md_plug }}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
var lightswitch = (function () {
 | 
			
		||||
	var fun = function () {
 | 
			
		||||
@@ -39,6 +42,7 @@ var lightswitch = (function () {
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
	</script>
 | 
			
		||||
    <script src="/.cpr/util.js"></script>
 | 
			
		||||
	<script src="/.cpr/deps/easymde.js"></script>
 | 
			
		||||
	<script src="/.cpr/mde.js"></script>
 | 
			
		||||
</body></html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
var dom_wrap = document.getElementById('mw');
 | 
			
		||||
var dom_nav = document.getElementById('mn');
 | 
			
		||||
var dom_doc = document.getElementById('m');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,61 +1,6 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// error handler for mobile devices
 | 
			
		||||
function hcroak(msg) {
 | 
			
		||||
    document.body.innerHTML = msg;
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    throw 'fatal_err';
 | 
			
		||||
}
 | 
			
		||||
function croak(msg) {
 | 
			
		||||
    document.body.textContent = msg;
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    throw msg;
 | 
			
		||||
}
 | 
			
		||||
function esc(txt) {
 | 
			
		||||
    return txt.replace(/[&"<>]/g, function (c) {
 | 
			
		||||
        return {
 | 
			
		||||
            '&': '&',
 | 
			
		||||
            '"': '"',
 | 
			
		||||
            '<': '<',
 | 
			
		||||
            '>': '>'
 | 
			
		||||
        }[c];
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
			
		||||
        esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
			
		||||
 | 
			
		||||
    if (error) {
 | 
			
		||||
        var find = ['desc', 'stack', 'trace'];
 | 
			
		||||
        for (var a = 0; a < find.length; a++)
 | 
			
		||||
            if (String(error[find[a]]) !== 'undefined')
 | 
			
		||||
                html.push('<h2>' + find[a] + '</h2>' +
 | 
			
		||||
                    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'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// https://stackoverflow.com/a/950146
 | 
			
		||||
function import_js(url, cb) {
 | 
			
		||||
    var head = document.head || document.getElementsByTagName('head')[0];
 | 
			
		||||
    var script = document.createElement('script');
 | 
			
		||||
    script.type = 'text/javascript';
 | 
			
		||||
    script.src = url;
 | 
			
		||||
 | 
			
		||||
    script.onreadystatechange = cb;
 | 
			
		||||
    script.onload = cb;
 | 
			
		||||
 | 
			
		||||
    head.appendChild(script);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function o(id) {
 | 
			
		||||
    return document.getElementById(id);
 | 
			
		||||
}
 | 
			
		||||
window.onerror = vis_exh;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
@@ -150,21 +95,21 @@ function up2k_init(have_crypto) {
 | 
			
		||||
 | 
			
		||||
    // show modal message
 | 
			
		||||
    function showmodal(msg) {
 | 
			
		||||
        o('u2notbtn').innerHTML = msg;
 | 
			
		||||
        o('u2btn').style.display = 'none';
 | 
			
		||||
        o('u2notbtn').style.display = 'block';
 | 
			
		||||
        o('u2conf').style.opacity = '0.5';
 | 
			
		||||
        ebi('u2notbtn').innerHTML = msg;
 | 
			
		||||
        ebi('u2btn').style.display = 'none';
 | 
			
		||||
        ebi('u2notbtn').style.display = 'block';
 | 
			
		||||
        ebi('u2conf').style.opacity = '0.5';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // hide modal message
 | 
			
		||||
    function unmodal() {
 | 
			
		||||
        o('u2notbtn').style.display = 'none';
 | 
			
		||||
        o('u2btn').style.display = 'block';
 | 
			
		||||
        o('u2conf').style.opacity = '1';
 | 
			
		||||
        o('u2notbtn').innerHTML = '';
 | 
			
		||||
        ebi('u2notbtn').style.display = 'none';
 | 
			
		||||
        ebi('u2btn').style.display = 'block';
 | 
			
		||||
        ebi('u2conf').style.opacity = '1';
 | 
			
		||||
        ebi('u2notbtn').innerHTML = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var post_url = o('op_bup').getElementsByTagName('form')[0].getAttribute('action');
 | 
			
		||||
    var post_url = ebi('op_bup').getElementsByTagName('form')[0].getAttribute('action');
 | 
			
		||||
    if (post_url && post_url.charAt(post_url.length - 1) !== '/')
 | 
			
		||||
        post_url += '/';
 | 
			
		||||
 | 
			
		||||
@@ -181,25 +126,25 @@ function up2k_init(have_crypto) {
 | 
			
		||||
            import_js('/.cpr/deps/sha512.js', unmodal);
 | 
			
		||||
 | 
			
		||||
            if (is_https)
 | 
			
		||||
                o('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
 | 
			
		||||
                ebi('u2foot').innerHTML = shame + ' so <em>this</em> uploader will do like 500kB/s at best';
 | 
			
		||||
            else
 | 
			
		||||
                o('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance';
 | 
			
		||||
                ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance';
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // show uploader if the user only has write-access
 | 
			
		||||
    if (!o('files'))
 | 
			
		||||
    if (!ebi('files'))
 | 
			
		||||
        goto('up2k');
 | 
			
		||||
 | 
			
		||||
    // shows or clears an error message in the basic uploader ui
 | 
			
		||||
    function setmsg(msg) {
 | 
			
		||||
        if (msg !== undefined) {
 | 
			
		||||
            o('u2err').setAttribute('class', 'err');
 | 
			
		||||
            o('u2err').innerHTML = msg;
 | 
			
		||||
            ebi('u2err').setAttribute('class', 'err');
 | 
			
		||||
            ebi('u2err').innerHTML = msg;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            o('u2err').setAttribute('class', '');
 | 
			
		||||
            o('u2err').innerHTML = '';
 | 
			
		||||
            ebi('u2err').setAttribute('class', '');
 | 
			
		||||
            ebi('u2err').innerHTML = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -210,7 +155,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // handle user intent to use the basic uploader instead
 | 
			
		||||
    o('u2nope').onclick = function (e) {
 | 
			
		||||
    ebi('u2nope').onclick = function (e) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        setmsg('');
 | 
			
		||||
        goto('bup');
 | 
			
		||||
@@ -229,9 +174,9 @@ function up2k_init(have_crypto) {
 | 
			
		||||
    function cfg_get(name) {
 | 
			
		||||
        var val = localStorage.getItem(name);
 | 
			
		||||
        if (val === null)
 | 
			
		||||
            return parseInt(o(name).value);
 | 
			
		||||
            return parseInt(ebi(name).value);
 | 
			
		||||
 | 
			
		||||
        o(name).value = val;
 | 
			
		||||
        ebi(name).value = val;
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +187,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
        else
 | 
			
		||||
            val = (val == '1');
 | 
			
		||||
 | 
			
		||||
        o(name).checked = val;
 | 
			
		||||
        ebi(name).checked = val;
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +195,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
        localStorage.setItem(
 | 
			
		||||
            name, val ? '1' : '0');
 | 
			
		||||
 | 
			
		||||
        o(name).checked = val;
 | 
			
		||||
        ebi(name).checked = val;
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -284,9 +229,9 @@ function up2k_init(have_crypto) {
 | 
			
		||||
        return un2k("this is the basic uploader; up2k needs at least<br />chrome 21 // firefox 13 // edge 12 // opera 12 // safari 5.1");
 | 
			
		||||
 | 
			
		||||
    function nav() {
 | 
			
		||||
        o('file' + fdom_ctr).click();
 | 
			
		||||
        ebi('file' + fdom_ctr).click();
 | 
			
		||||
    }
 | 
			
		||||
    o('u2btn').addEventListener('click', nav, false);
 | 
			
		||||
    ebi('u2btn').addEventListener('click', nav, false);
 | 
			
		||||
 | 
			
		||||
    function ondrag(ev) {
 | 
			
		||||
        ev.stopPropagation();
 | 
			
		||||
@@ -294,8 +239,8 @@ function up2k_init(have_crypto) {
 | 
			
		||||
        ev.dataTransfer.dropEffect = 'copy';
 | 
			
		||||
        ev.dataTransfer.effectAllowed = 'copy';
 | 
			
		||||
    }
 | 
			
		||||
    o('u2btn').addEventListener('dragover', ondrag, false);
 | 
			
		||||
    o('u2btn').addEventListener('dragenter', ondrag, false);
 | 
			
		||||
    ebi('u2btn').addEventListener('dragover', ondrag, false);
 | 
			
		||||
    ebi('u2btn').addEventListener('dragenter', ondrag, false);
 | 
			
		||||
 | 
			
		||||
    function gotfile(ev) {
 | 
			
		||||
        ev.stopPropagation();
 | 
			
		||||
@@ -357,7 +302,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
            var tr = document.createElement('tr');
 | 
			
		||||
            tr.innerHTML = '<td id="f{0}n"></td><td id="f{0}t">hashing</td><td id="f{0}p" class="prog"></td>'.format(st.files.length);
 | 
			
		||||
            tr.getElementsByTagName('td')[0].textContent = entry.name;
 | 
			
		||||
            o('u2tab').appendChild(tr);
 | 
			
		||||
            ebi('u2tab').appendChild(tr);
 | 
			
		||||
 | 
			
		||||
            st.files.push(entry);
 | 
			
		||||
            st.todo.hash.push(entry);
 | 
			
		||||
@@ -374,14 +319,14 @@ function up2k_init(have_crypto) {
 | 
			
		||||
            alert(msg);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    o('u2btn').addEventListener('drop', gotfile, false);
 | 
			
		||||
    ebi('u2btn').addEventListener('drop', gotfile, false);
 | 
			
		||||
 | 
			
		||||
    function more_one_file() {
 | 
			
		||||
        fdom_ctr++;
 | 
			
		||||
        var elm = document.createElement('div')
 | 
			
		||||
        elm.innerHTML = '<input id="file{0}" type="file" name="file{0}[]" multiple="multiple" />'.format(fdom_ctr);
 | 
			
		||||
        o('u2form').appendChild(elm);
 | 
			
		||||
        o('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
			
		||||
        ebi('u2form').appendChild(elm);
 | 
			
		||||
        ebi('file' + fdom_ctr).addEventListener('change', gotfile, false);
 | 
			
		||||
    }
 | 
			
		||||
    more_one_file();
 | 
			
		||||
 | 
			
		||||
@@ -602,7 +547,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
            pb_html += '<div id="f{0}p{1}" style="width:{2}%"><div></div></div>'.format(
 | 
			
		||||
                t.n, a, pb_perc);
 | 
			
		||||
 | 
			
		||||
        o('f{0}p'.format(t.n)).innerHTML = pb_html;
 | 
			
		||||
        ebi('f{0}p'.format(t.n)).innerHTML = pb_html;
 | 
			
		||||
 | 
			
		||||
        var reader = new FileReader();
 | 
			
		||||
 | 
			
		||||
@@ -677,7 +622,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
                alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            o('f{0}t'.format(t.n)).innerHTML = 'connecting';
 | 
			
		||||
            ebi('f{0}t'.format(t.n)).innerHTML = 'connecting';
 | 
			
		||||
            st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
 | 
			
		||||
            st.todo.handshake.push(t);
 | 
			
		||||
        };
 | 
			
		||||
@@ -706,7 +651,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
                if (response.name !== t.name) {
 | 
			
		||||
                    // file exists; server renamed us
 | 
			
		||||
                    t.name = response.name;
 | 
			
		||||
                    o('f{0}n'.format(t.n)).textContent = t.name;
 | 
			
		||||
                    ebi('f{0}n'.format(t.n)).textContent = t.name;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                t.postlist = [];
 | 
			
		||||
@@ -736,13 +681,13 @@ function up2k_init(have_crypto) {
 | 
			
		||||
                    msg = 'uploading';
 | 
			
		||||
                    done = false;
 | 
			
		||||
                }
 | 
			
		||||
                o('f{0}t'.format(t.n)).innerHTML = msg;
 | 
			
		||||
                ebi('f{0}t'.format(t.n)).innerHTML = msg;
 | 
			
		||||
                st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
 | 
			
		||||
 | 
			
		||||
                if (done) {
 | 
			
		||||
                    var spd1 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
 | 
			
		||||
                    var spd2 = (t.size / ((t.t3 - t.t2) / 1000.)) / (1024 * 1024.);
 | 
			
		||||
                    o('f{0}p'.format(t.n)).innerHTML = 'hash {0}, up {1} MB/s'.format(
 | 
			
		||||
                    ebi('f{0}p'.format(t.n)).innerHTML = 'hash {0}, up {1} MB/s'.format(
 | 
			
		||||
                        spd1.toFixed(2), spd2.toFixed(2));
 | 
			
		||||
                }
 | 
			
		||||
                tasker();
 | 
			
		||||
@@ -803,7 +748,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
                    t.postlist.splice(t.postlist.indexOf(npart), 1);
 | 
			
		||||
                    if (t.postlist.length == 0) {
 | 
			
		||||
                        t.t3 = new Date().getTime();
 | 
			
		||||
                        o('f{0}t'.format(t.n)).innerHTML = 'verifying';
 | 
			
		||||
                        ebi('f{0}t'.format(t.n)).innerHTML = 'verifying';
 | 
			
		||||
                        st.todo.handshake.push(t);
 | 
			
		||||
                    }
 | 
			
		||||
                    tasker();
 | 
			
		||||
@@ -834,7 +779,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
    //
 | 
			
		||||
 | 
			
		||||
    function prog(nfile, nchunk, color, percent) {
 | 
			
		||||
        var n1 = o('f{0}p{1}'.format(nfile, nchunk));
 | 
			
		||||
        var n1 = ebi('f{0}p{1}'.format(nfile, nchunk));
 | 
			
		||||
        var n2 = n1.getElementsByTagName('div')[0];
 | 
			
		||||
        if (percent === undefined) {
 | 
			
		||||
            n1.style.background = color;
 | 
			
		||||
@@ -857,7 +802,7 @@ function up2k_init(have_crypto) {
 | 
			
		||||
            dir.preventDefault();
 | 
			
		||||
        } catch (ex) { }
 | 
			
		||||
 | 
			
		||||
        var obj = o('nthread');
 | 
			
		||||
        var obj = ebi('nthread');
 | 
			
		||||
        if (dir.target) {
 | 
			
		||||
            obj.style.background = '#922';
 | 
			
		||||
            var v = Math.floor(parseInt(obj.value));
 | 
			
		||||
@@ -892,19 +837,19 @@ function up2k_init(have_crypto) {
 | 
			
		||||
        this.click();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    o('nthread_add').onclick = function (ev) {
 | 
			
		||||
    ebi('nthread_add').onclick = function (ev) {
 | 
			
		||||
        ev.preventDefault();
 | 
			
		||||
        bumpthread(1);
 | 
			
		||||
    };
 | 
			
		||||
    o('nthread_sub').onclick = function (ev) {
 | 
			
		||||
    ebi('nthread_sub').onclick = function (ev) {
 | 
			
		||||
        ev.preventDefault();
 | 
			
		||||
        bumpthread(-1);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    o('nthread').addEventListener('input', bumpthread, false);
 | 
			
		||||
    o('multitask').addEventListener('click', tgl_multitask, false);
 | 
			
		||||
    ebi('nthread').addEventListener('input', bumpthread, false);
 | 
			
		||||
    ebi('multitask').addEventListener('click', tgl_multitask, false);
 | 
			
		||||
 | 
			
		||||
    var nodes = o('u2conf').getElementsByTagName('a');
 | 
			
		||||
    var nodes = ebi('u2conf').getElementsByTagName('a');
 | 
			
		||||
    for (var a = nodes.length - 1; a >= 0; a--)
 | 
			
		||||
        nodes[a].addEventListener('touchend', nop, false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								copyparty/web/util.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								copyparty/web/util.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
// error handler for mobile devices
 | 
			
		||||
function hcroak(msg) {
 | 
			
		||||
    document.body.innerHTML = msg;
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    throw 'fatal_err';
 | 
			
		||||
}
 | 
			
		||||
function croak(msg) {
 | 
			
		||||
    document.body.textContent = msg;
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    throw msg;
 | 
			
		||||
}
 | 
			
		||||
function esc(txt) {
 | 
			
		||||
    return txt.replace(/[&"<>]/g, function (c) {
 | 
			
		||||
        return {
 | 
			
		||||
            '&': '&',
 | 
			
		||||
            '"': '"',
 | 
			
		||||
            '<': '<',
 | 
			
		||||
            '>': '>'
 | 
			
		||||
        }[c];
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
			
		||||
        esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
 | 
			
		||||
 | 
			
		||||
    if (error) {
 | 
			
		||||
        var find = ['desc', 'stack', 'trace'];
 | 
			
		||||
        for (var a = 0; a < find.length; a++)
 | 
			
		||||
            if (String(error[find[a]]) !== 'undefined')
 | 
			
		||||
                html.push('<h2>' + find[a] + '</h2>' +
 | 
			
		||||
                    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'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function ebi(id) {
 | 
			
		||||
    return document.getElementById(id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
 | 
			
		||||
if (!String.prototype.endsWith) {
 | 
			
		||||
    String.prototype.endsWith = function (search, this_len) {
 | 
			
		||||
        if (this_len === undefined || this_len > this.length) {
 | 
			
		||||
            this_len = this.length;
 | 
			
		||||
        }
 | 
			
		||||
        return this.substring(this_len - search.length, this_len) === search;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
if (!String.startsWith) {
 | 
			
		||||
    String.prototype.startsWith = function (s, i) {
 | 
			
		||||
        i = i > 0 ? i | 0 : 0;
 | 
			
		||||
        return this.substring(i, i + s.length) === s;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// https://stackoverflow.com/a/950146
 | 
			
		||||
function import_js(url, cb) {
 | 
			
		||||
    var head = document.head || document.getElementsByTagName('head')[0];
 | 
			
		||||
    var script = document.createElement('script');
 | 
			
		||||
    script.type = 'text/javascript';
 | 
			
		||||
    script.src = url;
 | 
			
		||||
 | 
			
		||||
    script.onreadystatechange = cb;
 | 
			
		||||
    script.onload = cb;
 | 
			
		||||
 | 
			
		||||
    head.appendChild(script);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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].onclick = function () {
 | 
			
		||||
            sortTable(table, i);
 | 
			
		||||
        };
 | 
			
		||||
    }(i));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								scripts/copyparty-repack.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										103
									
								
								scripts/copyparty-repack.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
# -- download latest copyparty (source.tgz and sfx),
 | 
			
		||||
# -- build minimal sfx versions,
 | 
			
		||||
# -- create a .tar.gz bundle
 | 
			
		||||
#
 | 
			
		||||
# convenient for deploying updates to inconvenient locations
 | 
			
		||||
#  (and those are usually linux so bash is good inaff)
 | 
			
		||||
#   (but that said this even has macos support)
 | 
			
		||||
#
 | 
			
		||||
# bundle will look like:
 | 
			
		||||
# -rwxr-xr-x  0 ed ed  183808 Nov 19 00:43 copyparty
 | 
			
		||||
# -rw-r--r--  0 ed ed  491318 Nov 19 00:40 copyparty-extras/copyparty-0.5.4.tar.gz
 | 
			
		||||
# -rwxr-xr-x  0 ed ed   30254 Nov 17 23:58 copyparty-extras/copyparty-fuse.py
 | 
			
		||||
# -rwxr-xr-x  0 ed ed  481403 Nov 19 00:40 copyparty-extras/sfx-full/copyparty-sfx.sh
 | 
			
		||||
# -rwxr-xr-x  0 ed ed  506043 Nov 19 00:40 copyparty-extras/sfx-full/copyparty-sfx.py
 | 
			
		||||
# -rwxr-xr-x  0 ed ed  167699 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.sh
 | 
			
		||||
# -rwxr-xr-x  0 ed ed  183808 Nov 19 00:43 copyparty-extras/sfx-lite/copyparty-sfx.py
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
command -v gtar && tar=gtar || tar=tar
 | 
			
		||||
td="$(mktemp -d)"
 | 
			
		||||
od="$(pwd)"
 | 
			
		||||
cd "$td"
 | 
			
		||||
pwd
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# debug: if cache exists, use that instead of bothering github
 | 
			
		||||
cache="$od/.copyparty-repack.cache"
 | 
			
		||||
[ -e "$cache" ] &&
 | 
			
		||||
	$tar -xvf "$cache" ||
 | 
			
		||||
{
 | 
			
		||||
	# get download links from github
 | 
			
		||||
	curl https://api.github.com/repos/9001/copyparty/releases/latest |
 | 
			
		||||
	(
 | 
			
		||||
		# prefer jq if available
 | 
			
		||||
		jq -r '.assets[]|select(.name|test("-sfx|tar.gz")).browser_download_url' ||
 | 
			
		||||
 | 
			
		||||
		# fallback to awk (sorry)
 | 
			
		||||
		awk -F\" '/"browser_download_url".*(\.tar\.gz|-sfx\.)/ {print$4}'
 | 
			
		||||
	) |
 | 
			
		||||
	tee /dev/stderr |
 | 
			
		||||
	tr -d '\r' | tr '\n' '\0' | xargs -0 curl -L --remote-name-all
 | 
			
		||||
 | 
			
		||||
	# debug: create cache
 | 
			
		||||
	#$tar -czvf "$cache" *
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# move src into copyparty-extras/,
 | 
			
		||||
# move sfx into copyparty-extras/sfx-full/
 | 
			
		||||
mkdir -p copyparty-extras/sfx-{full,lite}
 | 
			
		||||
mv copyparty-sfx.* copyparty-extras/sfx-full/
 | 
			
		||||
mv copyparty-*.tar.gz copyparty-extras/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# unpack the source code
 | 
			
		||||
( cd copyparty-extras/
 | 
			
		||||
$tar -xvf *.tar.gz
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# fix permissions
 | 
			
		||||
chmod 755 \
 | 
			
		||||
  copyparty-extras/sfx-full/* \
 | 
			
		||||
  copyparty-extras/copyparty-*/{scripts,bin}/*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# extract and repack the sfx with less features enabled
 | 
			
		||||
( cd copyparty-extras/sfx-full/
 | 
			
		||||
./copyparty-sfx.py -h
 | 
			
		||||
cd ../copyparty-*/
 | 
			
		||||
./scripts/make-sfx.sh re no-ogv no-cm
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# put new sfx into copyparty-extras/sfx-lite/,
 | 
			
		||||
# fuse client into copyparty-extras/,
 | 
			
		||||
# copy lite-sfx.py to ./copyparty,
 | 
			
		||||
# delete extracted source code
 | 
			
		||||
( cd copyparty-extras/
 | 
			
		||||
mv copyparty-*/dist/* sfx-lite/
 | 
			
		||||
mv copyparty-*/bin/copyparty-fuse.py .
 | 
			
		||||
cp -pv sfx-lite/copyparty-sfx.py ../copyparty
 | 
			
		||||
rm -rf copyparty-{0..9}*.*.*{0..9}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 # and include the repacker itself too
 | 
			
		||||
cp -pv "$od/$0" copyparty-extras/ 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# create the bundle
 | 
			
		||||
fn=copyparty-$(date +%Y-%m%d-%H%M%S).tgz
 | 
			
		||||
$tar -czvf "$od/$fn" *
 | 
			
		||||
cd "$od"
 | 
			
		||||
rm -rf "$td"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
echo
 | 
			
		||||
echo "done, here's your bundle:"
 | 
			
		||||
ls -al "$fn"
 | 
			
		||||
@@ -94,8 +94,39 @@ cd sfx
 | 
			
		||||
	rm -f ../tar
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ver="$(awk '/^VERSION *= \(/ {
 | 
			
		||||
	gsub(/[^0-9,]/,""); gsub(/,/,"."); print; exit}' < ../copyparty/__version__.py)"
 | 
			
		||||
ver=
 | 
			
		||||
command -v git >/dev/null && {
 | 
			
		||||
	git_ver="$(git describe --tags)";  # v0.5.5-2-gb164aa0
 | 
			
		||||
	ver="$(printf '%s\n' "$git_ver" | sed -r 's/^v//; s/-g?/./g')";
 | 
			
		||||
	t_ver=
 | 
			
		||||
 | 
			
		||||
	printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+$' && {
 | 
			
		||||
		# short format (exact version number)
 | 
			
		||||
		t_ver="$(printf '%s\n' "$ver" | sed -r 's/\./, /g')";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf '%s\n' "$git_ver" | grep -qE '^v[0-9\.]+-[0-9]+-g[0-9a-f]+$' && {
 | 
			
		||||
		# long format (unreleased commit)
 | 
			
		||||
		t_ver="$(printf '%s\n' "$ver" | sed -r 's/\./, /g; s/(.*) (.*)/\1 "\2"/')"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[ -z "$t_ver" ] && {
 | 
			
		||||
		printf 'unexpected git version format: [%s]\n' "$git_ver"
 | 
			
		||||
		exit 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dt="$(git log -1 --format=%cd --date=format:'%Y, %m, %d')"
 | 
			
		||||
	printf 'git %3s: \033[36m%s\033[0m\n' ver "$ver" dt "$dt"
 | 
			
		||||
	sed -ri '
 | 
			
		||||
		s/^(VERSION =)(.*)/#\1\2\n\1 ('"$t_ver"')/;
 | 
			
		||||
		s/^(S_VERSION =)(.*)/#\1\2\n\1 "'"$ver"'"/;
 | 
			
		||||
		s/^(BUILD_DT =)(.*)/#\1\2\n\1 ('"$dt"')/;
 | 
			
		||||
	' copyparty/__version__.py
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[ -z "$ver" ] && 
 | 
			
		||||
	ver="$(awk '/^VERSION *= \(/ {
 | 
			
		||||
		gsub(/[^0-9,]/,""); gsub(/,/,"."); print; exit}' < copyparty/__version__.py)"
 | 
			
		||||
 | 
			
		||||
ts=$(date -u +%s)
 | 
			
		||||
hts=$(date -u +%Y-%m%d-%H%M%S) # --date=@$ts (thx osx)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										141
									
								
								srv/extend.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								srv/extend.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
# hi
 | 
			
		||||
this showcases my worst idea yet; *extending markdown with inline javascript*
 | 
			
		||||
 | 
			
		||||
due to obvious reasons it's disabled by default, and can be enabled with `-emp`
 | 
			
		||||
 | 
			
		||||
the examples are by no means correct, they're as much of a joke as this feature itself
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### sub-header
 | 
			
		||||
nothing special about this one
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## except/
 | 
			
		||||
this one becomes a hyperlink to ./except/ thanks to
 | 
			
		||||
* the `copyparty_pre` plugin at the end of this file
 | 
			
		||||
* which is invoked as a markdown filter every time the document is modified
 | 
			
		||||
* which looks for headers ending with a `/` and erwrites all headers below that
 | 
			
		||||
 | 
			
		||||
it is a passthrough to the markdown extension api, see https://marked.js.org/using_pro
 | 
			
		||||
 | 
			
		||||
in addition to the markdown extension functions, `ctor` will be called on document init
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### these/
 | 
			
		||||
and this one becomes ./except/these/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### ones.md
 | 
			
		||||
finally ./except/these/ones.md
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### also-this.md
 | 
			
		||||
whic hshoud be ./except/also-this.md
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ok
 | 
			
		||||
now for another extension type, `copyparty_post` which is called to manipulate the generated dom instead
 | 
			
		||||
 | 
			
		||||
`copyparty_post` can have the following functions, all optional
 | 
			
		||||
* `ctor` is called on document init
 | 
			
		||||
* `render` is called when the dom is done but still in-memory
 | 
			
		||||
* `render2` is called with the live browser dom as-displayed
 | 
			
		||||
 | 
			
		||||
## post example
 | 
			
		||||
 | 
			
		||||
the values in the `ex:` columns are linkified to `example.com/$value`
 | 
			
		||||
 | 
			
		||||
| ex:foo       | bar      | ex:baz |
 | 
			
		||||
| ------------ | -------- | ------ |
 | 
			
		||||
| asdf         | nice     | fgsfds |
 | 
			
		||||
| more one row | hi hello | aaa    |
 | 
			
		||||
 | 
			
		||||
and the table can be sorted by clicking the headers
 | 
			
		||||
 | 
			
		||||
the difference is that with `copyparty_pre` you'll probably break various copyparty features but if you use `copyparty_post` then future copyparty versions will probably break you
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# heres the plugins
 | 
			
		||||
if there is anything below ths line in the preview then the plugin feature is disabled (good)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```copyparty_pre
 | 
			
		||||
ctor() {
 | 
			
		||||
    md_plug['h'] = {
 | 
			
		||||
        on: false,
 | 
			
		||||
        lv: -1,
 | 
			
		||||
        path: []
 | 
			
		||||
    }
 | 
			
		||||
},
 | 
			
		||||
walkTokens(token) {
 | 
			
		||||
    if (token.type == 'heading') {
 | 
			
		||||
        var h = md_plug['h'],
 | 
			
		||||
            is_dir = token.text.endsWith('/');
 | 
			
		||||
        
 | 
			
		||||
        if (h.lv >= token.depth) {
 | 
			
		||||
            h.on = false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!h.on && is_dir) {
 | 
			
		||||
            h.on = true;
 | 
			
		||||
            h.lv = token.depth;
 | 
			
		||||
            h.path = [token.text];
 | 
			
		||||
        }
 | 
			
		||||
        else if (h.on && h.lv < token.depth) {
 | 
			
		||||
            h.path = h.path.slice(0, token.depth - h.lv);
 | 
			
		||||
            h.path.push(token.text);
 | 
			
		||||
        }
 | 
			
		||||
        if (!h.on)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        var path = h.path.join('');
 | 
			
		||||
        var emoji = is_dir ? '📂' : '📜';
 | 
			
		||||
        token.tokens[0].text = '<a href="' + path + '">' + emoji + ' ' + path + '</a>';
 | 
			
		||||
    }
 | 
			
		||||
    if (token.type == 'paragraph') {
 | 
			
		||||
        //console.log(JSON.parse(JSON.stringify(token.tokens)));
 | 
			
		||||
        for (var a = 0; a < token.tokens.length; a++) {
 | 
			
		||||
            var t = token.tokens[a];
 | 
			
		||||
            if (t.type == 'text' || t.type == 'strong' || t.type == 'em') {
 | 
			
		||||
                var ret = '', text = t.text;
 | 
			
		||||
                for (var b = 0; b < text.length; b++)
 | 
			
		||||
                    ret += (Math.random() > 0.5) ? text[b] : text[b].toUpperCase();
 | 
			
		||||
                
 | 
			
		||||
                t.text = ret;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```copyparty_post
 | 
			
		||||
render(dom) {
 | 
			
		||||
    var ths = dom.querySelectorAll('th');
 | 
			
		||||
    for (var a = 0; a < ths.length; a++) {
 | 
			
		||||
        var th = ths[a];
 | 
			
		||||
        if (th.textContent.indexOf('ex:') === 0) {
 | 
			
		||||
            th.textContent = th.textContent.slice(3);
 | 
			
		||||
            var nrow = 0;
 | 
			
		||||
            while ((th = th.previousSibling) != null)
 | 
			
		||||
                nrow++;
 | 
			
		||||
            
 | 
			
		||||
            var trs = ths[a].parentNode.parentNode.parentNode.querySelectorAll('tr');
 | 
			
		||||
            for (var b = 1; b < trs.length; b++) {
 | 
			
		||||
                var td = trs[b].childNodes[nrow];
 | 
			
		||||
                td.innerHTML = '<a href="//example.com/' + td.innerHTML + '">' + td.innerHTML + '</a>';
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
},
 | 
			
		||||
render2(dom) {
 | 
			
		||||
    window.makeSortable(dom.getElementsByTagName('table')[0]);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
@@ -3,6 +3,7 @@
 | 
			
		||||
from __future__ import print_function, unicode_literals
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import json
 | 
			
		||||
import shutil
 | 
			
		||||
import unittest
 | 
			
		||||
@@ -59,8 +60,15 @@ class TestVFS(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
        if os.path.exists("/Volumes"):
 | 
			
		||||
            devname, _ = self.chkcmd("hdiutil", "attach", "-nomount", "ram://8192")
 | 
			
		||||
            _, _ = self.chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
			
		||||
            return "/Volumes/cptd"
 | 
			
		||||
            for _ in range(10):
 | 
			
		||||
                try:
 | 
			
		||||
                    _, _ = self.chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
 | 
			
		||||
                    return "/Volumes/cptd"
 | 
			
		||||
                except:
 | 
			
		||||
                    print('lol macos')
 | 
			
		||||
                    time.sleep(0.25)
 | 
			
		||||
            
 | 
			
		||||
            raise Exception("ramdisk creation failed")
 | 
			
		||||
 | 
			
		||||
        raise Exception("TODO support windows")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user