summaryrefslogtreecommitdiff
path: root/app/src/main/java/org/terst/feedreader/readability
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-25 00:55:59 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-25 00:55:59 +0000
commitf202c603259c40c4d21a4385c9f69a3efca87acc (patch)
tree35718f1d1a48f47c2d63f239121a71c96c076d2d /app/src/main/java/org/terst/feedreader/readability
parent439ad1a4f8b6a492bbd54f239462dc38b874cd4c (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.kt64
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