feat(cards): YouTube oEmbed; UI: inline actions in header, 🌝/🌚 summarize toggle, header collapse hides all
This commit is contained in:
parent
2680abcf1f
commit
29d94c13d5
2 changed files with 35 additions and 3 deletions
|
|
@ -270,6 +270,38 @@ func (s *Server) handleLinkCard(w http.ResponseWriter, r *http.Request) {
|
||||||
// fallthrough to generic fetch if oEmbed fails
|
// fallthrough to generic fetch if oEmbed fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for YouTube via oEmbed
|
||||||
|
if host == "www.youtube.com" || host == "youtube.com" || host == "m.youtube.com" || host == "youtu.be" {
|
||||||
|
watchURL := raw
|
||||||
|
if host == "youtu.be" {
|
||||||
|
// Convert youtu.be/ID to watch?v=ID
|
||||||
|
id := strings.TrimPrefix(u.Path, "/")
|
||||||
|
watchURL = "https://www.youtube.com/watch?v=" + id
|
||||||
|
}
|
||||||
|
oembed := "https://www.youtube.com/oembed?format=json&url=" + url.QueryEscape(watchURL)
|
||||||
|
client := &http.Client{Timeout: 8 * time.Second}
|
||||||
|
reqY, _ := http.NewRequestWithContext(r.Context(), http.MethodGet, oembed, nil)
|
||||||
|
respY, errY := client.Do(reqY)
|
||||||
|
if errY == nil && respY.StatusCode >= 200 && respY.StatusCode < 300 {
|
||||||
|
defer respY.Body.Close()
|
||||||
|
var o struct{
|
||||||
|
Title string `json:"title"`
|
||||||
|
Author string `json:"author_name"`
|
||||||
|
Thumb string `json:"thumbnail_url"`
|
||||||
|
HTML string `json:"html"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(respY.Body).Decode(&o); err == nil {
|
||||||
|
card := linkCard{URL: raw, Title: o.Title, Image: o.Thumb, HTML: o.HTML}
|
||||||
|
s.cardCache[raw] = card
|
||||||
|
s.cardCacheExp[raw] = time.Now().Add(24 * time.Hour)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_ = json.NewEncoder(w).Encode(card)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallthrough if oEmbed fails
|
||||||
|
}
|
||||||
|
|
||||||
// fetch minimal HTML and extract tags using a tolerant HTML parser
|
// fetch minimal HTML and extract tags using a tolerant HTML parser
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
req, _ := http.NewRequestWithContext(r.Context(), http.MethodGet, raw, nil)
|
req, _ := http.NewRequestWithContext(r.Context(), http.MethodGet, raw, nil)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
|
||||||
left.appendChild(fav); left.appendChild(title);
|
left.appendChild(fav); left.appendChild(title);
|
||||||
const actions=document.createElement('div'); actions.className='card-actions';
|
const actions=document.createElement('div'); actions.className='card-actions';
|
||||||
const chevron=document.createElement('button'); chevron.type='button'; chevron.title='Expand/collapse'; chevron.textContent='▾'; chevron.style.padding='0 .4rem'; chevron.style.fontSize='.9rem';
|
const chevron=document.createElement('button'); chevron.type='button'; chevron.title='Expand/collapse'; chevron.textContent='▾'; chevron.style.padding='0 .4rem'; chevron.style.fontSize='.9rem';
|
||||||
const btn=document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='Summarize ✨'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
|
const btn=document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='🌝'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
|
||||||
const spinner=document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
|
const spinner=document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
|
||||||
actions.appendChild(btn); actions.appendChild(chevron); actions.appendChild(spinner);
|
actions.appendChild(btn); actions.appendChild(chevron); actions.appendChild(spinner);
|
||||||
head.appendChild(left); head.appendChild(actions);
|
head.appendChild(left); head.appendChild(actions);
|
||||||
|
|
@ -74,10 +74,10 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
|
||||||
|
|
||||||
btn.onclick = async ()=>{
|
btn.onclick = async ()=>{
|
||||||
if(sum.style.display!== 'none' && sum.textContent){ // hide existing
|
if(sum.style.display!== 'none' && sum.textContent){ // hide existing
|
||||||
sum.style.display='none'; btn.textContent='Summarize ✨'; pinBottomMulti(); return;
|
sum.style.display='none'; btn.textContent='🌝'; pinBottomMulti(); return;
|
||||||
}
|
}
|
||||||
btn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display='';
|
btn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display='';
|
||||||
try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; btn.textContent='Hide summary'; }
|
try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; btn.textContent='🌚'; }
|
||||||
catch(e){ sum.textContent = 'error: '+e; }
|
catch(e){ sum.textContent = 'error: '+e; }
|
||||||
spinner.textContent=''; btn.disabled=false; pinBottomMulti();
|
spinner.textContent=''; btn.disabled=false; pinBottomMulti();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue