aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/critical-ext-bundle.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/critical-ext-bundle.js
parent82270d6f2685d84d5c9b4a454ae7260de75d34cf (diff)
auto: 2026-06-22_18:00 changes
Diffstat (limited to 'modules/ext/critical-ext-bundle.js')
-rw-r--r--modules/ext/critical-ext-bundle.js183
1 files changed, 178 insertions, 5 deletions
diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js
index 4242ebf..3c5d54b 100644
--- a/modules/ext/critical-ext-bundle.js
+++ b/modules/ext/critical-ext-bundle.js
@@ -26,6 +26,8 @@ class EditorModule {
const modal = document.getElementById('code-editor');
const title = document.getElementById('code-editor-title');
const textarea = document.getElementById('code-editor-textarea');
+ const gutter = document.getElementById('code-editor-gutter');
+ const statusEl = document.getElementById('code-editor-status');
if (!modal || !textarea) return this.terminal.print('Editor modal not found', 'error');
// Read existing file content or use empty for new files
const content = this.terminal.fs.readFile(path);
@@ -36,6 +38,51 @@ class EditorModule {
textarea.value = fileContent;
modal.style.display = 'flex';
textarea.focus();
+ // Editor enhancements: line numbers, Tab, Ctrl+S
+ const updateGutter = () => {
+ const lines = textarea.value.split('\n').length;
+ let html = '';
+ for (let i = 1; i <= lines; i++) html += i + '\n';
+ gutter.textContent = '';
+ gutter.textContent = html;
+ };
+ const syncScroll = () => { gutter.scrollTop = textarea.scrollTop; };
+ const updateStatus = () => {
+ const lines = textarea.value.split('\n').length;
+ const chars = textarea.value.length;
+ statusEl.textContent = `${lines} lines ยท ${chars} chars`;
+ };
+ updateGutter();
+ updateStatus();
+ textarea.onscroll = syncScroll;
+ textarea.oninput = () => { updateGutter(); updateStatus(); };
+ textarea.onkeydown = (e) => {
+ if (e.key === 'Tab') { e.preventDefault(); insertTab(textarea, e.shiftKey); updateGutter(); }
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); saveBtn.click(); }
+ };
+ function insertTab(el, shift) {
+ const s = el.selectionStart, e2 = el.selectionEnd;
+ if (shift) {
+ const before = el.value.substring(0, s);
+ const lineStart = before.lastIndexOf('\n') + 1;
+ if (lineStart < s && el.value.substring(lineStart, lineStart + 1) === '\t') {
+ el.value = el.value.substring(0, lineStart) + el.value.substring(lineStart + 1);
+ el.selectionStart = el.selectionEnd = s - 1;
+ }
+ } else {
+ el.value = el.value.substring(0, s) + '\t' + el.value.substring(e2);
+ el.selectionStart = el.selectionEnd = s + 1;
+ }
+ }
+ // Fullscreen toggle
+ window._editorToggleFullscreen = () => {
+ const content = modal.querySelector('.modal-content');
+ if (content.style.maxWidth === '100vw') {
+ content.style.maxWidth = '90vw'; content.style.maxHeight = '90vh'; content.style.width = '900px'; content.style.height = '80vh';
+ } else {
+ content.style.maxWidth = '100vw'; content.style.maxHeight = '100vh'; content.style.width = '100vw'; content.style.height = '100vh';
+ }
+ };
// Override save button to write back to VFS
const saveBtn = modal.querySelector('.modal-footer .btn:last-child');
const cancelBtn = modal.querySelector('.modal-footer .btn-danger');
@@ -809,6 +856,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;
@@ -1208,6 +1257,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
@@ -1429,17 +1497,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);
@@ -1472,13 +1548,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;