feat(webui): add on-demand ▶ link summary UI; calls /api/linksummary and renders inline under link card (24h cache)
This commit is contained in:
parent
8a6111aeb5
commit
0e0d0ea824
1 changed files with 31 additions and 1 deletions
|
|
@ -19,7 +19,37 @@ async function api(path, params){
|
||||||
function appendBatch(arr){ const el=document.getElementById('tail'); const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.appendChild(frag); if(st.atBottom){ el.scrollTop = el.scrollHeight; } }
|
function appendBatch(arr){ const el=document.getElementById('tail'); const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.appendChild(frag); if(st.atBottom){ el.scrollTop = el.scrollHeight; } }
|
||||||
function prependBatch(arr){ const el=document.getElementById('tail'); const oldTop=el.firstChild; const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.insertBefore(frag, el.firstChild); if(oldTop){ oldTop.scrollIntoView(); } }
|
function prependBatch(arr){ const el=document.getElementById('tail'); const oldTop=el.firstChild; const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.insertBefore(frag, el.firstChild); if(oldTop){ oldTop.scrollIntoView(); } }
|
||||||
|
|
||||||
function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not([data-card])'); links.forEach(a=>{ a.setAttribute('data-card','1'); fetch('/api/linkcard?url='+encodeURIComponent(a.href)).then(r=>r.json()).then(card=>{ if(!card) return; if(card.title||card.description||card.image){ const c=document.createElement('div'); c.className='card'; var html=''; if(card.image){ html += '<div><img src="'+card.image+'" alt="" style="max-width:160px;max-height:120px;object-fit:cover;border-radius:.25rem"></div>'; } html += '<div style="flex:1;margin-left:.5rem">'; if(card.title){ html += '<div style="font-weight:600">'+escapeHtml(card.title)+'</div>'; } if(card.description){ html += '<div style="opacity:.8">'+escapeHtml(card.description)+'</div>'; } html += '</div>'; c.innerHTML = '<div style="display:flex;align-items:flex-start;gap:.5rem">'+html+'</div>'; a.parentNode.insertBefore(c, a.nextSibling); } }).catch(()=>{}); }); }
|
function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not([data-card])'); links.forEach(a=>{ a.setAttribute('data-card','1');
|
||||||
|
// Fetch and render card
|
||||||
|
fetch('/api/linkcard?url='+encodeURIComponent(a.href)).then(r=>r.json()).then(card=>{
|
||||||
|
if(!card) return;
|
||||||
|
if(card.title||card.description||card.image){
|
||||||
|
const c=document.createElement('div'); c.className='card'; var html='';
|
||||||
|
if(card.image){ html += '<div><img src="'+card.image+'" alt="" style="max-width:160px;max-height:120px;object-fit:cover;border-radius:.25rem"></div>'; }
|
||||||
|
html += '<div style="flex:1;margin-left:.5rem">';
|
||||||
|
if(card.title){ html += '<div style="font-weight:600">'+escapeHtml(card.title)+'</div>'; }
|
||||||
|
if(card.description){ html += '<div style="opacity:.8">'+escapeHtml(card.description)+'</div>'; }
|
||||||
|
html += '</div>';
|
||||||
|
const row = document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML = html;
|
||||||
|
c.appendChild(row);
|
||||||
|
// Summary control row
|
||||||
|
const ctrl = document.createElement('div'); ctrl.style.marginTop='.25rem';
|
||||||
|
const btn = document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='\u25B6'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
|
||||||
|
const spinner = document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
|
||||||
|
const sum = document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem';
|
||||||
|
btn.onclick = async ()=>{
|
||||||
|
btn.disabled=true; spinner.textContent='…'; sum.textContent='';
|
||||||
|
try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; }
|
||||||
|
catch(e){ sum.textContent = 'error: '+e; }
|
||||||
|
spinner.textContent=''; btn.disabled=false;
|
||||||
|
};
|
||||||
|
ctrl.appendChild(btn); ctrl.appendChild(spinner);
|
||||||
|
c.appendChild(ctrl);
|
||||||
|
c.appendChild(sum);
|
||||||
|
a.parentNode.insertBefore(c, a.nextSibling);
|
||||||
|
}
|
||||||
|
}).catch(()=>{});
|
||||||
|
}); }
|
||||||
|
|
||||||
async function loadChannels(){ try{ const data = await api('/api/channels'); st.channels = data; renderChannels(); if(data.length>0){ selectChannel(data[0]); } } catch(e){} }
|
async function loadChannels(){ try{ const data = await api('/api/channels'); st.channels = data; renderChannels(); if(data.length>0){ selectChannel(data[0]); } } catch(e){} }
|
||||||
function renderChannels(){ const list=document.getElementById('chanlist'); if(!list) return; list.innerHTML=''; st.channels.forEach(c=>{ const a=document.createElement('a'); a.href='#'; a.textContent=c; a.onclick=(ev)=>{ev.preventDefault(); selectChannel(c)}; if(c===st.current) a.className='active'; list.appendChild(a); }); }
|
function renderChannels(){ const list=document.getElementById('chanlist'); if(!list) return; list.innerHTML=''; st.channels.forEach(c=>{ const a=document.createElement('a'); a.href='#'; a.textContent=c; a.onclick=(ev)=>{ev.preventDefault(); selectChannel(c)}; if(c===st.current) a.className='active'; list.appendChild(a); }); }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue