add textfile viewer

This commit is contained in:
ed 2021-11-04 01:40:03 +01:00
parent 49368a10ba
commit ba36f33bd8
7 changed files with 343 additions and 50 deletions

View file

@ -447,6 +447,7 @@ def run_argparse(argv, formatter):
ap2 = ap.add_argument_group('ui options')
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
ap2.add_argument("--textfiles", metavar="CSV", type=u, default="txt,nfo,diz,cue,readme", help="file extensions to present as plaintext")
ap2 = ap.add_argument_group('debug options')
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")

View file

@ -2268,6 +2268,19 @@ class HttpCli(object):
ls_ret["taglist"] = taglist
return self.tx_ls(ls_ret)
doc = self.uparam.get("doc") if self.can_read else None
if doc:
doc = unquotep(doc.replace("+", " "))
j2a["docname"] = doc
if next((x for x in files if x["name"] == doc), None):
with open(os.path.join(abspath, doc), "rb") as f:
doc = f.read().decode("utf-8", "replace")
else:
self.log("doc 404: [{}]".format(doc), c=6)
doc = "( textfile not found )"
j2a["doc"] = doc
for d in dirs:
d["name"] += "/"
@ -2276,6 +2289,7 @@ class HttpCli(object):
j2a["files"] = dirs + files
j2a["logues"] = logues
j2a["taglist"] = taglist
j2a["txt_ext"] = self.args.textfiles.replace(',', ' ')
if "mth" in vn.flags:
j2a["def_hcols"] = vn.flags["mth"].split(",")

View file

@ -23,7 +23,7 @@ html, body {
margin: 0;
padding: 0;
}
pre, code, tt {
pre, code, tt, #doc, #doc code {
font-family: 'scp', monospace, monospace;
}
#path,
@ -31,9 +31,8 @@ pre, code, tt {
font-size: 1em;
}
#path {
color: #aca;
color: #ccc;
text-shadow: 1px 1px 0 #000;
font-variant: small-caps;
font-weight: normal;
display: inline-block;
padding: .35em .5em .2em .5em;
@ -732,12 +731,12 @@ input.eq_gain {
#tree li:last-child {
border-bottom: none;
}
#treeul a.hl {
#tree ul a.hl {
color: #400;
background: #fc4;
text-shadow: none;
}
#treeul a {
#tree ul a {
border-radius: .3em;
display: inline-block;
}
@ -745,7 +744,7 @@ input.eq_gain {
width: calc(100% - 2em);
line-height: 1em;
}
#tree.nowrap #treeul li {
#tree.nowrap #tree li {
min-height: 1.4em;
white-space: nowrap;
}
@ -758,6 +757,7 @@ html.light #tree.nowrap #treeul a+a:hover {
background: rgba(255, 255, 255, 0.67);
color: #000;
}
#docul a:hover,
#treeul a+a:hover {
background: #181818;
color: #fff;
@ -857,30 +857,31 @@ html.light #tree.nowrap #treeul a+a:hover {
#wraptree.on+#hovertree {
display: none;
}
#ghead {
.ghead {
border-radius: .3em;
padding: .2em .5em;
line-height: 2.3em;
margin-bottom: 1em;
margin-bottom: 1.5em;
}
#ghead {
position: sticky;
top: -.3em;
z-index: 1;
}
html.light #ghead {
html.light .ghead {
background: #f7f7f7;
border-color: #ddd;
}
#ghead .btn {
.ghead .btn {
position: relative;
top: 0;
}
#ghead>span {
.ghead>span {
white-space: pre;
padding-left: .3em;
}
#ggrid {
padding-top: .5em;
margin: 0 -.5em;
margin: -.2em -.5em;
}
#ggrid>a>span {
overflow: hidden;
@ -989,6 +990,45 @@ html.light #rui {
padding: 0;
font-size: 1.5em;
}
#doc {
background: none;
overflow: visible;
margin: -1em 0 .5em 0;
padding: 1em 0 1em 0;
}
#docul li.bn {
text-align: center;
padding: .5em 0;
}
#doc.prism {
padding-left: 3em;
}
#doc code {
background: none;
box-shadow: none;
z-index: 1;
}
#doc.mdo {
white-space: normal;
}
#doc.prism * {
line-height: 1.5em;
}
#doc .line-highlight {
border-radius: .3em;
box-shadow: 0 0 .5em #333;
background: linear-gradient(90deg, #111, #222);
}
html.light #doc .line-highlight {
box-shadow: 0 0 .5em #ccc;
background: linear-gradient(90deg, #fff, #eee);
}
#docul li {
margin: 0;
}
#tree #docul a {
display: block;
}
#pvol,
#barbuf,
#barpos,
@ -1043,7 +1083,7 @@ html,
.opbox,
#path,
#srch_form,
#ghead {
.ghead {
background: #2b2b2b;
border: 1px solid #333;
box-shadow: 0 0 .3em #111;
@ -1110,12 +1150,15 @@ html.light #ops,
html.light .opbox,
html.light #path,
html.light #srch_form,
html.light #ghead,
html.light .ghead,
html.light #u2etas {
background: #f7f7f7;
box-shadow: 0 0 .3em #ccc;
border-color: #f7f7f7;
}
html.light #wrap.doc {
background: #f7f7f7;
}
html.light #ops a.act {
box-shadow: 0 .2em .2em #ccc;
background: #fff;
@ -1158,17 +1201,17 @@ html.light #treeul a+a {
background: inherit;
color: #06a;
}
html.light #treeul a.hl {
html.light #tree ul a.hl {
background: #07a;
color: #fff;
}
html.light #treeul a.hl:hover {
html.light #tree ul a.hl:hover {
background: #059;
}
html.light #tree li {
border-color: #f7f7f7 #fff #ddd #fff;
}
html.light #treeul a:hover {
html.light #tree ul a:hover {
background: #fff;
}
html.light #tree ul {
@ -1283,6 +1326,7 @@ html.light #files td div span {
color: #000;
}
html.light #path {
color: #777;
background: #f7f7f7;
text-shadow: none;
box-shadow: 0 0 .3em #bbb;
@ -1300,6 +1344,7 @@ html.light #path a:hover {
html.light #files tbody div a {
color: #d38;
}
html.light #docul a:hover,
html.light #files a:hover,
html.light #files tr.sel a:hover {
color: #000;

