+
+
Drag files here or click to select
@@ -255,8 +257,6 @@
Home
-
-
diff --git a/static/js/app/main.js b/static/js/app/main.js
index 8427b411..6f39aab4 100644
--- a/static/js/app/main.js
+++ b/static/js/app/main.js
@@ -105,8 +105,9 @@ function setActionsBarMode(mode, force = false) {
if (!elements.actionsBar) return;
if (mode === 'hidden') {
- elements.actionsBar.style.display = 'none';
+ elements.actionsBar.classList.add("hidden");
elements.actionsBar.dataset.mode = 'hidden';
+ console.log("......setup actions bar to hidden");
return;
}
@@ -118,7 +119,7 @@ function setActionsBarMode(mode, force = false) {
if (!html) return;
elements.actionsBar.innerHTML = html;
- elements.actionsBar.style.display = 'flex';
+ elements.actionsBar.classList.remove("hidden");
elements.actionsBar.dataset.mode = mode;
// Refresh cached action elements after rebuild
@@ -414,7 +415,7 @@ function cacheElements() {
elements.listViewBtn = document.getElementById('list-view-btn');
elements.breadcrumb = document.querySelector('.breadcrumb');
elements.pageTitle = document.querySelector('.page-title');
- elements.actionsBar = document.querySelector('.actions-bar');
+ elements.actionsBar = document.getElementById('actions-bar');
elements.navItems = document.querySelectorAll('.nav-item');
elements.searchInput = document.querySelector('.search-container input');
}
@@ -647,16 +648,6 @@ function setupEventListeners() {
!fileMenu.contains(e.target)) {
ui.closeFileContextMenu();
}
-
- // Deselect all cards when clicking empty area (not on a card, menu, or modal)
- // Note: multiSelect._hookGlobalDeselect() handles clearing the internal
- // selection state; this handler only covers the legacy CSS class removal.
- // Skip if a rubber-band selection just finished — the click is a side-effect.
- if (window.__rubberBandJustFinished) return;
- if (!e.target.closest('.file-card') && !e.target.closest('.file-item') && !e.target.closest('.context-menu') && !e.target.closest('.about-modal') && !e.target.closest('.batch-selection-bar') && !e.target.closest('.list-header.selection-mode')) {
- document.querySelectorAll('.file-card.selected').forEach(c => c.classList.remove('selected'));
- document.querySelectorAll('.file-item.selected').forEach(c => c.classList.remove('selected'));
- }
});
}
diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js
index 3b519658..d1d8e98c 100644
--- a/static/js/app/navigation.js
+++ b/static/js/app/navigation.js
@@ -182,6 +182,8 @@ function switchToSharedSection() {
window.sharedView.init();
window.sharedView.show();
}
+ if (window.multiSelect) window.multiSelect.clear();
+
}
function switchToFilesSection() {
@@ -208,6 +210,7 @@ function switchToFilesSection() {
window.app.currentPath = window.app.userHomeFolderId || '';
window.app.breadcrumbPath = [];
window.ui.updateBreadcrumb();
+ if (window.multiSelect) window.multiSelect.clear();
window.loadFiles();
}
@@ -246,6 +249,9 @@ function switchToFavoritesSection() {
`;
}
}
+
+ if (window.multiSelect) window.multiSelect.clear();
+
}
function switchToRecentFilesSection() {
@@ -282,6 +288,7 @@ function switchToRecentFilesSection() {
`;
}
}
+ if (window.multiSelect) window.multiSelect.clear();
}
function switchToPhotosSection() {
@@ -307,6 +314,8 @@ function switchToPhotosSection() {
if (window.photosView) {
window.photosView.show();
}
+ if (window.multiSelect) window.multiSelect.clear();
+
}
function switchToTrashSection() {
diff --git a/static/js/app/ui.js b/static/js/app/ui.js
index 37bf9a93..894d17ac 100644
--- a/static/js/app/ui.js
+++ b/static/js/app/ui.js
@@ -3,6 +3,8 @@
* This file handles UI-related functions, view toggling, and interface interactions
*/
+// @ts-check
+
// UI Module
const ui = {
/**
@@ -278,6 +280,12 @@ const ui = {
* Set up drag and drop functionality
*/
setupDragAndDrop() {
+ // prepare area to build dragged elements
+ this.dragPreview = document.createElement("div");
+ this.dragPreview.className="drag-preview";
+ document.body.appendChild(this.dragPreview);
+ this.draggedItems = null;
+
const dropzone = document.getElementById('dropzone');
const collectDroppedEntries = async (dataTransfer) => {
@@ -577,11 +585,8 @@ const ui = {
e.preventDefault();
card.classList.remove('drop-target');
- const id = e.dataTransfer.getData('text/plain');
- const isFolder =
- e.dataTransfer.getData('application/oxicloud-folder') === 'true';
-
- await self.move( id, isFolder, targetFolderId);
+ const action = e.dataTransfer?.dropEffect;
+ await self._dropToFolder( action, targetFolderId, e.dataTransfer );
});
} else {
// Last segment: current location, not clickable
@@ -591,32 +596,6 @@ const ui = {
});
},
-
- // TODO: support multiple elements to move ? (API does)
- /**
- * proceed to the drag & drop
- * @param {string} sourceId to move (can be a uniq object or)
- * @param {boolean} sourceIsAFolder
- * @param {string} targetFolderId
- */
- async move(sourceId, sourceIsAFolder, targetFolderId) {
- if (!sourceId) return;
-
- console.log(`request move ${ sourceIsAFolder ? "folder": "file"} ${sourceId} to folder ${targetFolderId}`);
-
- if (sourceIsAFolder) {
- if (sourceId === targetFolderId) {
- alert("You cannot move a folder to itself");
- return;
- }
- // TODO: handle errors...
- await fileOps.moveFolder(sourceId, targetFolderId);
- } else {
- // TODO: handle errors...
- await fileOps.moveFile(sourceId, targetFolderId);
- }
- },
-
/**
* Check if a file can be previewed in the viewer
* @param {Object} file - File object with mime_type property
@@ -742,6 +721,66 @@ const ui = {
}
},
+ /**
+ * handle the drop
+ * @param {string} action copy|move
+ * @param {string} targetFolderId the target
+ * @param {any} dataTransfer fallback if nothing is selected
+ */
+ async _dropToFolder(action, targetFolderId, dataTransfer) {
+ let selection = window.multiSelect.getSelection(targetFolderId);
+
+ window.multiSelect.clear();
+
+ if (selection.fileIds.length == 0 && selection.folderIds.length == 0) {
+
+ // try to use dataTransfer (direct move without selection)
+ const id = dataTransfer.getData('text/plain');
+ const isFolder = dataTransfer.getData('application/oxicloud-folder') === 'true';
+
+ if (isFolder && id === targetFolderId) {
+ console.log("nothing to do");
+ return; //nothing to do
+ }
+ // append current item to selection
+ if (isFolder) {
+ console.log(`adding ${id} as folder`);
+ selection.folderIds.push(id);
+ }
+ else {
+ console.log(`adding ${id} as file`);
+ selection.fileIds.push(id);
+ }
+ }
+
+ console.log(`request ${action} of: `, selection);
+ /*
+ TODO do we prefer use atomic operation on 1 item ? like:
+ await fileOps.moveFolder(sourceId, targetFolderId);
+ await fileOps.moveFile(sourceId, targetFolderId);
+ */
+
+ let result;
+ switch (action) {
+ case "copy":
+ result = await window.fileOps.batchCopy(selection.fileIds, selection.folderIds, targetFolderId);
+ break;
+
+ case "move":
+ result = await window.fileOps.batchMove(selection.fileIds, selection.folderIds, targetFolderId);
+ // redraw directory
+ if (result.success > 0)
+ window.loadFiles();
+ break;
+
+ default:
+ console.error(`drag and drop: action ${action} unknown`);
+ return;
+ }
+ window.multiSelect.showBatchResult(action, result);
+ console.log( result);
+ },
+
_hydrateViewIfNeeded(view) {
// Only hydrate if there is at least one rendered item in the opposite/current DOM.
// This prevents stale cache hydration in empty-state screens.
@@ -755,7 +794,6 @@ const ui = {
this._renderFoldersToView(this._lastFolders, 'grid');
this._renderFilesToView(this._lastFiles, 'grid');
- return;
}
if (view === 'list') {
@@ -787,9 +825,9 @@ const ui = {
const itemInfo = (card) => {
if (!card) return null;
const fileId = card.dataset.fileId;
- if (fileId) return { type: 'file', id: fileId, data: self._items.get(fileId) };
+ if (fileId) return { type: 'file', id: fileId, name: card.dataset.fileName, data: self._items.get(fileId) };
const folderId = card.dataset.folderId;
- if (folderId) return { type: 'folder', id: folderId, data: self._items.get(folderId) };
+ if (folderId) return { type: 'folder', id: folderId, name: card.dataset.folderName, data: self._items.get(folderId) };
return null;
};
@@ -947,31 +985,75 @@ const ui = {
// dragstart
container.addEventListener('dragstart', (e) => {
- const card = e.target.closest(sel);
+ let card = e.target.closest(sel);
if (!card) { e.preventDefault(); return; }
-
- // Grid items must be selected to start dragging
- if (container === grid &&
- !card.classList.contains('selected')) {
- e.preventDefault();
- return;
- }
-
+
const info = itemInfo(card);
if (!info) { e.preventDefault(); return; }
+ if (container === grid) {
+ // pickup the equivalent item in list
+ const selector = (info.type === 'folder') ? "data-folder-id" : "data-file-id";
+ card = list.querySelector(`div.file-item[${selector}="${info.id}"]`);
+ }
+
e.dataTransfer.setData('text/plain', info.id);
- if (info.type === 'folder') {
+ if (info.type === 'folder') {
e.dataTransfer.setData(
'application/oxicloud-folder', 'true');
}
- card.classList.add('dragging');
+ // allow copy or move (handled by the browser)
+ e.dataTransfer.effectAllowed = "copyMove";
+
+ self.draggedItems = document.createElement("div");
+ self.draggedItems.className = "dragged-items";
+
+ // Fplease note that dragged elements are taken from list view (uniformisation)
+ let selectedCardFromList = list.querySelectorAll(`div.selected > div.name-cell`);
+ if (selectedCardFromList.length == 0) {
+ // fallback to current element
+ selectedCardFromList = card.querySelectorAll('div.name-cell');
+ }
+
+ let index = 0;
+ const maxElements = 4;
+ let lastItemDiv = null;
+
+ while (index < selectedCardFromList.length && index < maxElements) {
+ let div = document.createElement("div");
+ div.className="file-item";
+ let clone = selectedCardFromList[index].cloneNode(true);
+ let star = clone.querySelector('.favorite-star-inline');
+ clone.querySelectorAll('img').forEach((img) => { img.loading="eager"; } );
+ if (star) clone.removeChild(star);
+ div.appendChild(clone);
+ self.draggedItems.appendChild( div);
+ index += 1;
+ lastItemDiv = div;
+ }
+
+ // if more than 1 item, display the badge
+ if (selectedCardFromList.length > 1) {
+ let badge = document.createElement("span");
+ badge.className="dragged-items-badge";
+ badge.innerText=selectedCardFromList.length;
+ self.draggedItems.appendChild( badge);
+ }
+
+ // if more than maxElements display the fading
+ if (selectedCardFromList.length > maxElements) {
+ lastItemDiv.classList.add("fading");
+ }
+
+ self.dragPreview.appendChild( self.draggedItems);
+ e.dataTransfer.setDragImage(self.draggedItems, -20, -20);
+
});
// dragend
container.addEventListener('dragend', (e) => {
- const card = e.target.closest(sel);
- if (card) card.classList.remove('dragging');
+
+ self.dragPreview.removeChild( self.draggedItems);
document.querySelectorAll('.drop-target')
.forEach(el => el.classList.remove('drop-target'));
});
@@ -1002,11 +1084,8 @@ const ui = {
e.preventDefault();
card.classList.remove('drop-target');
- const id = e.dataTransfer.getData('text/plain');
- const isFolder =
- e.dataTransfer.getData('application/oxicloud-folder') === 'true';
-
- await self.move( id, isFolder, targetFolderId);
+ const action = e.dataTransfer.dropEffect;
+ await self._dropToFolder( action, targetFolderId, e.dataTransfer);
});
}
},
@@ -1266,7 +1345,8 @@ const ui = {
this._items.set(folder.id, folder);
}
- this._renderFoldersToView(safeFolders, this._getActiveView());
+ this._renderFoldersToView(safeFolders, 'grid');
+ this._renderFoldersToView(safeFolders, 'list');
},
/**
@@ -1282,7 +1362,8 @@ const ui = {
this._items.set(file.id, file);
}
- this._renderFilesToView(safeFiles, this._getActiveView());
+ this._renderFilesToView(safeFiles, 'grid');
+ this._renderFilesToView(safeFiles, 'list');
},
/* ================================================================
@@ -1305,7 +1386,8 @@ const ui = {
this._items.set(folder.id, folder);
this._upsertById(this._lastFolders, folder);
- this._renderFoldersToView([folder], this._getActiveView());
+ this._renderFoldersToView([folder], 'grid');
+ this._renderFoldersToView([folder], 'list');
},
/**
@@ -1324,7 +1406,8 @@ const ui = {
this._items.set(file.id, file);
this._upsertById(this._lastFiles, file);
- this._renderFilesToView([file], this._getActiveView());
+ this._renderFilesToView([file], 'grid');
+ this._renderFilesToView([file], 'list');
}
};
@@ -1393,6 +1476,7 @@ function initRubberBandSelection() {
let active = false;
let startX = 0, startY = 0;
+ const list = document.getElementById('files-list-view');
// We listen on the whole files-container (covers grid + empty space)
const container = document.querySelector('.files-container') || document.getElementById('files-grid');
@@ -1454,9 +1538,16 @@ function initRubberBandSelection() {
if (intersects) {
card.classList.add('selected');
+
// Sync with multiSelect module
if (window.multiSelect) {
const info = window.multiSelect._extractInfo(card);
+
+ // select the equivalent list-item to activate it too
+ let selector = (info.type === 'folder') ? "data-folder-id" : "data-file-id";
+ const fileItem = list?.querySelector(`div.file-item[${selector}="${info.id}"]`);
+ fileItem?.classList.add('selected');
+
if (info) window.multiSelect.select(info.id, info.name, info.type, info.parentId);
}
} else {
@@ -1464,6 +1555,12 @@ function initRubberBandSelection() {
// Deselect from multiSelect module
if (window.multiSelect) {
const info = window.multiSelect._extractInfo(card);
+
+ // select the equivalent list-item to activate it too
+ let selector = (info.type === 'folder') ? "data-folder-id" : "data-file-id";
+ const fileItem = list?.querySelector(`div.file-item[${selector}="${info.id}"]`);
+ fileItem?.classList.remove('selected');
+
if (info) window.multiSelect.deselect(info.id);
}
}
diff --git a/static/js/core/notifications.js b/static/js/core/notifications.js
index 69b723de..117bfb01 100644
--- a/static/js/core/notifications.js
+++ b/static/js/core/notifications.js
@@ -79,7 +79,6 @@ const notifications = (() => {
}
function _renderBadge() {
const badge = $('notif-badge');
- console.log(`badge`, badge);
if (!badge) return;
if (_badgeCount > 0) {
badge.classList.remove("hidden");
diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js
index 095c0e68..d6f57c34 100644
--- a/static/js/features/files/contextMenus.js
+++ b/static/js/features/files/contextMenus.js
@@ -288,40 +288,13 @@ const contextMenus = {
const fileIds = items.filter(i => i.type === 'file').map(i => i.id);
const folderIds = items.filter(i => i.type === 'folder').map(i => i.id);
- let success = 0, errors = 0;
-
- try {
- // Batch copy files
- if (fileIds.length > 0) {
- const res = await fetch('/api/batch/files/copy', {
- method: 'POST',
- headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
- body: JSON.stringify({ file_ids: fileIds, target_folder_id: targetId })
- });
- const data = await res.json();
- success += data.stats?.successful || 0;
- errors += data.stats?.failed || 0;
- }
-
- // Note: Folder copy is not yet implemented in batch API
- if (folderIds.length > 0) {
- window.ui.showNotification('Info', 'Folder copy is not yet supported in batch mode');
- }
- } catch (err) {
- console.error('Batch copy error:', err);
- errors++;
- }
-
+ let result = await window.fileOps.batchCopy( fileIds, folderIds, targetId);
+
this.closeMoveDialog();
window.multiSelect.clear();
window.loadFiles();
- if (errors > 0) {
- window.ui.showNotification('Batch copy', `${success} copied, ${errors} failed`);
- } else {
- window.ui.showNotification('Items copied',
- `${success} item${success !== 1 ? 's' : ''} copied successfully`);
- }
+ window.multiSelect.showBatchResult( "copy", result);
return;
}
@@ -353,48 +326,14 @@ const contextMenus = {
const fileIds = items.filter(i => i.type === 'file').map(i => i.id);
const folderIds = items.filter(i => i.type === 'folder' && i.id !== targetId).map(i => i.id);
-
- let success = 0, errors = 0;
-
- try {
- // Batch move files in a single request
- if (fileIds.length > 0) {
- const res = await fetch('/api/batch/files/move', {
- method: 'POST',
- headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
- body: JSON.stringify({ file_ids: fileIds, target_folder_id: targetId })
- });
- const data = await res.json();
- success += data.stats?.successful || 0;
- errors += data.stats?.failed || 0;
- }
-
- // Batch move folders in a single request
- if (folderIds.length > 0) {
- const res = await fetch('/api/batch/folders/move', {
- method: 'POST',
- headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
- body: JSON.stringify({ folder_ids: folderIds, target_folder_id: targetId })
- });
- const data = await res.json();
- success += data.stats?.successful || 0;
- errors += data.stats?.failed || 0;
- }
- } catch (err) {
- console.error('Batch move error:', err);
- errors++;
- }
-
+
+ let result = await window.fileOps.batchMove( fileIds, folderIds, targetId);
+
this.closeMoveDialog();
window.multiSelect.clear();
window.loadFiles();
-
- if (errors > 0) {
- window.ui.showNotification('Batch move', `${success} moved, ${errors} failed`);
- } else {
- window.ui.showNotification('Items moved',
- `${success} item${success !== 1 ? 's' : ''} moved successfully`);
- }
+ window.multiSelect.showBatchResult( "move", result);
+
return;
}
diff --git a/static/js/features/files/fileOperations.js b/static/js/features/files/fileOperations.js
index dc959a18..13346a1a 100644
--- a/static/js/features/files/fileOperations.js
+++ b/static/js/features/files/fileOperations.js
@@ -771,6 +771,60 @@ const fileOps = {
}
},
+ /**
+ * @typedef {Object} BatchResult
+ * @property {number} success number of files|folders sucessfully updated
+ * @property {number} errors number of files|folders in error
+ * /
+
+ /**
+ * Move files & folders
+ * @param {string[]} fileIds - File IDs
+ * @param {string[]} folderIds - Folder IDs
+ * @param {string} targetFolderId - Target folder ID
+ * @returns {Promise} - Success status
+ */
+ async batchMove(fileIds, folderIds, targetFolderId) {
+
+ // TODO ensure not moving a folder into itself
+ let success = 0, errors = 0;
+
+ try {
+ // Batch move files in a single request
+ if (fileIds.length > 0) {
+ const res = await fetch('/api/batch/files/move', {
+ method: 'POST',
+ headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
+ body: JSON.stringify({ file_ids: fileIds, target_folder_id: targetFolderId })
+ });
+ const data = await res.json();
+ success += data.stats?.successful || 0;
+ errors += data.stats?.failed || 0;
+ }
+
+ // Batch move folders in a single request
+ if (folderIds.length > 0) {
+ const res = await fetch('/api/batch/folders/move', {
+ method: 'POST',
+ headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
+ body: JSON.stringify({ folder_ids: folderIds, target_folder_id: targetFolderId })
+ });
+ const data = await res.json();
+ success += data.stats?.successful || 0;
+ errors += data.stats?.failed || 0;
+ }
+ }
+ catch (err) {
+ console.error('Batch move error:', err);
+ errors++;
+ }
+
+ return {
+ success,
+ errors
+ };
+ },
+
/**
* Copy a file to another folder
* @param {string} fileId - File ID
@@ -828,6 +882,47 @@ const fileOps = {
return false;
},
+ /**
+ * Copy files & folders
+ * @param {string[]} fileIds - File IDs
+ * @param {string[]} folderIds - Folder IDs
+ * @param {string} targetFolderId - Target folder ID
+ * @returns {Promise} - Success status
+ */
+ async batchCopy(fileIds, folderIds, targetFolderId) {
+
+ // FIXME ensure not moving a folder into itself
+
+ let success = 0, errors = 0;
+ try {
+ // Batch copy files
+ if (fileIds.length > 0) {
+ const res = await fetch('/api/batch/files/copy', {
+ method: 'POST',
+ headers: { ...getAuthHeaders(), 'Content-Type': 'application/json' },
+ body: JSON.stringify({ file_ids: fileIds, target_folder_id: targetFolderId })
+ });
+ const data = await res.json();
+ success += data.stats?.successful || 0;
+ errors += data.stats?.failed || 0;
+ }
+
+ // Note: Folder copy is not yet implemented in batch API
+ if (folderIds.length > 0) {
+ window.ui.showNotification('Info', 'Folder copy is not yet supported in batch mode');
+ errors += folderIds.lenngth;
+ }
+ } catch (err) {
+ console.error('Batch copy error:', err);
+ errors++;
+ }
+
+ return {
+ success,
+ errors
+ };
+ },
+
/**
* Rename a file
* @param {string} fileId - File ID
diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/multiSelect.js
index cca75273..4cd111f7 100644
--- a/static/js/features/files/multiSelect.js
+++ b/static/js/features/files/multiSelect.js
@@ -76,6 +76,61 @@ const multiSelect = {
}
},
+ /**
+ * @typedef {Object} ItemSelection
+ * @property {string[]} fileIds list of files' id
+ * @property {string[]} folderIds list of folders' id
+ */
+
+ /**
+ * get selection
+ * @param {string} [targtFolderId] an optional targget (will be removed from selected item)
+ * @return {ItemSelection}
+ */
+ getSelection(targtFolderId) {
+ let fileIds=[];
+ let folderIds=[];
+
+ // TODO optimize & check if _selected is a better use
+ document.querySelectorAll(`div.file-item.selected`).forEach( (item) => {
+ if (item.dataset.fileId) {
+ fileIds.push(item.dataset.fileId);
+ }
+ else {
+ // ignore selectedItem if this is the target
+ if (targtFolderId && targtFolderId !== item.dataset.folderId)
+ folderIds.push(item.dataset.folderId);
+ }
+ });
+
+ return {
+ "fileIds": fileIds,
+ "folderIds": folderIds,
+ };
+ },
+
+ /**
+ * @param {string} action move|copy
+ * @param {BatchResult} result result of batch
+ */
+ showBatchResult(action, result) {
+ if (action === "copy") {
+ if (result.errors > 0) {
+ window.ui.showNotification('Batch copy', `${result.success} copied, ${result.errors} failed`);
+ } else {
+ window.ui.showNotification('Items copied',
+ `${result.success} item${result.success !== 1 ? 's' : ''} copied successfully`);
+ }
+ } else {
+ if (result.errors > 0) {
+ window.ui.showNotification('Batch move', `${result.success} moved, ${result.errors} failed`);
+ } else {
+ window.ui.showNotification('Items moved',
+ `${result.success} item${result.success !== 1 ? 's' : ''} moved successfully`);
+ }
+ }
+ },
+
// ── DOM helpers ─────────────────────────────────────────
_selectElement(el) {
@@ -94,13 +149,6 @@ const multiSelect = {
_getAllVisibleItems() {
return [...document.querySelectorAll('.file-item, .file-card')];
- /*
- const grid = document.getElementById('files-grid');
- if (grid && grid.style.display !== 'none') {
- return [...grid.querySelectorAll('.file-card')];
- }
- return [...document.querySelectorAll('#files-list-view .file-item')];
- */
},
_extractInfo(el) {
@@ -192,6 +240,7 @@ const multiSelect = {
const n = this._selected.size;
const batchSelectionBar = document.getElementById('batch-selection-bar');
+ const actionsBar = document.getElementById('actions-bar');
if (n > 0) {
this._barVisible = true;
@@ -201,14 +250,18 @@ const multiSelect = {
: (this._t('batch.n_selected', { count: n }) || `${n} items selected`);
document.getElementById("batch-bar-count").innerText = countText;
- batchSelectionBar.classList.add('visible');
+ actionsBar.classList.add('hidden');
+ batchSelectionBar.classList.remove('hidden');
} else {
this._barVisible = false;
// Hide grid bar
- batchSelectionBar.classList.remove('visible');
+ batchSelectionBar.classList.add('hidden');
+
+ if (actionsBar.dataset.mode !== "hidden")
+ actionsBar.classList.remove('hidden');
}
// Sync individual item checkboxes
@@ -425,9 +478,6 @@ const multiSelect = {
// Wire the initial select-all checkbox
this._injectListHeaderCheckbox();
- // Global deselect on empty-area click
- this._hookGlobalDeselect();
-
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.closest('input, textarea, [contenteditable], .rename-dialog, .share-dialog, .confirm-dialog')) return;
@@ -459,13 +509,6 @@ const multiSelect = {
cb.addEventListener('change', () => this.toggleAll());
},
- _hookGlobalDeselect() {
- document.addEventListener('click', (e) => {
- if (window.__rubberBandJustFinished) return;
- if (e.target.closest('.file-card, .file-item, .context-menu, .batch-selection-bar, .list-header.selection-mode, .about-modal, .rename-dialog, .share-dialog, .confirm-dialog, .modal-overlay, input, button')) return;
- if (this.hasSelection) this.clear();
- });
- }
};
// Expose globally