(function (root, factory) { const exports = factory(); if (typeof module === 'object' && module.exports) { module.exports = exports.createFollowing; module.exports.attachFollowing = exports.attachFollowing; } else { root.createFollowing = exports.createFollowing; root.attachFollowing = exports.attachFollowing; } })(typeof self !== 'undefined' ? self : this, function () { function createFollowing(options) { let generation = 0; let snapshot = {revision:0, items:[]}; function publish(status, error) { const state = {status, revision:snapshot.revision, items:[...snapshot.items]}; if (error) state.error = error; options.render?.(state); if (status === 'ready') options.onCount?.(snapshot.items.length); return state; } async function load() { const requestGeneration = ++generation; publish('loading'); try { const result = await options.fetchJson('api/v1/following', {headers:{Accept:'application/json'}}); if (requestGeneration !== generation) return snapshot; snapshot = { revision:Number(result?.revision) || 0, items:Array.isArray(result?.items) ? result.items.slice(0, 50) : [], }; publish('ready'); return snapshot; } catch (error) { if (requestGeneration === generation) publish('error', error); throw error; } } function open(index) { const item = snapshot.items[Number(index)]; if (!item) return false; Promise.resolve(options.onOpen?.({...item, kind:'issue'})).catch(() => {}); return true; } return {load, open, count:() => snapshot.items.length}; } function attachFollowing(onOpen) { const document = globalThis.document; const query = selector => document.querySelector(selector); const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, character => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[character]); const formatTime = value => new Date(value).toLocaleString(); const fetchJson = async (url, options) => { const response = await fetch(url, options); const payload = await response.json().catch(() => ({})); if (!response.ok) throw new Error(payload.detail || payload.error || 'Following is temporarily unavailable.'); return payload; }; let feature; function render(state) { const list = query('#following-list'); const status = query('#following-status'); query('#retry-following').hidden = state.status !== 'error'; if (state.status === 'loading') return void (status.textContent = 'Loading watched issues…'); if (state.status === 'error') return void (status.textContent = state.error?.message || 'Following is temporarily unavailable.'); status.textContent = state.items.length ? state.items.length + (state.items.length === 1 ? ' watched issue.' : ' watched issues.') : 'No watched issues yet. Watch one from Search to keep it here.'; list.innerHTML = state.items.map((item, index) => '').join(''); list.querySelectorAll('[data-following-index]').forEach(button => button.addEventListener('click', () => { query('#following-sheet').close(); feature.open(Number(button.dataset.followingIndex)); })); } feature = createFollowing({ fetchJson, render, onCount:count => { const value = query('[data-mobile-queue-count="following"]'); value.textContent = count; value.closest('button').setAttribute('aria-label', 'Following, ' + count + (count === 1 ? ' watched issue' : ' watched issues')); }, onOpen, }); query('#close-following').addEventListener('click', () => query('#following-sheet').close()); query('#retry-following').addEventListener('click', () => feature.load().catch(() => {})); feature.load().catch(() => {}); return { load:feature.load, open() { const sheet = query('#following-sheet'); if (!sheet.open) sheet.showModal(); feature.load().catch(() => {}); return 'opened-following'; }, }; } return {createFollowing, attachFollowing}; });