mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
fileman ui
This commit is contained in:
parent
fec0c620d4
commit
27cce086c6
11
README.md
11
README.md
|
@ -40,6 +40,7 @@ this is the readme for v0.12 (unreleased) which has a different expression for v
|
|||
* [zip downloads](#zip-downloads)
|
||||
* [uploading](#uploading)
|
||||
* [file-search](#file-search)
|
||||
* [file manager](#file-manager)
|
||||
* [markdown viewer](#markdown-viewer)
|
||||
* [other tricks](#other-tricks)
|
||||
* [searching](#searching)
|
||||
|
@ -230,6 +231,9 @@ the browser has the following hotkeys (assumes qwerty, ignores actual layout)
|
|||
* `M` parent folder (or unexpand current)
|
||||
* `G` toggle list / grid view
|
||||
* `T` toggle thumbnails / icons
|
||||
* `ctrl-X` cut selected files/folders
|
||||
* `ctrl-V` paste
|
||||
* `F2` rename selected file/folder
|
||||
* when playing audio:
|
||||
* `J/L` prev/next song
|
||||
* `U/O` skip 10sec back/forward
|
||||
|
@ -352,6 +356,13 @@ note that since up2k has to read the file twice, `[🎈 bup]` can be up to 2x fa
|
|||
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well (thanks to tls also functioning as an integrity check)
|
||||
|
||||
|
||||
## file manager
|
||||
|
||||
if you have the required permissions, you can cut/paste, rename, and delete files/folders
|
||||
|
||||
you can move files across browser tabs (cut in one tab, paste in another)
|
||||
|
||||
|
||||
## markdown viewer
|
||||
|
||||

|
||||
|
|
|
@ -1357,12 +1357,19 @@ class Up2k(object):
|
|||
self._forget_file(svn.realpath, srem, c1, w)
|
||||
return "k"
|
||||
|
||||
# not found in dst vol; copy info
|
||||
# not found in dst db; copy info
|
||||
self.log("mv: plain move")
|
||||
self._copy_tags(c1, c2, w)
|
||||
self._forget_file(svn.realpath, srem, c1, w)
|
||||
st = bos.stat(sabs)
|
||||
|
||||
if c1 and c2:
|
||||
self._copy_tags(c1, c2, w)
|
||||
|
||||
if c1:
|
||||
self._forget_file(svn.realpath, srem, c1, w)
|
||||
|
||||
if c2:
|
||||
self.db_add(c2, w, drd, dfn, st.st_mtime, st.st_size)
|
||||
|
||||
bos.rename(sabs, dabs)
|
||||
return "k"
|
||||
|
||||
|
|
|
@ -432,6 +432,27 @@ html.light #ggrid a.sel {
|
|||
font-size: .6em;
|
||||
display: block;
|
||||
}
|
||||
#wfm a:not(.en) {
|
||||
opacity: .3;
|
||||
color: #f6c;
|
||||
}
|
||||
html.light #wfm a:not(.en) {
|
||||
color: #c4a;
|
||||
}
|
||||
#files tbody td.c1 {
|
||||
animation: fcut1 .5s ease-out;
|
||||
}
|
||||
#files tbody td.c2 {
|
||||
animation: fcut2 .5s ease-out;
|
||||
}
|
||||
@keyframes fcut1 {
|
||||
0% {opacity:0}
|
||||
100% {opacity:1}
|
||||
}
|
||||
@keyframes fcut2 {
|
||||
0% {opacity:0}
|
||||
100% {opacity:1}
|
||||
}
|
||||
#wzip a {
|
||||
font-size: .4em;
|
||||
margin: -.3em .3em;
|
||||
|
|
|
@ -31,10 +31,9 @@ ebi('widget').innerHTML = (
|
|||
'<div id="wtoggle">' +
|
||||
'<span id="wfm"><a' +
|
||||
' href="#" id="fren" tt="rename selected item$NHotkey: F2">✎<span>name</span></a><a' +
|
||||
' href="#" id="fdel" tt="delete selected items">⌫<span>delete</span></a><a' +
|
||||
' href="#" id="fcut" tt="cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-shift-X">✂<span>cut</span></a><a' +
|
||||
' href="#" id="fcpy" tt="copy selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-shift-C">⧉<span>copy</span></a><a' +
|
||||
' href="#" id="fpst" tt="paste a previously cut/copied selection$NHotkey: ctrl-shift-V">📋<span>paste</span></a>' +
|
||||
' href="#" id="fdel" tt="delete selected items$NHotkey: ctrl-K">⌫<span>delete</span></a><a' +
|
||||
' href="#" id="fcut" tt="cut selected items <small>(then paste somewhere else)</small>$NHotkey: ctrl-X">✂<span>cut</span></a><a' +
|
||||
' href="#" id="fpst" tt="paste a previously cut/copied selection$NHotkey: ctrl-V">📋<span>paste</span></a>' +
|
||||
'</span><span id="wzip"><a' +
|
||||
' href="#" id="selall" tt="select all files">sel.<br />all</a><a' +
|
||||
' href="#" id="selinv" tt="invert selection">sel.<br />inv.</a><a' +
|
||||
|
@ -1465,6 +1464,132 @@ function play_linked() {
|
|||
})();
|
||||
|
||||
|
||||
var fileman = (function () {
|
||||
var bren = ebi('fren'),
|
||||
bdel = ebi('fdel'),
|
||||
bcut = ebi('fcut'),
|
||||
bpst = ebi('fpst'),
|
||||
r = {};
|
||||
|
||||
r.clip = null;
|
||||
r.bus = new BroadcastChannel("fileman_bus");
|
||||
|
||||
r.render = function () {
|
||||
if (r.clip === null)
|
||||
r.clip = jread('fman_clip', []);
|
||||
|
||||
var sel = msel.getsel();
|
||||
clmod(bren, 'en', sel.length == 1);
|
||||
clmod(bdel, 'en', sel.length);
|
||||
clmod(bcut, 'en', sel.length);
|
||||
clmod(bpst, 'en', r.clip && r.clip.length);
|
||||
bren.style.display = has(perms, 'write') && has(perms, 'move') ? '' : 'none';
|
||||
bdel.style.display = has(perms, 'delete') ? '' : 'none';
|
||||
bcut.style.display = has(perms, 'move') ? '' : 'none';
|
||||
bpst.style.display = has(perms, 'write') ? '' : 'none';
|
||||
bpst.setAttribute('tt', 'paste ' + r.clip.length + ' items$NHotkey: ctrl-V');
|
||||
};
|
||||
|
||||
r.rename = function (e) {
|
||||
ev(e);
|
||||
var sel = msel.getsel();
|
||||
if (sel.length !== 1)
|
||||
return alert('select exactly 1 item to rename');
|
||||
|
||||
var fn = prompt('new filename:', sel[0]);
|
||||
if (!fn || fn == sel[0])
|
||||
return alert('aborted');
|
||||
};
|
||||
|
||||
r.delete = function (e) {
|
||||
ev(e);
|
||||
var sel = msel.getsel(),
|
||||
req = msel.selu;
|
||||
|
||||
if (!sel.length)
|
||||
return alert('select at least 1 item to delete');
|
||||
|
||||
if (!confirm('===== DANGER =====\nDELETE these ' + req.length + ' items?\n\n' + req.join('\n')))
|
||||
return;
|
||||
|
||||
if (!confirm('Last chance! Delete?'))
|
||||
return;
|
||||
};
|
||||
|
||||
r.cut = function (e) {
|
||||
ev(e);
|
||||
var sel = msel.getsel(),
|
||||
vsel = msel.selu;
|
||||
|
||||
if (!sel.length)
|
||||
return alert('select at least 1 item to cut');
|
||||
|
||||
var tds = QSA('#files tbody tr.sel td');
|
||||
for (var a = 0; a < tds.length; a++) {
|
||||
var cl = tds[a].classList, inv = cl.contains('c1');
|
||||
cl.remove(inv ? 'c1' : 'c2');
|
||||
cl.add(inv ? 'c2' : 'c1');
|
||||
}
|
||||
|
||||
jwrite('fman_clip', vsel);
|
||||
r.tx();
|
||||
};
|
||||
|
||||
r.paste = function (e) {
|
||||
ev(e);
|
||||
if (!r.clip.length)
|
||||
return alert('first cut some items to paste\n\nnote: you can cut/paste across different tabs');
|
||||
|
||||
var req = [],
|
||||
exists = [],
|
||||
indir = [],
|
||||
links = QSA('#files tbody td:nth-child(2) a');
|
||||
|
||||
for (var a = 0, aa = links.length; a < aa; a++)
|
||||
indir.push(links[a].getAttribute('name'));
|
||||
|
||||
for (var a = 0; a < r.clip.length; a++) {
|
||||
var found = false;
|
||||
for (var b = 0; b < indir.length; b++) {
|
||||
if (r.clip[a].endsWith('/' + indir[b])) {
|
||||
exists.push(r.clip[a]);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
req.push(r.clip[a]);
|
||||
}
|
||||
|
||||
if (exists.length)
|
||||
alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + exists.join('\n'));
|
||||
|
||||
if (!confirm('paste these ' + req.length + ' items here?\n\n' + req.join('\n')))
|
||||
return;
|
||||
|
||||
jwrite('fman_clip', []);
|
||||
r.tx();
|
||||
};
|
||||
|
||||
r.bus.onmessage = function () {
|
||||
console.log('fman onmsg');
|
||||
r.clip = null;
|
||||
r.render();
|
||||
};
|
||||
|
||||
r.tx = function () {
|
||||
r.bus.postMessage(1);
|
||||
r.bus.onmessage();
|
||||
};
|
||||
|
||||
bren.onclick = r.rename;
|
||||
bdel.onclick = r.delete;
|
||||
bcut.onclick = r.cut;
|
||||
bpst.onclick = r.paste;
|
||||
|
||||
return r;
|
||||
})();
|
||||
|
||||
|
||||
var thegrid = (function () {
|
||||
var lfiles = ebi('files'),
|
||||
gfiles = mknod('div');
|
||||
|
@ -1794,7 +1919,7 @@ document.onkeydown = function (e) {
|
|||
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||
return;
|
||||
|
||||
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||
if (e.altKey || e.isComposing)
|
||||
return;
|
||||
|
||||
if (QS('#bbox-overlay.visible'))
|
||||
|
@ -1802,6 +1927,19 @@ document.onkeydown = function (e) {
|
|||
|
||||
var k = e.code + '', pos = -1, n;
|
||||
|
||||
if (ctrl(e)) {
|
||||
if (k == 'KeyX')
|
||||
return fileman.cut();
|
||||
|
||||
if (k == 'KeyV')
|
||||
return fileman.paste();
|
||||
|
||||
if (k == 'KeyK')
|
||||
return fileman.delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
|
||||
return;
|
||||
|
||||
|
@ -1840,6 +1978,9 @@ document.onkeydown = function (e) {
|
|||
if (k == 'KeyT')
|
||||
return ebi('thumbs').click();
|
||||
|
||||
if (k == 'F2')
|
||||
return fileman.rename();
|
||||
|
||||
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
|
||||
if (k == 'KeyA')
|
||||
return QS('#twig').click();
|
||||
|
@ -3026,18 +3167,35 @@ var arcfmt = (function () {
|
|||
|
||||
|
||||
var msel = (function () {
|
||||
function getsel() {
|
||||
var names = [],
|
||||
links = QSA('#files tbody tr.sel td:nth-child(2) a');
|
||||
var r = {};
|
||||
r.selu = null;
|
||||
r.seln = null;
|
||||
|
||||
for (var a = 0, aa = links.length; a < aa; a++)
|
||||
names.push(links[a].getAttribute('href').replace(/\/$/, "").split('/').slice(-1));
|
||||
r.getsel = function () {
|
||||
if (r.seln !== null)
|
||||
return r.seln;
|
||||
|
||||
return names;
|
||||
r.selu = [];
|
||||
r.seln = [];
|
||||
var links = QSA('#files tbody tr.sel td:nth-child(2) a'),
|
||||
vbase = get_evpath();
|
||||
|
||||
for (var a = 0, aa = links.length; a < aa; a++) {
|
||||
var url = links[a].getAttribute('href').replace(/\/$/, ""),
|
||||
name = url.split('/').slice(-1);
|
||||
|
||||
r.selu.push(url.indexOf('/') !== -1 ? url : vbase + url);
|
||||
r.seln.push(name);
|
||||
links[a].setAttribute('name', name);
|
||||
}
|
||||
|
||||
return r.seln;
|
||||
}
|
||||
function selui() {
|
||||
clmod(ebi('wtoggle'), 'sel', getsel().length);
|
||||
r.selu = r.seln = null;
|
||||
clmod(ebi('wtoggle'), 'sel', r.getsel().length);
|
||||
thegrid.loadsel();
|
||||
fileman.render();
|
||||
}
|
||||
function seltgl(e) {
|
||||
ev(e);
|
||||
|
@ -3060,7 +3218,7 @@ var msel = (function () {
|
|||
};
|
||||
ebi('selzip').onclick = function (e) {
|
||||
ev(e);
|
||||
var names = getsel(),
|
||||
var names = r.getsel(),
|
||||
arg = ebi('selzip').getAttribute('fmt'),
|
||||
txt = names.join('\n'),
|
||||
frm = mknod('form');
|
||||
|
@ -3083,16 +3241,16 @@ var msel = (function () {
|
|||
console.log(txt);
|
||||
frm.submit();
|
||||
};
|
||||
function render() {
|
||||
r.render = function () {
|
||||
var tds = QSA('#files tbody td+td+td');
|
||||
for (var a = 0, aa = tds.length; a < aa; a++) {
|
||||
tds[a].onclick = seltgl;
|
||||
}
|
||||
r.selu = r.seln = null;
|
||||
arcfmt.render();
|
||||
fileman.render();
|
||||
}
|
||||
return {
|
||||
"render": render
|
||||
};
|
||||
return r;
|
||||
})();
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue