diff options
| author | dev <dev@localhost> | 2026-10-18 00:54:58 +0000 |
|---|---|---|
| committer | dev <dev@localhost> | 2026-10-18 00:54:58 +0000 |
| commit | a9423f9fc8cb29651fd19ffe3afceff2b4f72d2f (patch) | |
| tree | d009150a59df018ce281bfbba80cb517d7f0a09e | |
| parent | 97887f681dbe687b37f341be66ee042d45748e7d (diff) | |
| download | medman-a9423f9fc8cb29651fd19ffe3afceff2b4f72d2f.tar.gz | |
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | templates/index.html | 29 |
2 files changed, 29 insertions, 1 deletions
@@ -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(){ |