View file

@ -76,6 +76,12 @@
<div id="wrap">
{%- if doc %}
<div id="bdoc"><pre>{{ doc|e }}</pre></div>
{%- else %}
<div id="bdoc"></div>
{%- endif %}
<div id="pro" class="logue">{{ logues[0] }}</div>
<table id="files">
@ -135,6 +141,7 @@
have_del = {{ have_del|tojson }},
have_unpost = {{ have_unpost|tojson }},
have_zip = {{ have_zip|tojson }},
txt_ext = "{{ txt_ext }}",
readme = {{ readme|tojson }};
document.documentElement.setAttribute("class", localStorage.lightmode == 1 ? "light" : "dark");

View file

@ -2295,6 +2295,204 @@ var fileman = (function () {
})();
var showfile = (function () {
var r = {};
r.map = {
'.ahk': 'autohotkey',
'.bas': 'basic',
'.bat': 'batch',
'.cxx': 'cpp',
'.h': 'c',
'.hpp': 'cpp',
'.htm': 'html',
'.hxx': 'cpp',
'.ps1': 'powershell',
'.psm1': 'powershell',
'.pl': 'perl',
'.rs': 'rust',
'.sh': 'bash',
'.service': 'systemd',
'.vb': 'vbnet',
'.v': 'verilog',
'.vh': 'verilog',
'.yml': 'yaml'
};
r.nmap = {
'cmakelists.txt': 'cmake',
'dockerfile': 'docker'
};
var x = txt_ext + ' c cpp cs css diff go html ini java js json jsx kt kts less latex lisp lua makefile md py r rss rb ruby sass scss sql svg swift tex toml ts vhdl xml yaml';
x = x.split(/ +/g);
for (var a = 0; a < x.length; a++)
r.map["." + x[a]] = x[a];
window.Prism = { 'manual': true };
var em = QS('#bdoc>pre');
if (em)
em = [window.location.search.split(/[?&]doc=/)[1].split('&')[0], window.location.hash, em.textContent];
r.setstyle = function () {
qsr('#prism_css');
var el = mknod('link');
el.rel = 'stylesheet';
el.href = '/.cpr/deps/prism' + (light ? '' : 'd') + '.css';
el.setAttribute('id', 'prism_css');
document.head.appendChild(el);
};
r.active = function () {
return document.location.search.indexOf('doc=') + 1;
};
function getlang(fn) {
fn = fn.toLowerCase();
var ext = fn.slice(fn.lastIndexOf('.'));
return r.map[ext] || r.nmap[fn];
}
r.addlinks = function () {
r.files = [];
var links = msel.getall();
for (var a = 0; a < links.length; a++) {
var link = links[a],
fn = link.vp.split('/').slice(-1)[0],
lang = getlang(fn);
if (!lang)
continue;
r.files.push({ 'id': link.id, 'name': fn });
if (lang == 'md')
continue;
ebi(link.id).closest('tr').getElementsByTagName('td')[0].innerHTML =
'<a href="#" hl="' + link.id + '">-txt-</a>';
}
if (em) {
render(em);
em = null;
}
};
r.show = function (url) {
var xhr = new XMLHttpRequest();
xhr.url = url;
xhr.ts = Date.now();
xhr.open('GET', url.split('?')[0] + '?raw', true);
xhr.onreadystatechange = load_cb;
xhr.send();
};
function load_cb() {
if (this.readyState != XMLHttpRequest.DONE)
return;
if (this.status !== 200) {
toast.err(0, "recvtree, http " + this.status + ": " + this.responseText);
return;
}
render([this.url, '', this.responseText]);
}
function render(doc) {
r.q = null;
var url = doc[0],
lnh = doc[1],
txt = doc[2],
name = url.split('/').slice(-1)[0],
lang = getlang(name),
is_md = lang == 'md';
ebi('files').style.display = ebi('gfiles').style.display = ebi('pro').style.display = ebi('epi').style.display = 'none';
ebi('dldoc').setAttribute('href', url);
var wr = ebi('bdoc'),
nav = ebi('treeul'),
defer = !Prism.highlightElement;
var fun = function (el) {
console.log('fun fun fun fun');
try {
if (lnh.slice(0, 5) == '#doc.')
sethash(lnh.slice(1));
Prism.highlightElement(el || QS('#doc>code'));
}
catch (ex) { }
}
qsr('#doc');
var el = mknod('pre');
el.setAttribute('id', 'doc');
clmod(ebi('wrap'), 'doc', !is_md);
if (is_md) {
show_md(txt, name, el);
}
else {
el.textContent = txt;
el.innerHTML = '<code>' + el.innerHTML + '</code>';
el.setAttribute('class', 'prism linkable-line-numbers line-numbers language-' + lang);
if (!defer)
fun(el.firstChild);
else
import_js('/.cpr/deps/prism.js', function () { fun(); });
}
wr.appendChild(el);
wr.style.display = '';
document.documentElement.scrollTop = 0;
hist_push('?doc=' + url.split('/').slice(-1)[0]);
qsr('#docul');
qsr('#docname');
el = mknod('span');
el.textContent = name;
el.setAttribute('id', 'docname');
ebi('path').appendChild(el);
if (!nav)
return;
var html = ['<li class="bn">list of textfiles in<br />' + esc(get_vpath()) + '</li>'];
for (var a = 0; a < r.files.length; a++) {
var file = r.files[a];
html.push('<li><a href="#" hl="' + file.id +
(file.name == name ? '" class="hl' : '') +
'">' + esc(uricom_dec(file.name)[0]) + '</a>');
}
el = mknod('ul');
el.innerHTML = html.join('\n');
el.setAttribute('id', 'docul');
nav.style.display = 'none';
nav.parentNode.insertBefore(el, nav);
el.onclick = ebi('files').onclick;
}
var bdoc = ebi('bdoc');
bdoc.setAttribute('class', 'line-numbers');
bdoc.innerHTML = (
'<div id="hdoc" class="ghead">\n' +
'<a href="#" class="btn" id="xdoc">close</a>\n' +
'<a href="#" class="btn" id="dldoc">download</a>\n' +
'<a href="#" class="btn" id="prevdoc">prev</a>\n' +
'<a href="#" class="btn" id="nextdoc">next</a>\n' +
'</div>'
);
ebi('xdoc').onclick = function () {
thegrid.setvis(true);
}
ebi('dldoc').setAttribute('download', '');
ebi('prevdoc').onclick = function () { tree_neigh(-1); };
ebi('nextdoc').onclick = function () { tree_neigh(1); };
return r;
})();
var thegrid = (function () {
var lfiles = ebi('files'),
gfiles = mknod('div');
@ -2302,7 +2500,7 @@ var thegrid = (function () {
gfiles.setAttribute('id', 'gfiles');
gfiles.style.display = 'none';
gfiles.innerHTML = (
'<div id="ghead">' +
'<div id="ghead" class="ghead">' +
'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a> <span>zoom: ' +
'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">&ndash;</a> ' +
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a></span> <span>chop: ' +
@ -2350,8 +2548,22 @@ var thegrid = (function () {
for (var a = 0; a < links.length; a++)
links[a].onclick = btnclick;
r.setvis = function (vis) {
(r.en ? gfiles : lfiles).style.display = vis ? '' : 'none';
r.setvis = function (force) {
if (showfile.active()) {
if (!force)
return;
hist_push(get_evpath());
}
var vis = has(perms, "read");
gfiles.style.display = vis && r.en ? '' : 'none';
lfiles.style.display = vis && !r.en ? '' : 'none';
ebi('pro').style.display = ebi('epi').style.display = ebi('treeul').style.display = '';
ebi('bdoc').style.display = 'none';
clmod(ebi('wrap'), 'doc');
qsr('#docname');
qsr('#docul');
};
r.setdirty = function () {
@ -2359,6 +2571,7 @@ var thegrid = (function () {
if (r.en) {
loadgrid();
}
r.setvis();
};
function setln(v) {
@ -2477,18 +2690,11 @@ var thegrid = (function () {
tt.att(ebi('ggrid'));
};
function ungrid() {
lfiles.style.display = '';
gfiles.style.display = 'none';
}
function loadgrid() {
if (have_webp === null)
return setTimeout(loadgrid, 50);
lfiles.style.display = 'none';
gfiles.style.display = 'block';
r.setvis();
if (!r.dirty)
return r.loadsel();
@ -2575,7 +2781,7 @@ var thegrid = (function () {
bcfg_bind(r, 'thumbs', 'thumbs', true, r.setdirty);
bcfg_bind(r, 'sel', 'gridsel', false, r.loadsel);
bcfg_bind(r, 'en', 'griden', false, function (v) {
v ? loadgrid() : ungrid();
v ? loadgrid() : r.setvis(true);
pbar.onresize();
vbar.onresize();
});
@ -2600,7 +2806,7 @@ function th_onload(el) {
function tree_scrollto(e) {
ev(e);
var act = QS('#treeul a.hl'),
var act = QS('#tree a.hl'),
ul = act ? act.offsetParent : null;
if (!ul)
@ -2620,7 +2826,7 @@ function tree_scrollto(e) {
function tree_neigh(n) {
var links = QSA('#treeul li>a+a');
var links = QSA(showfile.active() ? '#docul li>a' : '#treeul li>a+a');
if (!links.length) {
treectl.dir_cb = function () {
tree_neigh(n);
@ -2650,6 +2856,9 @@ function tree_neigh(n) {
function tree_up() {
if (showfile.active())
return thegrid.setvis(true);
var act = QS('#treeul a.hl');
if (!act) {
treectl.dir_cb = tree_up;
@ -3426,7 +3635,7 @@ var treectl = (function () {
html = html.join('\n');
set_files_html(html);
if (this.hpush)
if (this.hpush && !showfile.active())
hist_push(this.top);
acct = res.acct;
@ -3597,7 +3806,7 @@ function apply_perms(newperms) {
up2k.set_fsearch();
ebi('widget').style.display = have_read ? '' : 'none';
thegrid.setvis(have_read);
thegrid.setvis();
if (!have_read && have_write)
goto('up2k');
}
@ -3930,6 +4139,7 @@ var light;
pbar.drawbuf();
pbar.drawpos();
vbar.draw();
showfile.setstyle();
}
bcfg_bind(window, 'light', 'lightmode', false, freshen);
@ -4232,13 +4442,9 @@ var msel = (function () {
})();
function show_readme(md, url, depth) {
if (!treectl.ireadme)
return;
var div = ebi('epi'),
errmsg = 'cannot show README.md:\n\n',
now = window.location.href.replace(/\/?[?#].*/, "");
function show_md(md, name, div, url, depth) {
var errmsg = 'cannot show ' + name + ':\n\n',
now = get_evpath();
url = url || now;
if (url != now)
@ -4249,7 +4455,7 @@ function show_readme(md, url, depth) {
return toast.warn(10, errmsg + 'failed to load marked.js')
return import_js('/.cpr/deps/marked.js', function () {
show_readme(md, url, 1);
show_md(md, name, div, url, 1);
});
}
@ -4273,6 +4479,14 @@ function show_readme(md, url, depth) {
toast.warn(10, errmsg + ex);
}
}
function show_readme(md) {
if (!treectl.ireadme)
return;
show_md(md, 'README.md', ebi('epi'));
}
if (readme)
show_readme(readme);
@ -4463,15 +4677,20 @@ function goto_unpost(e) {
ebi('files').onclick = function (e) {
var tgt = e.target.closest('a[id]');
if (!tgt || tgt.getAttribute('id').indexOf('f-') !== 0 || !tgt.textContent.endsWith('/'))
return;
if (tgt && tgt.getAttribute('id').indexOf('f-') === 0 && tgt.textContent.endsWith('/')) {
var el = treectl.find(tgt.textContent.slice(0, -1));
if (!el)
return;
var el = treectl.find(tgt.textContent.slice(0, -1));
if (!el)
return;
el.click();
return ev(e);
}
ev(e);
el.click();
tgt = e.target.closest('a[hl]');
if (tgt) {
showfile.show(noq_href(ebi(tgt.getAttribute('hl'))), tgt.getAttribute('lang'));
return ev(e);
}
}
@ -4531,6 +4750,7 @@ function reload_browser(not_mp) {
thegrid.setdirty();
msel.render();
showfile.addlinks();
}
reload_browser(true);
mukey.render();

View file

@ -736,7 +736,7 @@ function hist_replace(url) {
function sethash(hv) {
if (window.history && history.replaceState) {
hist_replace(document.location.pathname + '#' + hv);
hist_replace(document.location.pathname + document.location.search + '#' + hv);
}
else {
document.location.hash = hv;

View file

@ -45,6 +45,12 @@ RUN mkdir -p /z/dist/no-pk \
&& tar -xf zopfli.tgz
# todo
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js
# https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/default.min.css
# https://prismjs.com/download.html#themes=prism-funky&languages=markup+css+clike+javascript+autohotkey+bash+basic+batch+c+csharp+cpp+cmake+diff+docker+go+ini+java+json+kotlin+latex+less+lisp+lua+makefile+objectivec+perl+powershell+python+r+jsx+ruby+rust+sass+scss+sql+swift+systemd+toml+typescript+vbnet+verilog+vhdl+yaml&plugins=line-highlight+line-numbers+autolinker
# build fonttools (which needs zopfli)
RUN tar -xf zopfli.tgz \
&& cd zopfli* \