+
Live
-
-
-
-
-
-
-
+ }
+
+ function escapeHtml(s) { return String(s || '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"','\'':'''}[c])); }
+ function escAttr(s) { return escapeHtml(s); }
+
+ function openModal(id) { qs('#' + id).classList.add('open'); }
+ function closeModal(id) { qs('#' + id).classList.remove('open'); }
+
+ /* Whiteboard */
+ function initWhiteboard() {
+ const canvas = qs('#wb');
+ const ctx = canvas.getContext('2d');
+ let drawing = false;
+ function resize() {
+ const dpr = window.devicePixelRatio || 1;
+ const r = canvas.getBoundingClientRect();
+ canvas.width = r.width * dpr;
+ canvas.height = r.height * dpr;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = '#111';
+ ctx.lineWidth = 2;
+ }
+ resize();
+ window.addEventListener('resize', resize);
+ const p = (e) => {
+ const r = canvas.getBoundingClientRect();
+ return { x: e.clientX - r.left, y: e.clientY - r.top };
+ };
+ canvas.addEventListener('pointerdown', (e) => { drawing = true; const pos = p(e); ctx.beginPath(); ctx.moveTo(pos.x, pos.y); });
+ canvas.addEventListener('pointermove', (e) => { if (!drawing) return; const pos = p(e); ctx.lineTo(pos.x, pos.y); ctx.stroke(); });
+ canvas.addEventListener('pointerup', () => { drawing = false; });
+ canvas.addEventListener('pointerleave', () => { drawing = false; });
+ qs('#wb-clear').addEventListener('click', () => { const r = canvas.getBoundingClientRect(); ctx.clearRect(0, 0, r.width, r.height); });
+ qs('#wb-save').addEventListener('click', () => { const a = document.createElement('a'); a.href = canvas.toDataURL(); a.download = 'whiteboard.png'; a.click(); });
+ }
+ initWhiteboard();
+
+ /* Markdown widget */
+ qs('#close-markdown').addEventListener('click', () => closeModal('markdown-modal'));
+ qs('#md-input').addEventListener('input', () => renderMarkdown());
+ function renderMarkdown() {
+ const raw = qs('#md-input').value || '';
+ const html = raw
+ .replace(/^#{1,6}\s.*$/gm, (m) => { const lvl = m.match(/^(#{1,6})/)[1].length; return '
' + m.replace(/^#{1,6}\s/, '') + ''; })
+ .replace(/\*\*(.+?)\*\*/g, '
$1')
+ .replace(/\*(.+?)\*/g, '
$1')
+ .replace(/`([^`]+)`/g, '
$1')
+ .replace(/^-\s?(.+)$/gm, '
$1')
+ .replace(/(
.*<\/li>)/s, '')
+ .replace(/\n/g, '
');
+ qs('#md-preview').innerHTML = '' + escapeHtml(raw) + '
' + html + '
';
+ }
+
+ /* Command palette / widgets */
+ const commands = [
+ { name: 'Open whiteboard', run: () => { openModal('whiteboard-modal'); initWhiteboard(); resizeWhiteboard(); } },
+ { name: 'Open markdown widget', run: () => { openModal('markdown-modal'); renderMarkdown(); } },
+ { name: 'Focus issues', run: () => qs('#issues').scrollIntoView({ behavior: 'smooth', block: 'start' }) },
+ { name: 'Focus PRs', run: () => qs('#prs').scrollIntoView({ behavior: 'smooth', block: 'start' }) },
+ { name: 'Focus AI', run: () => qs('#right').scrollIntoView({ behavior: 'smooth', block: 'start' }) },
+ { name: 'Refresh now', run: load },
+ ];
+ function resizeWhiteboard(){ const canvas = qs('#wb'); if(!canvas) return; const r = canvas.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; canvas.width = r.width * dpr; canvas.height = r.height * dpr; canvas.getContext('2d').setTransform(dpr, 0, 0, dpr, 0, 0); canvas.getContext('2d').lineCap = 'round'; canvas.getContext('2d').lineJoin = 'round'; }
+ function renderCommands(filter) {
+ const el = qs('#cmd-results');
+ const term = String(filter || '').toLowerCase();
+ const items = commands.filter(c => c.name.toLowerCase().includes(term));
+ el.innerHTML = items.map((c, idx) => '' + escapeHtml(c.name) + '
').join('');
+ el.querySelectorAll('.cmd-item').forEach((item) => {
+ item.addEventListener('click', () => { commands[Number(item.dataset.idx)].run(); qs('#cmd-palette').classList.remove('open'); qs('#cmd-input').value = ''; });
+ });
+ }
+ qs('#open-palette').addEventListener('click', () => { qs('#cmd-palette').classList.add('open'); qs('#cmd-input').focus(); renderCommands(''); });
+ qs('#cmd-input').addEventListener('input', (e) => renderCommands(e.target.value));
+ document.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); qs('#cmd-palette').classList.toggle('open'); if (qs('#cmd-palette').classList.contains('open')) { qs('#cmd-input').focus(); renderCommands(''); } } });
+ qs('#close-whiteboard').addEventListener('click', () => closeModal('whiteboard-modal'));
+
+ qs('#refresh').addEventListener('click', load);
+ load();
+ setInterval(load, 8000);
+
+ /* Live widgets */
+ const widgetRegistry = {
+ markdown: () => ({ title: 'Markdown', html: 'Launches a tiny markdown scratchpad.
' }),
+ countdown: () => ({ title: 'Live clock', html: '—
' }),
+ repoAtAGlance: () => ({ title: 'Repo mix', html: 'Loading…
' }),
+ };
+ const widgetOrder = ['countdown', 'repoAtAGlance'];
+ const widgetsEl = qs('#widgets');
+ function paintWidgets() {
+ widgetsEl.innerHTML = widgetOrder.map(k => {
+ const w = widgetRegistry[k]();
+ return '' + escapeHtml(w.title) + '
' + w.html + '';
+ }).join('');
+ qs('#widget-markdown-launch')?.addEventListener('click', () => { openModal('markdown-modal'); renderMarkdown(); });
+ }
+ function tickWidgets() {
+ const clock = qs('#widget-clock');
+ if (clock) clock.textContent = fmt(new Date());
+ }
+ paintWidgets();
+ setInterval(tickWidgets, 1000);
+
+ window.repoAtAGlance = async function() {
+ const el = qs('#repo-mix');
+ if (!el) return;
+ try {
+ const res = await fetch('/api/v1/context', { headers: { Accept: 'application/json' } });
+ const data = await res.json();
+ const repos = data.repos || [];
+ const issues = data.issues || [];
+ const prs = data.pull_requests || [];
+ el.innerHTML = 'Repos
' + repos.length + '
' +
+ '
Open issues
' + issues.filter(i => i.state === 'open').length + '
' +
+ '
Open PRs
' + prs.length + '
';
+ } catch (e) {
+ el.innerHTML = 'Widget unavailable.
';
+ }
+ };
+ setInterval(window.repoAtAGlance, 8000);
+ window.repoAtAGlance();
+})();
+