aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/core/module-loader.js3
-rw-r--r--modules/core/module-neural-vfs.js6
-rw-r--r--modules/core/terminal.js2
-rw-r--r--modules/ext/floating.js90
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
@@ -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) {