diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/core/module-loader.js | 3 | ||||
| -rw-r--r-- | modules/core/module-neural-vfs.js | 6 | ||||
| -rw-r--r-- | modules/core/terminal.js | 2 | ||||
| -rw-r--r-- | modules/ext/floating.js | 90 |
4 files changed, 91 insertions, 10 deletions
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js index 9b8d892..f0d3f4c 100644 --- a/modules/core/module-loader.js +++ b/modules/core/module-loader.js @@ -15,6 +15,7 @@ 'module-fs.js', 'module-ollama.js', 'module-my.js', + 'module-modmgr.js', 'module-searxng.js', 'module-servermon.js', 'module-config.js', @@ -104,7 +105,7 @@ window.__moduleLoaderReload = reloadWithSelection; - const LATEST_VER = '2026-06-08-v4.5.25'; + const LATEST_VER = '2026-06-09-v4.5.27'; const CACHE = '?v=' + LATEST_VER; const stored = localStorage.getItem('webx11_cache_ver'); if (stored !== LATEST_VER) { diff --git a/modules/core/module-neural-vfs.js b/modules/core/module-neural-vfs.js index 59618c8..bdf1ffb 100644 --- a/modules/core/module-neural-vfs.js +++ b/modules/core/module-neural-vfs.js @@ -2,8 +2,10 @@ class NeuralVFSModule { constructor() { this.commands = {}; this.commandsMeta = { - 'nvfs-index': { argType: 'file' }, 'nvfs-resolve': { argType: 'file' }, - 'nvfs-tree': { argType: 'file' }, 'nvfs-alias': { argType: 'file' }, + 'nvfs-index': { argType: 'file' }, 'nvfs-status': { argType: 'none' }, + 'nvfs-resolve': { argType: 'file' }, + 'nvfs-tree': { argType: 'file' }, 'nvfs-config': { argType: 'file' }, + 'nvfs-alias': { argType: 'file' }, 'nvfs-sync': { argType: 'file' }, }; this.terminal = null; diff --git a/modules/core/terminal.js b/modules/core/terminal.js index 3651713..87636d6 100644 --- a/modules/core/terminal.js +++ b/modules/core/terminal.js @@ -10,7 +10,7 @@ this.destructiveCmds = new Set(['delete','del','write','save','overwrite','format','kill','stop','shutdown','reboot']); this.systemCmds = new Set(['reboot','shutdown','format','kill','halt','poweroff']); this.suspiciousFlags = new Set(['--dump','--execute','--eval','--exec','--shell','--script']); - this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list']); + this.whitelist = new Set(['help','clear','ls','cat','pwd','whoami','df','free','top','ps','echo','date','env','history','find','grep','tree','head','tail','wc','sort','du','diff','which','hostname','cal','basename','dirname','yes','seq','cut','uniq','expand','printf','base64','sleep','ll','h','mem','status','health-check','system-health','alias','unalias','modules','listmods','modules-list','nvfs-index','nvfs-status','nvfs-resolve','nvfs-tree','nvfs-config','nvfs-alias','nvfs-sync','floating-add','floating-remove','floating-list','floating-pos','floating-clear','floating-toggle']); // Load persisted overrides from localStorage try { const d = localStorage.getItem('webx11_security_destructive'); if(d){JSON.parse(d).forEach(c=>this.destructiveCmds.add(c));} }catch(e){} try { const s = localStorage.getItem('webx11_security_system'); if(s){JSON.parse(s).forEach(c=>this.systemCmds.add(c));} }catch(e){} diff --git a/modules/ext/floating.js b/modules/ext/floating.js index f59aa29..4c332fc 100644 --- a/modules/ext/floating.js +++ b/modules/ext/floating.js @@ -37,9 +37,14 @@ class FloatingModule { this.commands['floating-toggle'] = (args) => this._cmdToggle(args); // Restore persisted elements at boot — registerCommands() is the only hook called by ModuleManager // but defer to next tick so terminal/fs are ready - setTimeout(() => { - this._restore(); - }, 0); + const doRestore = () => { + if (document.body) { + this._restore(); + } else { + setTimeout(doRestore, 50); + } + }; + setTimeout(doRestore, 0); } // ── Persistence ── @@ -125,10 +130,62 @@ class FloatingModule { node.style.bottom = 'auto'; node.style.right = 'auto'; } + // Attach drag behavior to draggable elements (buttons, menu headers, panel titles) + if (el.type === 'button' || el.type === 'menu' || el.type === 'panel') { + const handle = el.type === 'menu' ? node.querySelector('.floating-menu-header') + : el.type === 'panel' ? node.querySelector('.floating-panel-title') + : node; + if (handle) { + this._makeDraggable(handle, node, id); + } + } container.appendChild(node); } } + _makeDraggable(handle, node, id) { + let startX, startY, startLeft, startTop, dragging = false; + const onStart = (e) => { + const ev = e.touches ? e.touches[0] : e; + dragging = true; + startX = ev.clientX; + startY = ev.clientY; + const rect = node.getBoundingClientRect(); + startLeft = rect.left; + startTop = rect.top; + handle.setPointerCapture(ev.pointerId); + handle.style.cursor = 'grabbing'; + }; + const onMove = (e) => { + if (!dragging) return; + const ev = e.touches ? e.touches[0] : e; + node.style.position = 'fixed'; + node.style.left = (startLeft + (ev.clientX - startX)) + 'px'; + node.style.top = (startTop + (ev.clientY - startY)) + 'px'; + node.style.bottom = 'auto'; + node.style.right = 'auto'; + }; + const onEnd = (e) => { + if (!dragging) return; + dragging = false; + handle.style.cursor = ''; + handle.releasePointerCapture(e.pointerId); + const el = this._elements[id]; + if (el) { + const rect = node.getBoundingClientRect(); + el.x = Math.round(rect.left); + el.y = Math.round(rect.top); + this._save(); + } + }; + handle.addEventListener('pointerdown', onStart); + handle.addEventListener('pointermove', onMove); + handle.addEventListener('pointerup', onEnd); + handle.addEventListener('pointercancel', onEnd); + handle.style.touchAction = 'none'; + handle.style.cursor = 'grab'; + } + _renderElement(el) { switch (el.type) { case 'button': return this._renderButton(el); @@ -191,13 +248,31 @@ class FloatingModule { _renderPanel(el) { const panel = document.createElement('div'); panel.className = 'floating-panel'; + const titleHtml = this._escapeHtml(el.label); + const contentHtml = this._renderMarkdownLite(el.content); panel.innerHTML = ` - <div class="floating-panel-title">${this._escapeHtml(el.label)}</div> - <div class="floating-panel-content">${this._escapeHtml(el.content)}</div> + <div class="floating-panel-title">${titleHtml}</div> + <div class="floating-panel-content">${contentHtml}</div> `; return panel; } + _renderMarkdownLite(str) { + if (!str) return ''; + let html = this._escapeHtml(str); + // **bold** + html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); + // *italic* + html = html.replace(/\*(.+?)\*/g, '<em>$1</em>'); + // `code` + html = html.replace(/`([^`]+)`/g, '<code>$1</code>'); + // [text](url) + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>'); + // newlines to <br> + html = html.replace(/\n/g, '<br>'); + return html; + } + _escapeHtml(str) { if (!str) return ''; return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') @@ -252,7 +327,10 @@ class FloatingModule { const secondColon = btnBuf.indexOf(':', firstColon + 1); if (secondColon === -1) continue; // need more args to find command const prefix = btnBuf.substring(0, firstColon); - if (prefix !== 'btn') { btnBuf = ''; continue; } + if (prefix !== 'btn') { + this.terminal.print(`Warning: skipping "${btnBuf}" — expected "btn:Label:cmd" (got prefix "${prefix}")`, 'warning'); + btnBuf = ''; continue; + } const label = btnBuf.substring(firstColon + 1, secondColon); const command = btnBuf.substring(secondColon + 1); if (label && command) { |