Page type-check: strict throughout, a typed $, null checks (#236)
$<T> returns the element type it is given and assumes the element is there: 122 lookups take their type from the tag their id is on in the markup. A handler reads its target through a cast, since a typed element will not take a handler whose event names a narrower target. strictNullChecks found no bug the page had; its 94 places were values the code already handles in ways the type-check could not follow (data- attributes the page writes, a map entry set the line before, Share drawn only for an item with a link), each now said so. isPlayable stays a plain boolean: as a type guard its false would have meant "no file", and it is also false for a file that is not audio or video. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ async function loadFeeds(keepSel?: boolean){
|
||||
// brand new account) leaves S.feed alone, so the empty state's own message shows instead.
|
||||
if(!keepSel && S.feeds.length){
|
||||
const known = S.feed && (VIEWS[S.feed] || S.feeds.some(f=>f.id===S.feed));
|
||||
selectFeed(known ? S.feed : ':all');
|
||||
selectFeed(known ? S.feed! : ':all');
|
||||
}
|
||||
}
|
||||
// An OPML can hold dozens of feeds; the ones with something new go first. sort is stable, so the
|
||||
@@ -45,9 +45,9 @@ function setScanning(id: string, on: boolean){
|
||||
paintScanning();
|
||||
}
|
||||
function paintScanning(){
|
||||
const slow=(id: string)=>scanning.has(id)&&Date.now()-scanning.get(id)>=SPIN_AFTER;
|
||||
const slow=(id: string)=>scanning.has(id)&&Date.now()-scanning.get(id)!>=SPIN_AFTER;
|
||||
for(const row of $$('#feedlist .feed')){
|
||||
const id=row.dataset.id;
|
||||
const id=row.dataset.id!;
|
||||
const on=slow(id)||S.feeds.some(c=>c.group===id&&slow(c.id));
|
||||
row.classList.toggle('scanning',on);
|
||||
if(on) row.title='Checking for new items…'; else row.removeAttribute('title');
|
||||
@@ -73,21 +73,21 @@ function spinEnd(done?: {only: string|null, forced: boolean}){
|
||||
}
|
||||
|
||||
function renderFeeds(){
|
||||
const q=$('#feedFilter').value.trim().toLowerCase();
|
||||
const q=$<HTMLInputElement>('#feedFilter').value.trim().toLowerCase();
|
||||
const list=$('#feedlist'); const top=list.scrollTop;
|
||||
// Every row is replaced, so put the keyboard back on the row, or the triangle, it was on.
|
||||
const a=document.activeElement, was=a&&a.closest&&a.closest<HTMLElement>('#feedlist [data-id]');
|
||||
const back=was&&([was.dataset.id,a.classList.contains('chev')] as [string, boolean]);
|
||||
const done=()=>{
|
||||
list.scrollTop=top;
|
||||
const row=back&&list.querySelector(`[data-id="${CSS.escape(back[0])}"]`);
|
||||
if(row) (back[1]&&$('.chev',row)||row).focus();
|
||||
const row=back&&list.querySelector<HTMLElement>(`[data-id="${CSS.escape(back[0])}"]`);
|
||||
if(row) (back[1]&&$('.chev',row)||row as HTMLElement).focus();
|
||||
};
|
||||
// The images already drawn, to go into the rows that replace theirs. Made anew, every icon in
|
||||
// the list went blank and loaded again, once a frame through a check of every feed, as each
|
||||
// feed's new row came in (#134). Matched by their markup, so a changed one is made anew.
|
||||
const drawn=new Map<string,HTMLImageElement[]>();
|
||||
for(const img of $$('img',list)){ const k=img.outerHTML; if(!drawn.has(k)) drawn.set(k,[]); drawn.get(k).push(img); }
|
||||
for(const img of $$<HTMLImageElement>('img',list)){ const k=img.outerHTML; if(!drawn.has(k)) drawn.set(k,[]); drawn.get(k)!.push(img); }
|
||||
list.innerHTML='';
|
||||
const unreadAll=S.feeds.reduce((n,f)=>n+(f.unread||0),0);
|
||||
const places=document.createElement('div');
|
||||
@@ -168,7 +168,7 @@ function renderFeeds(){
|
||||
done();
|
||||
}
|
||||
function selectFeed(id: string){
|
||||
S.feed=id; S.offset=0; S.sel=null; S.q=''; $('#epSearch').value='';
|
||||
S.feed=id; S.offset=0; S.sel=null; S.q=''; $<HTMLInputElement>('#epSearch').value='';
|
||||
try{ localStorage.setItem('ipx.feed',id); }catch{}
|
||||
renderFeeds(); renderFeed(); loadEntries();
|
||||
}
|
||||
@@ -185,6 +185,6 @@ function failBannerHTML(f: Feed){
|
||||
}
|
||||
function wireFailBanner(box: ParentNode,f: Feed){
|
||||
const un=$('[data-ffail="unsub"]',box); if(un) un.onclick=()=>removeFeed(f);
|
||||
const nu=$('[data-ffail="newurl"]',box); if(nu) nu.onclick=()=>settingsModal(f,f.failing.new_url);
|
||||
const nu=$('[data-ffail="newurl"]',box); if(nu) nu.onclick=()=>settingsModal(f,f.failing?.new_url??undefined);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user