diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-22 18:00:43 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-22 18:00:43 +0200 |
| commit | 6f6161562c5f1028256f3163083b665719741646 (patch) | |
| tree | ccc9ab59e8433555557b6db8b6c1d4940b8652a4 /modules/ext/filemgr.js | |
| parent | 82270d6f2685d84d5c9b4a454ae7260de75d34cf (diff) | |
auto: 2026-06-22_18:00 changes
Diffstat (limited to 'modules/ext/filemgr.js')
| -rw-r--r-- | modules/ext/filemgr.js | 136 |
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; |