Skip to content

Show inline video embeds (YouTube, Vimeo) in the reader - #80

Merged
jonocodes merged 1 commit into
mainfrom
feature/video-embeds
Oct 4, 2026
Merged

jonocodes merged 1 commit into
mainfrom
feature/video-embeds

Conversation

@jonocodes

Copy link
Copy Markdown
Owner

Problem

Saved articles showed their images but silently lost any embedded video — not even a link. Readability deliberately keeps a page's video <iframe>s (its REGEXPS.videos), so the player survived scraping, but the reader's DOMPurify.sanitize(...) call only allowed link, and DOMPurify drops every iframe by default.

Fix

  • Add src/utils/article/sanitize.ts, which allows <iframe> only for the hosts Readability itself preserves: YouTube, youtube-nocookie, Vimeo (player.vimeo.com), Twitch, Dailymotion, v.qq.com, archive.org / upload.wikimedia.org. Arbitrary, srcdoc, and javascript: iframes stay blocked.
  • Preserve the player attributes the original page set (allow, allowfullscreen, frameborder, scrolling, referrerpolicy).
  • ArticleComponent now renders through sanitizeArticleHtml; script stripping is unchanged.

Because sanitizing happens at render time, already-saved articles start showing their videos too — their stored content.html already contains the iframe.

Tests

  • New src/utils/article/sanitize.test.ts (21 tests): allows YouTube/Vimeo/Twitch/Dailymotion/archive.org, rejects non-video, srcdoc, javascript:, and empty sources, and keeps script stripping + stylesheet links intact.
  • npx jest → 348 passed · npx tsc --noEmit clean · lint 0 errors.

Notes

Video needs the network, so embeds won't play offline (text/images still do).

Readability keeps a page's video <iframe>s, but the reader's DOMPurify call
stripped every iframe, so a saved article silently lost its player while the
images survived. Allow iframes only for the embed hosts Readability itself
preserves (YouTube, youtube-nocookie, Vimeo, Twitch, Dailymotion, v.qq,
archive.org/wikimedia), and keep the player attributes (allow, allowfullscreen,
frameborder). Arbitrary, srcdoc, and javascript: iframes stay blocked.

Extract the policy into src/utils/article/sanitize.ts with unit tests covering
allowed and rejected embeds.
@netlify

netlify Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for savrlist ready!

Name Link
🔨 Latest commit 32b55ba
🔍 Latest deploy log https://app.netlify.com/projects/savrlist/deploys/6ac1ce8f35c41b0008d923ed
😎 Deploy Preview https://deploy-preview-80--savrlist.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@jonocodes
jonocodes merged commit 37e99ee into main Oct 4, 2026
6 checks passed
jonocodes added a commit that referenced this pull request Oct 4, 2026
Conflicts:
- ArticleComponent.tsx: #80 moved sanitising into sanitizeArticleHtml
  (video-embed iframe allow-list) but still passed a fresh `{ __html }`
  literal each render, so the DOM-rebuild bug remained on main. Keep #80's
  sanitiser and this branch's memoization: useMemo(() => ({ __html:
  sanitizeArticleHtml(html) }), [html]).
- CHANGELOG.md: keep both; this branch's entries move under #80's
  2026-10-03 heading since they land after it.

With embeds now rendered the rebuild also reloaded every player on each
save: on main's code 3 embeds were loaded 18 times over 3 reading pauses.
Added a scroll-stability test pinning one load per embed (fails on main's
version of the line, passes with the memoization).

Adjusted for #79, which swaps failed images for a local placeholder:
- The failed-download dimensions test now expects the placeholder and
  data-orig-src, and still asserts no width/height is claimed.
- The shared jest mock of ~/utils/article/tools gains a FetchError class.
  ingestion.ts now does `e instanceof FetchError` on a failed image, and
  without it the check throws; no existing test ran a failed download
  through ingestHtml, so the gap was latent.
- Comment and changelog wording: only articles saved before #79 still
  hold remote URLs for failed images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant