diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 812d5183..c8fb1c46 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -1086,14 +1086,24 @@ html.light #rui { } #rui table { width: 100%; + border-collapse: collapse; } -#rui td { - padding: .2em .5em; +#rui td+td { + padding: .2em 0 .2em .5em; +} +#rn_vadv input { + font-family: monospace, monospace; } #rui td+td, -#rui td input { +#rui td input[type="text"] { width: 100%; } +#rn_f.m td:first-child { + white-space: nowrap; +} +#rn_f.m td+td { + width: 50%; +} #rui input[readonly] { color: #fff; background: #444; @@ -1109,6 +1119,7 @@ html.light #rui { #barbuf, #barpos, #u2conf label, +#rui label, #ops { -webkit-user-select: none; -moz-user-select: none; diff --git a/copyparty/web/browser.js b/copyparty/web/browser.js index b8be8998..66d9c8a8 100644 --- a/copyparty/web/browser.js +++ b/copyparty/web/browser.js @@ -1460,6 +1460,79 @@ function play_linked() { })(); + +function fmt_ren(re, md, fmt) { + var ptr = 0; + function dive() { + var ret = '', ng = 0; + while (ptr < fmt.length) { + var ch = fmt[ptr++]; + + if (ch == '\\') { + ret += fmt[ptr++]; + continue; + } + + if (ch == ')' || ch == ']') + return [ng, ret]; + + if (ch == '$') { + ch = fmt[ptr++]; + if (ch == '[') { + var r2 = dive(); + if (r2[0] == 0) + ret += r2[1]; + } + else if (ch == '(') { + var end = fmt.indexOf(')', ptr); + if (end < 0) + throw 'the $( was never closed: ' + fmt.slice(0, ptr); + + var arg = fmt.slice(ptr, end), v = null; + ptr = end + 1; + + if (arg != parseInt(arg)) + v = md[arg]; + else { + arg = parseInt(arg); + if (!re) + throw 'regex did not match'; + + if (arg >= re.length) + throw 'matching group ' + arg + ' exceeds ' + (re.length - 0); + + v = re[arg]; + } + + if (v !== null && v !== undefined) + ret += v; + else + ng++; + } + else { + var end = fmt.indexOf('(', ptr); + if (end < 0) + throw 'no function name after the $ here: ' + fmt.slice(0, ptr); + + var fun = fmt.slice(ptr - 1, end); + throw 'function not implemented: [' + fun + ']'; + } + continue; + } + + ret += ch; + } + return [ng, ret]; + } + try { + return [true, dive()[1]]; + } + catch (ex) { + return [false, ex]; + } +} + + var fileman = (function () { var bren = ebi('fren'), bdel = ebi('fdel'), @@ -1493,16 +1566,32 @@ var fileman = (function () { return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder'); var sel = msel.getsel(); - if (sel.length !== 1) - return toast.err(3, 'select exactly 1 item to rename'); + if (!sel.length) + return toast.err(3, 'select at least one item to rename'); - var src = sel[0].vp; - if (src.endsWith('/')) - src = src.slice(0, -1); + var f = [], + base = vsplit(sel[0].vp)[0], + mkeys; - var vsp = vsplit(src), - base = vsp[0], - ofn = uricom_dec(vsp[1])[0]; + for (var a = 0; a < sel.length; a++) { + var vp = sel[a].vp; + if (vp.endsWith('/')) + vp = vp.slice(0, -1); + + var vsp = vsplit(vp); + if (base != vsp[0]) + return toast.err(0, 'bug:\n' + base + '\n' + vsp[0]); + + var vars = ft2dict(ebi(sel[a].id).closest('tr')); + mkeys = vars[1].concat(vars[2]); + + f.push({ + "src": vp, + "ofn": uricom_dec(vsp[1])[0], + "md": vars[0], + "ok": true + }); + } var rui = ebi('rui'); if (!rui) { @@ -1510,62 +1599,147 @@ var fileman = (function () { rui.setAttribute('id', 'rui'); document.body.appendChild(rui); } - var html = [ - '

rename file

