diff options
Diffstat (limited to 'modules/ext')
| -rw-r--r-- | modules/ext/floating.js | 90 |
1 files changed, 84 insertions, 6 deletions
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) { |