diff --git a/copyparty/__main__.py b/copyparty/__main__.py index c44ac711..2e91822e 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -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") diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index cb3aaf53..0a3bc2d2 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -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(",") diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 2dd4c853..88a138eb 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -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; diff --git a/copyparty/web/browser.html b/copyparty/web/browser.html index bf46ef8f..4b27f836 100644 --- a/copyparty/web/browser.html +++ b/copyparty/web/browser.html @@ -76,6 +76,12 @@
+ {%- if doc %} +
{{ doc|e }}
+ {%- else %} +
+ {%- endif %} +
{{ logues[0] }}
@@ -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"); diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index 6f6d025c..f4119e1c 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -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 = + '-txt-'; + } + 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 = '' + el.innerHTML + ''; + 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 = ['
  • list of textfiles in
    ' + esc(get_vpath()) + '
  • ']; + for (var a = 0; a < r.files.length; a++) { + var file = r.files[a]; + html.push('
  • ' + esc(uricom_dec(file.name)[0]) + ''); + } + + 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 = ( + '
    \n' + + 'close\n' + + 'download\n' + + 'prev\n' + + 'next\n' + + '
    ' + ); + 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 = ( - '
    ' + + '
    ' + 'multiselect zoom: ' + ' ' + '+ 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(); diff --git a/copyparty/web/util.js b/copyparty/web/util.js index d783121c..097b8bfd 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -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; diff --git a/scripts/deps-docker/Dockerfile b/scripts/deps-docker/Dockerfile index b5211498..c13323cd 100644 --- a/scripts/deps-docker/Dockerfile +++ b/scripts/deps-docker/Dockerfile @@ -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* \