diff --git a/tests/page-unit.js b/tests/page-unit.js index a89622c..7a4a598 100644 --- a/tests/page-unit.js +++ b/tests/page-unit.js @@ -262,3 +262,14 @@ test('a tap ended over a minute after its last request reports once, without rec run('endTap()'); assert.equal(run(`sent.filter(([u, o]) => JSON.parse(o.body).ev === 'tap').length`), 1); }); + +test('the next page starts after the unread ones on the Unread tab, after all of them elsewhere', () => { + run(`S.entries = [{guid:'a',read:true},{guid:'b',read:false},{guid:'c',read:true}]; S.total = 3`); + run(`S.filter = 'unread'`); + // Two read here are off the server's unread list: starting at 3 would skip two unread ones (#383). + assert.equal(run('nextOffset()'), 1); + assert.equal(run('hasMore()'), true); + run(`S.filter = 'all'`); + assert.equal(run('nextOffset()'), 3); + assert.equal(run('hasMore()'), false); +}); diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index 38768ab..a46ba13 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- Swiping, or j, through more than 50 items no longer stops at the 50th until a reload: the next ones are fetched as you near the end, and Load more on the Unread tab no longer skips as many unread items as you had read (#383). - ipx-web no longer logs a deprecation warning about its metrics setting at every start (#381). - A tap's time in the traces ends with the last answer to what it set off, not with a refresh made seconds later (#379). - Switching away from the page a minute after using it no longer stops it with "Maximum call stack size exceeded" (#378). diff --git a/web/src/items.ts b/web/src/items.ts index 3a7cfec..0f29414 100644 --- a/web/src/items.ts +++ b/web/src/items.ts @@ -90,7 +90,7 @@ async function loadEntries(append?: boolean, quiet?: boolean){ else if(!append&&!quiet) spin=setTimeout(()=>{ if(seq===loadSeq) listLoading(); },120); let r: ListPage; try{ - r=await (!append&&!kept&&fetching.get(key) || api(listUrl(feed,S.filter,S.offset))); + r=await (!append&&!kept&&fetching.get(key) || api(listUrl(feed,S.filter,append?S.offset:0))); }finally{ clearTimeout(spin); } // Only the latest list counts: marking everything read reloads the All tab, and clicking // Unread straight after could have that answer land last and replace the Unread one. @@ -108,7 +108,8 @@ function showList(r: ListPage, append: boolean, asked: number){ const w=readWrites.get(entryKey(e)); if(w && !(w.done!==undefined&&w.done!S.entries.some(x=>entryKey(x)===entryKey(e)))) : r.entries; // A background scan finishing refreshes the list from the server, which -- on the Unread // tab -- would drop the item you have open the moment reading it took it off the filter. // Keep it until you pick a different one; the next refresh after that no longer protects it. @@ -147,10 +148,10 @@ function renderEntries(){ for(const e of S.entries) box.appendChild(epEl(e)); syncPlayButtons(); if(pane) pane.scrollTop=top; - if(S.entries.length < S.total){ + if(hasMore()){ const b=document.createElement('button'); b.className='btn'; b.id='more'; b.textContent=`Load more (${S.entries.length} of ${S.total})`; - b.onclick=()=>{S.offset+=LIMIT;loadEntries(true)}; + b.onclick=()=>{S.offset=nextOffset();loadEntries(true)}; box.appendChild(b); } } @@ -318,6 +319,21 @@ function selectEntry(e: Entry,turning=false){ swapRow(e); showDetail(e); const d=$('#detail'); if(d) d.scrollTop=0; + if(turning) loadAhead(); +} + +/// Where the next page starts. On the Unread tab the items read here are no longer unread on the +/// server, so counting them, as a flat 50 a page did, skipped as many unread ones. +function nextOffset(){ return S.filter==='unread' ? S.entries.filter(e=>!e.read).length : S.entries.length; } +const hasMore=()=>nextOffset()entryKey(x)===S.sel); + if(loadingAhead || S.entries.length-1-i>5 || !hasMore()) return; + loadingAhead=true; S.offset=nextOffset(); + loadEntries(true).catch(()=>{}).finally(()=>{ loadingAhead=false; }); } /// Drag the divider between the item list and the item text.