101 lines
4.5 KiB
JavaScript
101 lines
4.5 KiB
JavaScript
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;
|
|
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] ?
|
|
'<div class="muted">' + deferredQueues[active] + ' is still loading…</div>' : 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 '<article class="my-work-card progressive-my-work-card">' +
|
|
(href ? '<a class="my-work-card-main" href="' + escapeHtml(href) + '">' : '<div class="my-work-card-main">') +
|
|
'<strong>' + title + '</strong><span class="small">' + context + ' · ' + reason + '</span>' +
|
|
(href ? '</a>' : '</div>') + '</article>';
|
|
}).join('') : '<div class="muted">No work in this queue.</div>';
|
|
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 {
|
|
handoff() {
|
|
return { selectedFilter:selectedByUser ? active : null };
|
|
},
|
|
async start() {
|
|
if (status) status.textContent = 'Loading assigned work…';
|
|
try {
|
|
const snapshot = await fetchSnapshot();
|
|
if (stopped) return false;
|
|
const context = snapshot?.context || snapshot || {};
|
|
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;
|