function createProgressiveMyWork({ document, fetchSnapshot }) { const list = document.querySelector('#my-work-list'); const status = document.querySelector('#my-work-status'); const filters = Array.from(document.querySelectorAll('[data-work-filter]')); const listeners = []; let items = []; let active = 'all'; let stopped = false; let selectedByUser = false; let liveSnapshot = null; let liveSnapshotPromise = null; let confirmedLogin = ''; const deferredQueues = { today:'Today', agenda:'Agenda', later:'Later', draft:'Drafts', }; const escapeHtml = value => String(value || '').replace(/[&<>"']/g, character => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''', })[character]); const safeUrl = value => { try { const url = new URL(String(value || ''), globalThis.location?.href || 'https://invalid.example/'); return ['http:', 'https:'].includes(url.protocol) ? url.href : ''; } catch (_error) { return ''; } }; const matches = (item, filter) => filter === 'review' ? item.is_review : filter === 'update' ? item.has_update : filter === 'attention' ? item.needs_attention : filter === 'filed' ? item.is_filed : filter === 'authored' ? item.is_authored : filter === 'pull' ? item.kind === 'pull' && !item.is_review : item.kind === filter; const visibleItems = () => active === 'all' ? items : items.filter(item => matches(item, active)); const updateCounts = () => { ['all','attention','filed','authored','issue','pull','review','update'].forEach(filter => { const count = filter === 'all' ? items.length : items.filter(item => matches(item, filter)).length; const element = document.querySelector('[data-work-count="' + filter + '"]'); if (element) element.textContent = String(count); }); }; const render = () => { if (stopped || !list) return; const visible = visibleItems(); list.innerHTML = deferredQueues[active] ? '
' + deferredQueues[active] + ' is still loading…
' : visible.length ? visible.map(item => { const href = safeUrl(item.url); const title = escapeHtml(item.title || item.key || 'Untitled work'); const context = escapeHtml(item.key || ''); const reason = escapeHtml(item.reason || 'Assigned to you'); return '
' + (href ? '' : '
') + '' + title + '' + context + ' · ' + reason + '' + (href ? '' : '
') + '
'; }).join('') : '
No work in this queue.
'; filters.forEach(button => button.setAttribute('aria-pressed', String(button.dataset.workFilter === active))); }; filters.forEach(button => { const listener = () => { active = button.dataset.workFilter || 'all'; selectedByUser = true; render(); }; button.addEventListener('click', listener); listeners.push([button, listener]); }); return { login() { return confirmedLogin; }, handoff() { const state = { selectedFilter:selectedByUser ? active : null }; if (liveSnapshot) { state.liveSnapshot = liveSnapshot; liveSnapshot = null; liveSnapshotPromise = null; } else if (liveSnapshotPromise) { state.liveSnapshotPromise = liveSnapshotPromise; liveSnapshotPromise = null; } return state; }, async start() { if (status) status.textContent = 'Loading assigned work…'; const request = Promise.resolve().then(() => fetchSnapshot()); liveSnapshotPromise = request.then(snapshot => ( snapshot && typeof snapshot === 'object' && Object.prototype.hasOwnProperty.call(snapshot, 'context') ? snapshot : null ), () => null); try { const snapshot = await request; if (stopped) return false; const transferable = snapshot && typeof snapshot === 'object' && Object.prototype.hasOwnProperty.call(snapshot, 'context'); if (transferable) liveSnapshot = snapshot; else liveSnapshotPromise = null; const context = snapshot?.context || snapshot || {}; confirmedLogin = String(context.user?.login || '').trim(); items = buildMyWork({ ...context, notifications:snapshot?.notifications || context.notifications || [] }); updateCounts(); render(); const assigned = items.filter(item => item.is_assigned).length; if (status) status.textContent = assigned + ' assigned work item' + (assigned === 1 ? '' : 's') + ' ready.'; return true; } catch (_error) { if (status && !stopped) status.textContent = 'Assigned work is reconnecting…'; return false; } }, stop() { stopped = true; listeners.forEach(([button, listener]) => button.removeEventListener?.('click', listener)); }, }; } if (typeof window !== 'undefined' && typeof document !== 'undefined') { window.stackchainProgressiveMyWork = createProgressiveMyWork({ document, fetchSnapshot: async () => { const response = await fetch('api/v1/live', { headers:{Accept:'application/json'} }); if (!response.ok) throw new Error('HTTP ' + response.status); return response.json(); }, }); void window.stackchainProgressiveMyWork.start(); } if (typeof module !== 'undefined' && module.exports) module.exports = createProgressiveMyWork;