(function (root, factory) { const createSearchPreview = factory(); if (typeof module === 'object' && module.exports) module.exports = createSearchPreview; if (root) { root.createSearchPreview = createSearchPreview; root.safeSearchUrl = value => { try { const url = new URL(value); return ['http:', 'https:'].includes(url.protocol) ? url.href : ''; } catch (_) { return ''; } }; } })(typeof globalThis !== 'undefined' ? globalThis : this, function () { return function createSearchPreview({ fetchJson, mutate, share, session, getSession, loadMore, onNavigate, navigationRoot, onState }) { if (Array.isArray(session)) { getSession = session[0]; loadMore = () => session[1].loadMore(); onNavigate = session[2]; navigationRoot = document; } let generation = 0; let current = null; let mutationRequest = null; let shareRequest = null; let moveRequest = null; function sameItem(left, right) { return left && right && left.kind === right.kind && left.repository === right.repository && Number(left.number) === Number(right.number); } function navigation(item) { const session = typeof getSession === 'function' ? getSession() : null; const items = Array.isArray(session?.items) ? session.items : []; const index = items.findIndex(candidate => sameItem(candidate, item)); if (index < 0) return null; return { position:index + 1, total:items.length, hasPrevious:index > 0, hasNext:index < items.length - 1 || session.more === true, }; } function publish(state) { const position = navigation(state.item || current); if (navigationRoot) { const bar = navigationRoot.querySelector('.search-preview-navigation'); bar.hidden = !position; if (position) { navigationRoot.querySelector('#search-preview-position').textContent = position.position + ' of ' + position.total; navigationRoot.querySelector('#previous-search-result').disabled = !position.hasPrevious; navigationRoot.querySelector('#next-search-result').disabled = !position.hasNext; } } onState(position ? { ...state, navigation:position } : state); if (state.complete && navigationRoot) { navigationRoot.querySelector('#search-preview-status').textContent = 'Search pass complete.'; } } function run(action, pending, success, detail) { if (mutationRequest) return mutationRequest; publish({ status: pending, item: current, detail }); mutationRequest = mutate(detail, action).then(result => { publish({ status: success, item: current, detail, result }); return result; }).catch(error => { publish({ status: 'ready', item: current, detail, error }); throw error; }).finally(() => { mutationRequest = null; }); return mutationRequest; } const api = { open(item) { generation += 1; const requestGeneration = generation; current = { ...item }; publish({ status: 'loading', item: current }); return fetchJson(current).then(detail => { if (requestGeneration === generation) { current = { ...current, ...detail }; publish({ status: 'ready', item: current, detail }); } return detail; }).catch(error => { if (requestGeneration === generation) { publish({ status: 'error', item: current, error }); } throw error; }); }, close() { generation += 1; current = null; onState({ status: 'closed' }); }, previous() { const session = typeof getSession === 'function' ? getSession() : null; const items = Array.isArray(session?.items) ? session.items : []; const index = items.findIndex(item => sameItem(item, current)); if (index <= 0) return Promise.resolve(current); const item = items[index - 1]; if (typeof onNavigate === 'function') onNavigate(item); return api.open(item); }, next() { if (moveRequest) return moveRequest; moveRequest = (async () => { let session = typeof getSession === 'function' ? getSession() : null; let items = Array.isArray(session?.items) ? session.items : []; let index = items.findIndex(item => sameItem(item, current)); if (index < 0) return current; if (index === items.length - 1 && session?.more === true && typeof loadMore === 'function') { await loadMore(); session = getSession(); items = Array.isArray(session?.items) ? session.items : []; index = items.findIndex(item => sameItem(item, current)); } if (index >= 0 && index < items.length - 1) { const item = items[index + 1]; if (typeof onNavigate === 'function') onNavigate(item); return api.open(item); } publish({ status:'ready', item:current, detail:current, complete:true }); return current; })().finally(() => { moveRequest = null; }); return moveRequest; }, share(url) { if (shareRequest) return shareRequest; if (!current || typeof share !== 'function') return Promise.reject(new Error('Sharing is unavailable.')); const detail = current; publish({ status:'sharing', item:current, detail }); shareRequest = share(url).then(result => { publish({ status:result, item:current, detail }); return result; }).catch(error => { publish({ status:error?.name === 'AbortError' ? 'share-canceled' : 'share-error', item:current, detail, error }); throw error; }).finally(() => { shareRequest = null; }); return shareRequest; }, claim(detail) { return run('claim', 'claiming', 'claimed', detail); }, reopen(detail) { return run('reopen', 'reopening', 'reopened', detail); }, }; if (navigationRoot) { navigationRoot.querySelector('#previous-search-result').addEventListener('click', () => api.previous().catch(() => {})); navigationRoot.querySelector('#next-search-result').addEventListener('click', () => api.next().catch(() => {})); } return api; }; });