', - '
', - '', - '', - '
old:
new:
', + + var html = sel.length > 1 ? ['
'] : [ '
', - '', - '|', - '', - '', - '', - '
', - '
' + '', + '//', + '' ]; - var vars = ft2dict(ebi(sel[0].id).closest('tr')), - keys = vars[1].concat(vars[2]); - - vars = vars[0]; - for (var a = 0; a < keys.length; a++) - html.push(''); + html = html.concat([ + '', + '', + 'advanced', + 'case', + '', + '
' + esc(keys[a]) + '
', + '', + '', + '', + '
regex
format
preset', + '', + '', + '
' + ]); + if (sel.length == 1) + html.push( + '
\n' + + '\n' + + ''); + else { + html.push( + '
old:
new:
' + + ''); + for (var a = 0; a < f.length; a++) + html.push( + '', + '' + + ''); + } html.push('
new nameold name
' + + '', + '
'); - rui.innerHTML = html.join('\n'); - var iold = ebi('rn_old'), - inew = ebi('rn_new'); - function rn_reset() { - inew.value = iold.value; - inew.focus(); - inew.setSelectionRange(0, inew.value.lastIndexOf('.'), "forward"); + if (sel.length == 1) { + html.push('
tags for the selected file (read-only):'); + for (var a = 0; a < mkeys.length; a++) + html.push(''); + + html.push('
' + esc(mkeys[a]) + '
'); + } + + rui.innerHTML = html.join('\n'); + for (var a = 0; a < f.length; a++) { + var k = '[n="' + a + '"]'; + f[a].iold = QS('#rn_old' + k); + f[a].inew = QS('#rn_new' + k); + f[a].inew.value = f[a].iold.value = f[a].ofn; + + (function (a) { + f[a].inew.onkeydown = function (e) { + f[a].ok = true; + + if (e.key == 'Escape') + return rn_cancel(); + + if (e.key == 'Enter') + return rn_apply(); + }; + QS('.rn_dec' + k).onclick = function () { + f[a].inew.value = uricom_dec(f[a].inew.value)[0]; + }; + QS('.rn_reset' + k).onclick = function () { + rn_reset(a); + }; + })(a); + } + rn_reset(0); + tt.att(rui); + + var adv = bcfg_get('rn_adv', false), + cs = bcfg_get('rn_case', false); + + function sadv() { + ebi('rn_vadv').style.display = ebi('rn_case').style.display = adv ? '' : 'none'; + } + sadv(); + + function rn_reset(n) { + f[n].inew.value = f[n].iold.value = f[n].ofn; + f[n].inew.focus(); + f[n].inew.setSelectionRange(0, f[n].inew.value.lastIndexOf('.'), "forward"); } function rn_cancel() { rui.parentNode.removeChild(rui); } - inew.onkeydown = function (e) { - if (e.key == 'Escape') - return rn_cancel(); - - if (e.key == 'Enter') - return rn_apply(); - }; ebi('rn_cancel').onclick = rn_cancel; - ebi('rn_reset').onclick = rn_reset; ebi('rn_apply').onclick = rn_apply; - ebi('rn_dec').onclick = function () { - inew.value = uricom_dec(inew.value)[0]; + ebi('rn_adv').onclick = function () { + adv = !adv; + bcfg_set('rn_adv', adv); + sadv(); + }; + ebi('rn_case').onclick = function () { + cs = !cs; + bcfg_set('rn_case', cs); }; - iold.value = ofn; - rn_reset(); + var ire = ebi('rn_re'), + ifmt = ebi('rn_fmt'), + ipre = ebi('rn_pre'), + idel = ebi('rn_pdel'), + inew = ebi('rn_pnew'); + + ire.oninput = ifmt.oninput = function (e) { + var re = ire.value, + fmt = ifmt.value; + + if (!fmt) + return; + + try { + re = re ? new RegExp(re, cs ? 'i' : '') : null; + } + catch (ex) { + return toast.err(5, 'invalid regex:\n' + ex); + } + toast.hide(); + + for (var a = 0; a < f.length; a++) { + var m = re ? re.exec(f[a].ofn) : null, + ret = fmt_ren(m, f[a].md, fmt); + + f[a].ok = ret[0]; + f[a].inew.value = ret[1]; + } + }; function rn_apply() { - var dst = base + uricom_enc(inew.value, false); + toast.inf(0, 'renaming ' + f.length + ' items\n\n' + f[0].ofn); + var dst = base + uricom_enc(f[0].inew.value, false); function rename_cb() { if (this.readyState != XMLHttpRequest.DONE) @@ -1576,15 +1750,21 @@ var fileman = (function () { toast.err(9, 'rename failed:\n' + msg); return; } + + f.shift().inew.value = '( OK )'; + if (f.length) + return rn_apply(); + toast.ok(2, 'rename OK'); treectl.goto(get_evpath()); rn_cancel(); } + var xhr = new XMLHttpRequest(); - xhr.open('GET', src + '?move=' + dst, true); + xhr.open('GET', f[0].src + '?move=' + dst, true); xhr.onreadystatechange = rename_cb; xhr.send(); - }; + } }; r.delete = function (e) {