From f72dec6fe128b5e2d9ab2ecf889de1a300f62482 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sun, 5 Apr 2026 01:49:52 +0200 Subject: [PATCH] fix(multiSelect): correct select all checkbox, correct shift click --- static/js/app/filesView.js | 21 +++--------- static/js/app/ui.js | 9 ++++- static/js/features/files/multiSelect.js | 45 +++++++++++++++---------- static/js/features/files/search.js | 12 ------- static/js/features/library/favorites.js | 12 ------- static/js/features/library/recent.js | 4 +++ 6 files changed, 44 insertions(+), 59 deletions(-) diff --git a/static/js/app/filesView.js b/static/js/app/filesView.js index 14ed1051..42ce1eea 100644 --- a/static/js/app/filesView.js +++ b/static/js/app/filesView.js @@ -206,23 +206,11 @@ async function loadFiles(options = { insertHistory: true}) { const listing = await response.json(); - if (window.multiSelect) window.multiSelect.clear(); window.ui._items.clear(); - - const _t = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop(); - window.ui.showError(` -
-
-
${_t('files.name')}
-
${_t('files.type')}
-
${_t('files.size')}
-
${_t('files.modified')}
-
` - ); - - const selectAllCb = document.getElementById('select-all-checkbox'); - if (selectAllCb && window.multiSelect) { - selectAllCb.addEventListener('change', () => window.multiSelect.toggleAll()); + window.ui.resetFilesList(); + if (window.multiSelect) { + window.multiSelect.clear(); + window.multiSelect.init(); // this will wire buttons & select-all-checkbox } const folderList = Array.isArray(listing.folders) ? listing.folders : []; @@ -231,7 +219,6 @@ async function loadFiles(options = { insertHistory: true}) { if (folderList.length === 0 && fileList.length === 0) { window.ui.showEmptyList(); } else { - window.ui.resetFilesList(); window.ui.renderFolders(folderList); window.ui.renderFiles(fileList); } diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 1acb2a30..30fb5280 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -892,6 +892,12 @@ const ui = { return; } + // shiftkey is used to complete selection + if (e.shiftKey && window.multiSelect) { + window.multiSelect.handleToggleItem(card, e); + return; + } + if (info.type === 'folder') { navigateFolder(card); } else { @@ -1225,6 +1231,7 @@ const ui = { const filesContainerError=document.getElementById("files-container-error"); if (!filesList) return; + filesList.innerHTML=`
@@ -1350,7 +1357,7 @@ const ui = { */ function toggleCardSelection(card, event) { if (window.multiSelect) { - window.multiSelect.handleItemClick(card, event); + window.multiSelect.handleToggleItem(card, event); } else { card.classList.toggle('selected'); } diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/multiSelect.js index 723ab321..d63d6b1f 100644 --- a/static/js/features/files/multiSelect.js +++ b/static/js/features/files/multiSelect.js @@ -9,6 +9,8 @@ // TODO: rename into selection-bar ? // TODO: merge with photo part +// @ts-check + const multiSelect = { /** Currently selected items: Map */ _selected: new Map(), @@ -162,17 +164,12 @@ const multiSelect = { // ── Click handler (shared by grid + list) ─────────────── - handleItemClick(el, event) { + handleToggleItem(el, event) { const items = this._getAllVisibleItems(); const index = items.indexOf(el); const info = this._extractInfo(el); if (!info) return; - const selectorOther = info.type === 'folder' - ? `[data-folder-id="${info.id}"]` - : `[data-file-id="${info.id}"]`; - const otherEl = [...document.querySelectorAll(selectorOther)].find(e => e !== el); - if (event && event.shiftKey && this._lastClickedIndex >= 0 && index >= 0) { const start = Math.min(this._lastClickedIndex, index); const end = Math.max(this._lastClickedIndex, index); @@ -183,16 +180,24 @@ const multiSelect = { const sel = iInfo.type === 'folder' ? `[data-folder-id="${iInfo.id}"]` : `[data-file-id="${iInfo.id}"]`; - document.querySelectorAll(sel).forEach(e => e.classList.add('selected')); + document.querySelectorAll(sel).forEach((e) => { + e.classList.add('selected'); + let checkbox = e.querySelector('input[type="checkbox"]'); + if (checkbox) + checkbox.checked = true; + }); } } } else { const nowSelected = this.toggle(info.id, info.name, info.type, info.parentId); el.classList.toggle('selected', nowSelected); - if (otherEl) otherEl.classList.toggle('selected', nowSelected); + let checkbox = el.querySelector('input[type="checkbox"]'); + if (checkbox) + checkbox.checked = nowSelected; } this._lastClickedIndex = index; this._syncUI(); + this._syncSelectAllCheckbox(); }, // ── Selection bar (replaces list-header when items selected) ──── @@ -481,15 +486,19 @@ const multiSelect = { document.addEventListener('keydown', (e) => { if (e.target.closest('input, textarea, [contenteditable], .rename-dialog, .share-dialog, .confirm-dialog')) return; + const selectAllCheckbox = document.getElementById('select-all-checkbox'); // ctrl+a cmd+a if ((e.ctrlKey || e.metaKey) && e.key === 'a') { - const filesList = document.getElementById('files-list'); - if (filesList && filesList.closest('.files-container')) { - e.preventDefault(); - this.selectAll(); - } + if (selectAllCheckbox) + selectAllCheckbox.checked = true; + this.selectAll(); + e.preventDefault(); + } + if (e.key === 'Escape' && this.hasSelection) { + this.clear(); + if (selectAllCheckbox) + selectAllCheckbox.checked = false; } - if (e.key === 'Escape' && this.hasSelection) this.clear(); if (e.key === 'Delete' && this.hasSelection) this.batchDelete(); }); @@ -503,10 +512,12 @@ const multiSelect = { }, + // FIXME: competition with _ _injectListHeaderCheckbox() { - const cb = document.getElementById('select-all-checkbox'); - if (!cb) return; - cb.addEventListener('change', () => this.toggleAll()); + const self = this; + const selectAllCheckbox = document.getElementById('select-all-checkbox'); + if (!selectAllCheckbox) return; + selectAllCheckbox.addEventListener('change', () => self.toggleAll()); }, }; diff --git a/static/js/features/files/search.js b/static/js/features/files/search.js index 77b7058f..eb75c27d 100644 --- a/static/js/features/files/search.js +++ b/static/js/features/files/search.js @@ -106,18 +106,6 @@ const search = { window.ui.resetFilesList(); // ensure also list visible & error hidden const filesList = document.getElementById('files-list'); - // reset list - filesList.innerHTML = ` -
-
-
Name
-
Type
-
Size
-
Modified
-
-
- `; - // Search results header with query time and sort controls const totalCount = results.total_count || (results.files.length + results.folders.length); const queryTimeText = results.query_time_ms !== undefined diff --git a/static/js/features/library/favorites.js b/static/js/features/library/favorites.js index b3747df6..20405de1 100644 --- a/static/js/features/library/favorites.js +++ b/static/js/features/library/favorites.js @@ -168,18 +168,6 @@ const favorites = { } window.ui.resetFilesList(); // ensure also list visible & error hidden - const filesList = document.getElementById('files-list'); - - filesList.innerHTML = ` -
-
-
Name
-
Type
-
Size
-
Modified
-
- `; - window.ui.updateBreadcrumb(''); if (this._cache.size === 0) { diff --git a/static/js/features/library/recent.js b/static/js/features/library/recent.js index c19b731c..78bf32e3 100644 --- a/static/js/features/library/recent.js +++ b/static/js/features/library/recent.js @@ -100,6 +100,10 @@ const recent = {
`; + if (window.multiSelect) { + window.multiSelect.clear(); + window.multiSelect.init(); // this will wire buttons & select-all-checkbox + } window.ui.updateBreadcrumb(''); if (recentItems.length === 0) {