aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordev <dev@localhost>2026-10-18 00:54:58 +0000
committerdev <dev@localhost>2026-10-18 00:54:58 +0000
commita9423f9fc8cb29651fd19ffe3afceff2b4f72d2f (patch)
treed009150a59df018ce281bfbba80cb517d7f0a09e
parent97887f681dbe687b37f341be66ee042d45748e7d (diff)
downloadmedman-a9423f9fc8cb29651fd19ffe3afceff2b4f72d2f.tar.gz
Add shift-click range selection to media gridHEADmaster
-rw-r--r--README.md1
-rw-r--r--templates/index.html29
2 files changed, 29 insertions, 1 deletions
diff --git a/README.md b/README.md
index df1f37e..bcad9da 100644
--- a/README.md
+++ b/README.md
@@ -127,6 +127,7 @@ volumes:
- **Bulk operations**: select-all, batch tag/untag/move/delete
- **Previous/Next navigation**: arrow keys or buttons in detail view
- **Checkbox selection**: click the checkbox on any thumbnail to toggle
+- **Shift-select**: hold Shift and click a second thumbnail to select the whole range from the last clicked item
- **Export/Import**: tags as JSON (export/import)
- **archival.py**: create SquashFS + LUKS containers directly from MedMan
- **Container mount API**: mount SquashFS containers as additional data dirs
diff --git a/templates/index.html b/templates/index.html
index 24b9658..4568bd6 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -257,6 +257,7 @@ const S = {
_sentinel:null, // sentinel element
_scrollTop:0, // saved scroll position when closing modal
_prefetch:[], // preloaded adjacent file data (cleared on modal close)
+ _lastSel:null, // hash of last clicked card (anchor for shift-select)
};
function st(msg){let e=document.getElementById('status');e.textContent=msg;e.classList.add('on');clearTimeout(e._t);e._t=setTimeout(()=>e.classList.remove('on'),2200);}
@@ -435,6 +436,7 @@ function _bindCardEvents(c){
c.addEventListener('click',e=>{
let h=c.dataset.hash;
if(e.ctrlKey||e.metaKey){toggleSel(h,c);return;}
+ if(e.shiftKey){rangeSel(h);e.preventDefault();return;}
if(e.target.classList.contains('chk')||e.target.closest('.chk')){
toggleSel(h,c); e.stopPropagation(); return;
}
@@ -559,18 +561,43 @@ function fs(b){if(!b)return'';if(b<1024)return b+' B';if(b<1048576)return (b/102
function toggleSel(h,cardEl){
if(S.sel.has(h)){S.sel.delete(h);if(cardEl)cardEl.classList.remove('sel');}
else{S.sel.add(h);if(cardEl)cardEl.classList.add('sel');}
+ S._lastSel=h;
updateSelUI(); _updateURL();
}
+// Select every card between two hashes (inclusive), using the current grid
+// order. Returns false if either endpoint is not currently rendered.
+function _selRange(aHash,bHash){
+ let a=document.querySelector('#grid .card[data-hash="'+CSS.escape(aHash)+'"]');
+ let b=document.querySelector('#grid .card[data-hash="'+CSS.escape(bHash)+'"]');
+ if(!a||!b) return false;
+ let cards=[...document.querySelectorAll('#grid .card')];
+ let i=cards.indexOf(a), j=cards.indexOf(b);
+ if(i<0||j<0) return false;
+ let lo=Math.min(i,j), hi=Math.max(i,j);
+ for(let k=lo;k<=hi;k++) S.sel.add(cards[k].dataset.hash);
+ updateSelUI(); _updateURL(); refreshCardSelections();
+ return true;
+}
+// Shift-click: extend the selection from the last clicked card to this one.
+function rangeSel(h){
+ let anchor=S._lastSel;
+ if(anchor===h) return;
+ if(anchor && _selRange(anchor,h)){S._lastSel=h;return;}
+ // No usable anchor (first click / anchor filtered out) → plain toggle
+ let card=document.querySelector('#grid .card[data-hash="'+CSS.escape(h)+'"]');
+ toggleSel(h,card);
+}
function updateSelUI(){
let n=S.sel.size;
document.getElementById('sel-n').textContent=n+' ausgewählt';
document.getElementById('sel-bar').classList.toggle('on',n>0);
document.getElementById('sel-info').textContent=n?n+' Dateien ausgewählt':'Keine Auswahl';
}
-function clearSel(){S.sel.clear();updateSelUI();_updateURL(); refreshCardSelections();}
+function clearSel(){S.sel.clear();S._lastSel=null;updateSelUI();_updateURL(); refreshCardSelections();}
function selAll(){
let cards=document.querySelectorAll('#grid .card');
for(let c of cards){S.sel.add(c.dataset.hash);}
+ if(cards.length) S._lastSel=cards[cards.length-1].dataset.hash;
updateSelUI();_updateURL(); refreshCardSelections();
}
function refreshCardSelections(){