diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-25 00:55:59 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-25 00:55:59 +0000 |
| commit | f202c603259c40c4d21a4385c9f69a3efca87acc (patch) | |
| tree | 35718f1d1a48f47c2d63f239121a71c96c076d2d /app/src/main/java/org/terst/feedreader/readability | |
| parent | 439ad1a4f8b6a492bbd54f239462dc38b874cd4c (diff) | |
Text-only bottom bar, re-extract action, fix YouTube thumbnails, verify Stash creds before saving
- Bottom bar: Previous/Keep unread/Next are now plain text labels, no icons
- Add "Re-download and re-extract" to the overflow menu (ReaderScreen and
LinkReaderScreen) to force a fresh fetch + Readability pass on demand,
overwriting whatever's cached
- Fix a real compile bug in the previous commit's reader-mode-off handler
(passed null into a non-null setExtractedContent) via a proper
clearExtractedContent() DAO query
- YouTube embeds: fixed the broken-image-glyph bug two ways -- (1) an
11-char video-ID regex was accidentally matching a playlist embed's
literal "videoseries" placeholder path, building a thumbnail URL for a
video that doesn't exist, and (2) Readability's link-density cleanup was
discarding the replacement entirely (a bare <a><img></a> reads as an
ad/share unit). Restructured as a <figure>/<figcaption> pair -- the same
shape a normal captioned photo takes -- confirmed live this survives
extraction intact
- Stash settings: API key field is now a password field with a show/hide
toggle, and "Verify and save" makes a real authenticated request before
persisting anything, showing why it failed instead of silently saving
bad credentials
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
Diffstat (limited to 'app/src/main/java/org/terst/feedreader/readability')
| -rw-r--r-- | app/src/main/java/org/terst/feedreader/readability/ReaderExtractor.kt | 64 |
1 files changed, 51 insertions, 13 deletions
diff --git a/app/src/main/java/org/terst/feedreader/readability/ReaderExtractor.kt b/app/src/main/java/org/terst/feedreader/readability/ReaderExtractor.kt index 3cb4741..9a96e63 100644 --- a/app/src/main/java/org/terst/feedreader/readability/ReaderExtractor.kt +++ b/app/src/main/java/org/terst/feedreader/readability/ReaderExtractor.kt @@ -180,6 +180,17 @@ object ReaderExtractor { } } + // Some sites lazy-load the embed iframe itself, not just images -- + // leaving `src` blank or a generic loader URL until their own script + // runs, which never happens here. Resolve iframe data-src first (same + // idea as the image lazy-load fix above) so the YouTube detection below + // has a real URL to work with instead of nothing. + var lazyFrames = document.querySelectorAll('iframe[data-src], iframe[data-lazy-src]'); + for (var i = 0; i < lazyFrames.length; i++) { + var lazyFrameSrc = lazyFrames[i].getAttribute('data-src') || lazyFrames[i].getAttribute('data-lazy-src'); + if (lazyFrameSrc) lazyFrames[i].setAttribute('src', lazyFrameSrc); + } + // A YouTube iframe embed is dead weight here -- JS is off in the WebView // that actually displays this content, so the real embedded player could // never initialize anyway (just a blank/broken box). Swap it for a @@ -193,20 +204,47 @@ object ReaderExtractor { ); for (var i = 0; i < iframes.length; i++) { var frame = iframes[i]; - var match = frame.getAttribute('src').match(/embed\/([a-zA-Z0-9_-]+)/); - if (!match) continue; - var videoId = match[1]; + var frameSrc = frame.getAttribute('src') || ''; + // A real video ID is always exactly 11 chars -- a playlist/live + // embed's path is literally ".../embed/videoseries?list=...", which + // (coincidentally exactly 11 characters itself) previously slipped + // past a length-only check and built a thumbnail URL for a video + // that doesn't exist (confirmed live: rendered as a broken-image + // glyph, not a still frame -- exactly what was reported). + var match = frameSrc.match(/embed\/([a-zA-Z0-9_-]{11})(?:[/?]|$)/); + if (match && match[1] === 'videoseries') match = null; + // Neither a class NOR an inline style survives here -- Readability + // strips both as part of its own cleanup. A bare <a><img></a> with + // no surrounding text didn't survive either, for a different reason: + // Readability's link-density cleanup treats a node that's almost + // entirely inside an <a> as a probable ad/share-button unit and + // discards it (confirmed live: the iframe was there, the transform + // ran, and the result still vanished from the final extracted + // content). A <figure>/<figcaption> pair -- the exact shape a normal + // captioned photo takes, which reliably survives elsewhere in this + // pipeline -- with the caption text OUTSIDE the link keeps the + // node's link-density low enough to read as real content. + var figure = document.createElement('figure'); var link = document.createElement('a'); - link.className = 'yt-embed'; - link.href = 'https://www.youtube.com/watch?v=' + videoId; - var thumb = document.createElement('img'); - thumb.src = 'https://i.ytimg.com/vi/' + videoId + '/hqdefault.jpg'; - thumb.alt = 'YouTube video'; - var play = document.createElement('span'); - play.className = 'yt-play'; - link.appendChild(thumb); - link.appendChild(play); - frame.parentNode.replaceChild(link, frame); + figure.appendChild(link); + if (match) { + var videoId = match[1]; + link.href = 'https://www.youtube.com/watch?v=' + videoId; + var thumb = document.createElement('img'); + thumb.src = 'https://i.ytimg.com/vi/' + videoId + '/hqdefault.jpg'; + thumb.alt = 'YouTube video'; + link.appendChild(thumb); + var caption = document.createElement('figcaption'); + caption.textContent = '▶ Watch on YouTube'; + figure.appendChild(caption); + } else { + // No confident single video ID (a playlist, a live stream, or + // lazy-load resolution above still came up empty) -- a plain + // link beats a broken thumbnail or a dead iframe. + link.href = frameSrc || 'https://www.youtube.com/'; + link.textContent = 'Watch on YouTube'; + } + frame.parentNode.replaceChild(figure, frame); } // A news/blog article page's <header> conventionally holds everything |
