Signals in a folder of their own in the feed list, as an OPML's feeds are (#255)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 17:14:38 +00:00
parent 13cad18857
commit 0fdd609791
8 changed files with 89 additions and 27 deletions

View File

@@ -12,23 +12,75 @@ function signalOf(view: string|null){
return id===null ? undefined : S.signals.find(s=>s.id===id);
}
/// Your Signals under the places, and the way to make one.
function signalRows(places: HTMLElement){
for(const sg of S.signals){
const id=':signal:'+sg.id, el=document.createElement('div');
el.className='place'+(S.feed===id?' sel':'');
el.tabIndex=0; el.dataset.id=id;
el.innerHTML=`<span class="ico">${ICON.signal}</span><b>${esc(sg.name)}</b>`+
`<span class="badge${sg.unread?'':' zero'}" title="${sg.unread} unread">${sg.unread>999?'999+':sg.unread}</span>`;
el.onclick=()=>{ selectFeed(id); nav(false); };
el.onpointerenter=el.ontouchstart=()=>prefetch(id);
places.appendChild(el);
/// The Signals folder's place: a folder of their own at the top of the feed list, open or closed as
/// an OPML's is (#255). Under the places, a long list of them pushed the feeds down.
const SIGNALS=':signals';
// Tinted as a feed's initials are: a plain tile vanished into a selected row (see .art.ini).
const sigArt=(icon=ICON.signal)=>`<span class="fart"><div class="art ini sig" style="--tint:var(--accent)">${icon}</div></span>`;
function feedRow(id: string, cls: string, html: string, open: ()=>void){
const el=document.createElement('div');
el.className='feed '+cls+(S.feed===id?' sel':'');
el.tabIndex=0; el.dataset.id=id; el.innerHTML=html;
el.onclick=()=>{ open(); nav(false); };
return el;
}
const badgeHTML=(n: number)=>`<span class="badge${n?'':' zero'}" title="${n} unread">${n>999?'999+':n}</span>`;
function signalFolder(list: HTMLElement, q: string){
const mine=S.signals.filter(s=>!q||s.name.toLowerCase().includes(q));
if(q&&!mine.length) return;
const open=expanded.has(SIGNALS)||!!q;
const n=S.signals.length;
list.appendChild(feedRow(SIGNALS,'sigs',
`<button class="chev" aria-expanded="${open}" title="Show or hide your signals" aria-label="Show or hide your signals">${ICON.caret}</button>`+
sigArt()+`<div class="txt"><b>Signals</b><small>${n?plural(n,'signal'):'What matters, out of the noise'}</small></div>`+
badgeHTML(S.signals.reduce((k,s)=>k+s.unread,0)),
()=>selectFeed(SIGNALS)));
$('.chev',list.lastElementChild!).onclick=(ev: Event)=>{ ev.stopPropagation(); toggleGroup(SIGNALS); };
if(!open) return;
for(const sg of mine){
const el=feedRow(':signal:'+sg.id,'child',
`<span class="fart">${tileHTML(sg.name)}</span><div class="txt"><b>${esc(sg.name)}</b><small>${esc(sg.keywords.join(', '))}</small></div>`+badgeHTML(sg.unread),
()=>selectFeed(':signal:'+sg.id));
el.onpointerenter=el.ontouchstart=()=>prefetch(':signal:'+sg.id);
list.appendChild(el);
}
list.appendChild(feedRow(':signal:new','child',sigArt(ICON.plus)+'<div class="txt"><b>New signal</b></div>',()=>signalModal()));
}
/// The folder's page: every Signal, as an OPML's page lists its feeds.
function renderSignals(){
const unread=S.signals.reduce((k,s)=>k+s.unread,0);
$('#count').textContent=`Signals: ${plural(S.signals.length,'signal')}, ${unread} unread`;
$<HTMLButtonElement>('#tbRemove').disabled=true;
syncTools(null);
const box=$('#content');
box.classList.add('plain');
box.innerHTML=`
<div class="fhead slim">
<div class="art">${ICON.signal}</div>
<div class="meta">
<h2>Signals</h2>
<div class="sub">Each one picks what matters out of the noise of your ${plural(S.feeds.length,'feed')} · ${unread} unread</div>
</div>
<div class="acts">
<button class="btn ico" data-a="new" title="New signal" aria-label="New signal">${ICON.plus}</button>
</div>
</div>
<div class="childlist" id="siglist"></div>`;
$<HTMLButtonElement>('#content .acts [data-a="new"]').onclick=()=>signalModal();
const list=$('#siglist');
if(!S.signals.length){
list.innerHTML='<p class="empty">No signals yet. Make one to pull a subject out of every feed you subscribe to.</p>';
return;
}
for(const sg of S.signals){
const el=document.createElement('div');
el.className='childrow';
el.innerHTML=tileHTML(sg.name)+`<div class="txt"><b>${esc(sg.name)}</b>`+
`<small class="meta">${esc(sg.keywords.join(', '))}</small></div><span class="badge${sg.unread?'':' zero'}">${sg.unread}</span>`;
el.onclick=()=>selectFeed(':signal:'+sg.id);
list.appendChild(el);
}
const add=document.createElement('div');
add.className='place'; add.tabIndex=0; add.dataset.id=':signal:new';
add.innerHTML=`<span class="ico">${ICON.plus}</span><b>New signal</b>`;
add.onclick=()=>{ signalModal(); nav(false); };
places.appendChild(add);
}
function signalHead(sg: Signal){