aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/filemgr.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-22 18:00:43 +0200
committerAgando <roduccomm@gmail.com>2026-06-22 18:00:43 +0200
commit6f6161562c5f1028256f3163083b665719741646 (patch)
treeccc9ab59e8433555557b6db8b6c1d4940b8652a4 /modules/ext/filemgr.js
parent82270d6f2685d84d5c9b4a454ae7260de75d34cf (diff)
auto: 2026-06-22_18:00 changes
Diffstat (limited to 'modules/ext/filemgr.js')
-rw-r--r--modules/ext/filemgr.js136
1 files changed, 131 insertions, 5 deletions
diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js
index 141bee4..acbb963 100644
--- a/modules/ext/filemgr.js
+++ b/modules/ext/filemgr.js
@@ -8,6 +8,8 @@ class FileManagerModule {
this.currentPath = [];
this._overlay = null;
this._selectedFile = null;
+ this._multiSelect = new Set(); // v4.5.78: multi-select for batch ops
+ this._clipboard = { mode: null, paths: [] }; // 'copy' or 'cut'
this._searchFilter = '';
this._rawMode = false;
this._inlineHtml = false;
@@ -407,6 +409,25 @@ class FileManagerModule {
};
toolbar.appendChild(searchInput);
+ // v4.5.78: Batch operation buttons
+ const batchBar = document.createElement('div');
+ batchBar.style.cssText = 'display:flex;align-items:center;gap:4px;flex-wrap:wrap;';
+ const mkBtn = (label, title, action) => {
+ const b = document.createElement('button');
+ b.textContent = label;
+ b.title = title;
+ b.style.cssText = 'background:none;border:1px solid var(--border-color);color:var(--text-color);cursor:pointer;padding:4px 8px;border-radius:4px;font-family:inherit;font-size:0.85em;min-height:32px;touch-action:manipulation;';
+ b.onclick = () => action();
+ return b;
+ };
+ batchBar.appendChild(mkBtn('📋 Copy', 'Copy selected files', () => this._batchCopy()));
+ batchBar.appendChild(mkBtn('✂️ Cut', 'Cut selected files', () => this._batchCut()));
+ batchBar.appendChild(mkBtn('📥 Paste', 'Paste from clipboard', () => this._batchPaste()));
+ batchBar.appendChild(mkBtn('🗑️ Del', 'Delete selected files', () => this._batchDelete()));
+ batchBar.appendChild(mkBtn('📝 Rename', 'Rename selected file', () => this._batchRename()));
+ batchBar.appendChild(mkBtn('☐ All', 'Toggle select all', () => this._toggleSelectAll()));
+ toolbar.appendChild(batchBar);
+
overlay.appendChild(toolbar);
// Main area — wraps sidebar + preview in a responsive flex container
@@ -628,17 +649,25 @@ class FileManagerModule {
const item = document.createElement('div');
const fullPath = pathStr === '/' ? '/' + entry.name : pathStr + '/' + entry.name;
const isSelected = this._selectedFile === fullPath;
+ const isMulti = this._multiSelect.has(fullPath);
item.dataset.path = fullPath;
item.dataset.type = 'file';
- item.dataset.selected = isSelected ? 'true' : 'false';
- const bgColor = isSelected ? 'var(--bg-tertiary)' : 'transparent';
+ item.dataset.selected = (isSelected || isMulti) ? 'true' : 'false';
+ const bgColor = (isSelected || isMulti) ? 'var(--bg-tertiary)' : 'transparent';
item.style.cssText = 'display:flex;align-items:center;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--border-color);gap:6px;background:' + bgColor + ';';
- if (isSelected) {
+ if (isSelected || isMulti) {
item.style.borderLeft = '3px solid var(--accent-color)';
}
item.onmouseenter = () => { if (!item.dataset.selected) item.style.background = 'var(--bg-tertiary)'; };
item.onmouseleave = () => { if (!item.dataset.selected) item.style.background = 'transparent'; };
- item.onclick = () => {
+ item.onclick = (e) => {
+ // v4.5.78: Ctrl+click or checkbox for multi-select
+ if (e.ctrlKey || e.metaKey) {
+ if (this._multiSelect.has(fullPath)) this._multiSelect.delete(fullPath);
+ else this._multiSelect.add(fullPath);
+ this._refreshFileList();
+ return;
+ }
this._selectedFile = fullPath;
this._highlightSelected();
const content = this.terminal.fs.readFile(fullPath);
@@ -671,13 +700,110 @@ class FileManagerModule {
if (!sidebar) return;
const items = sidebar.querySelectorAll('[data-type="file"]');
items.forEach(item => {
- const selected = item.dataset.path === this._selectedFile;
+ const selected = item.dataset.path === this._selectedFile || this._multiSelect.has(item.dataset.path);
item.dataset.selected = selected ? 'true' : 'false';
item.style.background = selected ? 'var(--bg-tertiary)' : 'transparent';
item.style.borderLeft = selected ? '3px solid var(--accent-color)' : '3px solid transparent';
});
}
+ // v4.5.78: Batch operations
+ _getSelectedPaths() {
+ const paths = [...this._multiSelect];
+ if (this._selectedFile && !paths.includes(this._selectedFile)) paths.push(this._selectedFile);
+ return paths;
+ }
+
+ _batchCopy() {
+ const paths = this._getSelectedPaths();
+ if (!paths.length) { this._updateStatus('No files selected'); return; }
+ this._clipboard = { mode: 'copy', paths: [...paths] };
+ this._multiSelect.clear();
+ this._updateStatus('📋 Copied ' + paths.length + ' item(s)');
+ }
+
+ _batchCut() {
+ const paths = this._getSelectedPaths();
+ if (!paths.length) { this._updateStatus('No files selected'); return; }
+ this._clipboard = { mode: 'cut', paths: [...paths] };
+ this._multiSelect.clear();
+ this._updateStatus('✂️ Cut ' + paths.length + ' item(s)');
+ }
+
+ _batchPaste() {
+ if (!this._clipboard.paths.length) { this._updateStatus('Clipboard empty'); return; }
+ const dest = '/' + this.currentPath.join('/');
+ let count = 0;
+ this._clipboard.paths.forEach(src => {
+ const name = src.split('/').pop();
+ const destPath = dest === '/' ? '/' + name : dest + '/' + name;
+ try {
+ if (this._clipboard.mode === 'copy') {
+ const content = this.terminal.fs.readFile(src);
+ if (content !== null) { this.terminal.fs.writeFile(destPath, content); count++; }
+ } else if (this._clipboard.mode === 'cut') {
+ const content = this.terminal.fs.readFile(src);
+ if (content !== null) {
+ this.terminal.fs.writeFile(destPath, content);
+ this.terminal.fs.deleteFile(src);
+ count++;
+ }
+ }
+ } catch (e) { /* skip errors */ }
+ });
+ if (this._clipboard.mode === 'cut') this._clipboard = { mode: null, paths: [] };
+ this._multiSelect.clear();
+ this._refreshFileList();
+ this._updateStatus('📥 Pasted ' + count + ' item(s)');
+ }
+
+ _batchDelete() {
+ const paths = this._getSelectedPaths();
+ if (!paths.length) { this._updateStatus('No files selected'); return; }
+ if (!confirm('Delete ' + paths.length + ' file(s)?')) return;
+ let count = 0;
+ paths.forEach(p => {
+ try { this.terminal.fs.deleteFile(p); count++; } catch (e) { /* skip */ }
+ });
+ this._multiSelect.clear();
+ this._selectedFile = null;
+ this._refreshFileList();
+ this._updateStatus('🗑️ Deleted ' + count + ' file(s)');
+ }
+
+ _batchRename() {
+ const path = this._selectedFile || (this._multiSelect.size === 1 ? [...this._multiSelect][0] : null);
+ if (!path) { this._updateStatus('Select one file to rename'); return; }
+ const oldName = path.split('/').pop();
+ const newName = prompt('Rename to:', oldName);
+ if (!newName || newName === oldName) return;
+ const dir = path.substring(0, path.lastIndexOf('/')) || '/';
+ const newPath = dir === '/' ? '/' + newName : dir + '/' + newName;
+ try {
+ const content = this.terminal.fs.readFile(path);
+ if (content === null) { this._updateStatus('Cannot read file'); return; }
+ this.terminal.fs.writeFile(newPath, content);
+ this.terminal.fs.deleteFile(path);
+ this._selectedFile = newPath;
+ this._multiSelect.clear();
+ this._refreshFileList();
+ this._updateStatus('📝 Renamed to ' + newName);
+ } catch (e) { this._updateStatus('Rename failed: ' + e.message); }
+ }
+
+ _toggleSelectAll() {
+ const sidebar = document.getElementById('filemgr-sidebar');
+ if (!sidebar) return;
+ const fileItems = sidebar.querySelectorAll('[data-type="file"]');
+ if (this._multiSelect.size >= fileItems.length) {
+ this._multiSelect.clear();
+ } else {
+ fileItems.forEach(item => this._multiSelect.add(item.dataset.path));
+ }
+ this._refreshFileList();
+ this._updateStatus(this._multiSelect.size + ' selected');
+ }
+
_renderPreview(path, content) {
const preview = document.getElementById('filemgr-preview');
if (!preview) return;