122 lines
4.1 KiB
JavaScript
122 lines
4.1 KiB
JavaScript
/*
|
|
* AdminWindow — floating panel chrome for the admin canvas.
|
|
*
|
|
* A near-verbatim copy of digest-engine/render/digest-canvas-sdk/window-chrome.js
|
|
* (docs/project-plan.md Phase 12), duplicated rather than shared per Phase 13's
|
|
* decision to keep the two canvases fully decoupled. Only the `digest-` class/name
|
|
* prefix has been renamed to `admin-` throughout; the behaviour is identical.
|
|
*
|
|
* 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; there is
|
|
* no 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.
|
|
*/
|
|
|
|
(function (global) {
|
|
'use strict';
|
|
|
|
var counter = 0;
|
|
|
|
function escapeHtml(text) {
|
|
return String(text)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
// Intentionally tiny: bold, italic, inline code and paragraph breaks — 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, '<code>$1</code>')
|
|
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
.replace(/(^|[\s(])\*([^*]+)\*/g, '$1<em>$2</em>');
|
|
}
|
|
|
|
function renderContent(content) {
|
|
var body = document.createElement('div');
|
|
body.className = 'admin-window-body';
|
|
|
|
if (Array.isArray(content)) {
|
|
var list = document.createElement('ul');
|
|
list.className = 'admin-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 stat/chart windows
|
|
// their 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 it.
|
|
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, '<br>');
|
|
body.appendChild(p);
|
|
});
|
|
|
|
return body;
|
|
}
|
|
|
|
var AdminWindow = {
|
|
open: function (options) {
|
|
options = options || {};
|
|
|
|
var container = options.container || document.body;
|
|
|
|
var el = document.createElement('section');
|
|
el.className = 'admin-window';
|
|
el.id = options.id || ('admin-window-' + (++counter));
|
|
if (options.variant) {
|
|
el.classList.add('admin-window-' + options.variant);
|
|
}
|
|
|
|
var bar = document.createElement('header');
|
|
bar.className = 'admin-window-titlebar';
|
|
|
|
var dots = document.createElement('span');
|
|
dots.className = 'admin-window-dots';
|
|
dots.setAttribute('aria-hidden', 'true');
|
|
bar.appendChild(dots);
|
|
|
|
var title = document.createElement('h2');
|
|
title.className = 'admin-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('admin-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.AdminWindow = AdminWindow;
|
|
})(window);
|