aboutsummaryrefslogtreecommitdiff
path: root/modules/ext
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-09 18:00:26 +0200
committerAgando <roduccomm@gmail.com>2026-06-09 18:00:26 +0200
commite8d4985bbb60797936d31b65604a7f94c76bf623 (patch)
treeadcc96553bd63bd039e7454d2c50f2360ac8496b /modules/ext
parent9f60030e39feecc76f11671a4b9d82303c83670c (diff)
auto: 2026-06-09_18:00 changes
Diffstat (limited to 'modules/ext')
-rw-r--r--modules/ext/floating.js90
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, '&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) {