/* * DigestWindow — floating panel chrome for the digest canvas. * * Windows look like windows (title bar, rounded corners, shadow, glow accent) * but are not actually draggable. The canvas is read on a wall-mounted kiosk and * inside a Home Assistant iframe card; neither has a pointer doing window * management, and real drag-and-drop would only add state that nothing persists. * * x/y/w/h are optional. Given, the window is absolutely positioned (percentages * of the canvas) — that is the escape hatch for a deliberately composed layout. * Omitted, the window participates in the canvas's normal flow/grid layout, which * is what render.js does by default so the layout survives any window count the * LLM decides to emit. */ (function (global) { 'use strict'; var counter = 0; function escapeHtml(text) { return String(text) .replace(/&/g, '&') .replace(//g, '>'); } // Intentionally tiny: bold, italic, inline code and paragraph breaks. The // schema calls content "markdown-ish", and pulling in a markdown parser for // three inline forms would violate this project's no-unnecessary-deps rule. function renderInline(text) { return escapeHtml(text) .replace(/`([^`]+)`/g, '$1') .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/(^|[\s(])\*([^*]+)\*/g, '$1$2'); } function renderContent(content) { var body = document.createElement('div'); body.className = 'digest-window-body'; if (Array.isArray(content)) { var list = document.createElement('ul'); list.className = 'digest-window-list'; content.forEach(function (item) { var li = document.createElement('li'); li.innerHTML = renderInline(item); list.appendChild(li); }); body.appendChild(list); return body; } // Duck-typed rather than `instanceof Node`: render.js hands the globe its // mount point through here, and an identity check against a global that may // not exist in every embedding context is a needless way to lose the globe. if (content && typeof content.nodeType === 'number') { body.appendChild(content); return body; } String(content === undefined || content === null ? '' : content) .split(/\n{2,}/) .forEach(function (block) { if (!block.trim()) { return; } var p = document.createElement('p'); p.innerHTML = renderInline(block).replace(/\n/g, '
'); body.appendChild(p); }); return body; } var DigestWindow = { open: function (options) { options = options || {}; var container = options.container || document.body; var el = document.createElement('section'); el.className = 'digest-window'; el.id = options.id || ('digest-window-' + (++counter)); if (options.variant) { el.classList.add('digest-window-' + options.variant); } var bar = document.createElement('header'); bar.className = 'digest-window-titlebar'; var dots = document.createElement('span'); dots.className = 'digest-window-dots'; dots.setAttribute('aria-hidden', 'true'); bar.appendChild(dots); var title = document.createElement('h2'); title.className = 'digest-window-title'; title.textContent = options.title || ''; bar.appendChild(title); el.appendChild(bar); el.appendChild(renderContent(options.content)); if (typeof options.x === 'number' && typeof options.y === 'number') { el.classList.add('digest-window-positioned'); el.style.left = options.x + '%'; el.style.top = options.y + '%'; } if (typeof options.w === 'number') { el.style.width = options.w + '%'; } if (typeof options.h === 'number') { el.style.height = options.h + '%'; } container.appendChild(el); return el; } }; global.DigestWindow = DigestWindow; })(window);