From f8a04e76ea9aa5156e6e80a7534f1aa5a336429b Mon Sep 17 00:00:00 2001 From: Agando Date: Thu, 28 May 2026 18:01:22 +0200 Subject: auto: 2026-05-28_18:00 changes --- modules/ext/filemgr.js | 169 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 134 insertions(+), 35 deletions(-) (limited to 'modules/ext/filemgr.js') diff --git a/modules/ext/filemgr.js b/modules/ext/filemgr.js index 5532cee..4074f06 100644 --- a/modules/ext/filemgr.js +++ b/modules/ext/filemgr.js @@ -7,6 +7,7 @@ class FileManagerModule { this._selectedFile = null; this._searchFilter = ''; this._rawMode = false; + this._inlineHtml = false; } requirements() { @@ -15,8 +16,8 @@ class FileManagerModule { getHelpInfo() { return { - 'files': 'Open the file manager overlay with VFS directory listing and preview', - 'preview': 'Preview a specific file (usage: preview )', + 'files': 'files — Open the file manager overlay with VFS directory listing, preview, encrypt, zip', + 'preview': 'preview — Preview a specific file', }; } @@ -52,15 +53,16 @@ class FileManagerModule { _detectType(filePath) { const ext = filePath.split('.').pop().toLowerCase(); if (!ext || ext === filePath) return 'other'; - const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - const videoExts = ['mp4', 'webm']; - const audioExts = ['mp3', 'wav', 'ogg']; - const codeExts = ['js', 'py', 'html', 'css', 'yaml', 'yml', 'toml']; + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico']; + const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv']; + const audioExts = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a']; + const codeExts = ['js', 'py', 'css', 'yaml', 'yml', 'toml']; if (ext === 'md') return 'md'; if (imageExts.includes(ext)) return 'image'; if (videoExts.includes(ext)) return 'video'; if (audioExts.includes(ext)) return 'audio'; if (codeExts.includes(ext)) return 'code'; + if (ext === 'html') return 'html'; if (ext === 'json') return 'json'; if (ext === 'csv') return 'csv'; if (ext === 'pdf') return 'pdf'; @@ -113,7 +115,10 @@ class FileManagerModule { const fileName = path.split('/').pop() || path; // Handle binary detection for media types - const isLikelyBinary = typeof content === 'string' && content.length > 0 && !content.startsWith('\n') && !content.startsWith('\r') && !content.startsWith(' ') && !content.startsWith('<') && !content.startsWith('{') && !content.startsWith('[') && content.charCodeAt(0) < 32 && content.charCodeAt(0) !== 9 && content.charCodeAt(0) !== 10 && content.charCodeAt(0) !== 13; + if (typeof content !== 'string') { + return '
Cannot preview: non-string content
'; + } + const isLikelyBinary = content.length > 0 && !content.startsWith('\n') && !content.startsWith('\r') && !content.startsWith(' ') && !content.startsWith('<') && !content.startsWith('{') && !content.startsWith('[') && content.charCodeAt(0) < 32 && content.charCodeAt(0) !== 9 && content.charCodeAt(0) !== 10 && content.charCodeAt(0) !== 13; const isTextContent = typeof content === 'string' && (content.startsWith('{') || content.startsWith('[') || content.startsWith('<') || content.startsWith('#') || /^[\w\s\n\r\t.,;:!?()\-=+*@#$%^&"'\/\\|`~]/.test(content.substring(0, 100))); @@ -121,6 +126,17 @@ class FileManagerModule { return '
' + this._renderMarkdown(content) + '
'; } + if (type === 'html') { + // Render HTML in an inline sandbox frame + const safeHtml = content + .replace(/)<[^<]*)*<\/script>/gi, '') + .replace(/on\w+\s*=\s*"[^"]*"/gi, '') + .replace(/on\w+\s*=\s*'[^']*'/gi, ''); + return '
' + + '
'; + } + if (type === 'text') { const truncated = content.substring(0, 2000); const extra = content.length > 2000 ? '
(truncated to 2000 chars)
' : ''; @@ -158,11 +174,14 @@ class FileManagerModule { } if (type === 'image') { - if (isLikelyBinary && !isTextContent) { - return '
📷 Image — download to view
'; + // VFS stores images as base64 strings — detect and render inline + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {jpg:'image/jpeg',jpeg:'image/jpeg',png:'image/png',gif:'image/gif',webp:'image/webp',bmp:'image/bmp',ico:'image/x-icon'}; + const mime = mimeTypes[ext] || 'image/png'; + return '
' + this._escapeHtml(fileName) + '
'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '
' + this._escapeHtml(fileName) + '
'; + return '
📷 Image — cannot render, file may be stored as binary
'; } if (type === 'svg') { @@ -187,28 +206,31 @@ class FileManagerModule { } if (type === 'video') { - if (isLikelyBinary && !isTextContent) { - return '
📷 Video — download to view
'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp4:'video/mp4',webm:'video/webm',avi:'video/x-msvideo',mov:'video/quicktime',mkv:'video/x-matroska'}; + const mime = mimeTypes[ext] || 'video/mp4'; + return '
'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '
'; + return '
🎬 Video — cannot render
'; } if (type === 'audio') { - if (isLikelyBinary && !isTextContent) { - return '
🎵 Audio — download to play
'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + const mimeTypes = {mp3:'audio/mpeg',wav:'audio/wav',ogg:'audio/ogg',flac:'audio/flac',aac:'audio/aac',m4a:'audio/mp4'}; + const mime = mimeTypes[ext] || 'audio/mpeg'; + return '
'; } - const base64 = btoa(unescape(encodeURIComponent(content))); - return '
'; + return '
🎵 Audio — cannot render
'; } if (type === 'pdf') { - try { - const base64 = btoa(unescape(encodeURIComponent(content))); - return '
'; - } catch (e) { - return '
📄 PDF — download to view
'; + const isBase64 = /^[A-Za-z0-9+/=]+$/.test(content.substring(0, 100)); + if (isBase64) { + return '
'; } + return '
📄 PDF — cannot render
'; } if (type === 'csv') { @@ -285,7 +307,7 @@ class FileManagerModule { const overlay = document.createElement('div'); overlay.id = 'filemgr-overlay'; - overlay.style.cssText = 'position:fixed;inset:0;z-index:1800;display:flex;flex-direction:column;background:var(--bg-color);color:var(--text-color);font-family:var(--font-primary);font-size:14px;'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:1800;display:flex;flex-direction:column;background:var(--bg-color);color:var(--text-color);font-family:var(--font-primary);font-size:14px;height:100vh;'; // Inject responsive styles for the overlay const styleId = 'filemgr-styles'; @@ -318,10 +340,11 @@ class FileManagerModule { border-right: none !important; border-bottom: 1px solid var(--border-color); } - #filemgr-overlay #filemgr-preview { - width: 100% !important; - min-height: 40vh; - } + #filemgr-overlay #filemgr-preview { + width: 100% !important; + flex: 1 !important; + min-height: 0; + } #filemgr-action-bar { flex-wrap: wrap; gap: 6px; @@ -384,12 +407,12 @@ class FileManagerModule { const mainArea = document.createElement('div'); mainArea.id = 'filemgr-main-container'; mainArea.className = 'filemgr-main'; - mainArea.style.cssText = 'overflow:hidden;'; + mainArea.style.cssText = 'flex:1;overflow:hidden;display:flex;min-height:0;'; // Sidebar const sidebar = document.createElement('div'); sidebar.id = 'filemgr-sidebar'; - sidebar.style.cssText = 'width:30%;min-width:200px;overflow-y:auto;background:var(--bg-secondary);border-right:1px solid var(--border-color);'; + sidebar.style.cssText = 'width:25%;min-width:150px;overflow-y:auto;background:var(--bg-secondary);border-right:1px solid var(--border-color);'; mainArea.appendChild(sidebar); @@ -413,6 +436,18 @@ class FileManagerModule { statusSpan.textContent = 'No file selected'; actionBar.appendChild(statusSpan); + const encryptBtn = document.createElement('button'); + encryptBtn.textContent = '🔐 Encrypt'; + encryptBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--warning-color);color:var(--warning-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + encryptBtn.onclick = () => this._actionEncrypt(); + actionBar.appendChild(encryptBtn); + + const compressBtn = document.createElement('button'); + compressBtn.textContent = '📦 Zip'; + compressBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--info-color);color:var(--info-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + compressBtn.onclick = () => this._actionCompress(); + actionBar.appendChild(compressBtn); + const deleteBtn = document.createElement('button'); deleteBtn.textContent = '🗑 Delete'; deleteBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--error-color);color:var(--error-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; @@ -431,6 +466,12 @@ class FileManagerModule { editBtn.onclick = () => this._actionEdit(); actionBar.appendChild(editBtn); + const newFileBtn = document.createElement('button'); + newFileBtn.textContent = '📄 New'; + newFileBtn.style.cssText = 'padding:6px 14px;background:var(--bg-tertiary);border:1px solid var(--success-color);color:var(--success-color);border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9em;min-height:44px;touch-action:manipulation;'; + newFileBtn.onclick = () => this._actionNewFile(); + actionBar.appendChild(newFileBtn); + const previewToggleBtn = document.createElement('button'); previewToggleBtn.id = 'filemgr-toggle-raw'; previewToggleBtn.textContent = '📄 Raw'; @@ -727,9 +768,14 @@ class FileManagerModule { this._updateStatus('No file selected to edit'); return; } - if (typeof webx11 !== 'undefined' && webx11 && webx11.executeCommand) { - webx11.executeCommand('edit ' + this._selectedFile); - this._updateStatus('✏ Opened in editor: ' + this._selectedFile); + const path = this._selectedFile; + // Close file manager first so code editor modal (z-index:1000) is visible above + this._closeOverlay(); + if (typeof webx11 !== 'undefined' && webx11 && webx11.dispatchCommand) { + setTimeout(() => { + webx11.dispatchCommand('edit', [path]); + }, 100); + this._updateStatus('✏ Opened in editor: ' + path); } else { this._updateStatus('❌ Editor not available'); } @@ -748,8 +794,61 @@ class FileManagerModule { this._renderPreview(this._selectedFile, content); } } + + _actionEncrypt() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.enc')) { + this._updateStatus('⚠ Already encrypted (.enc)'); + return; + } + const passphrase = prompt('Enter passphrase for AES-256-GCM encryption:'); + if (!passphrase || passphrase.length < 4) { + this._updateStatus('❌ Passphrase too short (min 4 chars)'); + return; + } + // Delegate to cryptogzip module's encrypt command + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('encrypt ' + this._selectedFile + ' ' + passphrase); + this._updateStatus('🔐 Encrypted: ' + this._selectedFile + '.enc'); + this._refreshFileList(); + } + } + + _actionCompress() { + if (!this._selectedFile) { + this._updateStatus('No file selected'); + return; + } + if (this._selectedFile.endsWith('.gz')) { + this._updateStatus('⚠ Already compressed (.gz)'); + return; + } + if (typeof webx11 !== 'undefined' && webx11.executeCommand) { + webx11.executeCommand('gzip ' + this._selectedFile); + this._updateStatus('📦 Compressed: ' + this._selectedFile + '.gz'); + this._refreshFileList(); + } + } + + _actionNewFile() { + const name = prompt('New file name (e.g. myfile.txt):'); + if (!name) return; + const pathStr = '/' + this.currentPath.join('/'); + const cleanPath = pathStr === '/' ? '/' + name : pathStr + '/' + name; + const content = prompt('Enter file content (optional):') || ''; + const ok = this.terminal.fs.writeFile(cleanPath, content); + if (ok) { + this._updateStatus('✅ Created: ' + cleanPath + ' (' + content.length + ' chars)'); + this._refreshFileList(); + } else { + this._updateStatus('❌ Failed to create file'); + } + } } if (typeof window.webx11 !== 'undefined' && window.webx11) { - webx11.moduleManager.registerModule(new FileManagerModule(), 'https://webx11.duckdns.org/spaces/chatstudio/newos4/modules/ext/filemgr.js'); + webx11.moduleManager.registerModule(new FileManagerModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/filemgr.js'); } -- cgit v1.2.3