fix(import): route YouTube iframes and <video> elements to the base.video module#151
Draft
DavidBabinec wants to merge 2 commits into
Draft
fix(import): route YouTube iframes and <video> elements to the base.video module#151DavidBabinec wants to merge 2 commits into
DavidBabinec wants to merge 2 commits into
Conversation
…ideo module YouTube <iframe> embeds and <video> elements imported via paste-HTML, Super Import, or the AI agent's site_insert_html tool previously fell through to the catch-all rule as base.container nodes. base.container declares no CSP sources, so YouTube iframes stayed blocked by the published page's frame-src 'none' even after PR #141 (which only fires when base.video renders). New rules added to HTML_TO_MODULE_RULES in src/core/htmlImport/rules.ts: - iframe: YouTube host check (youtube.com/m.youtube.com/youtube-nocookie.com/ youtu.be) maps to base.video; any other iframe falls back to base.container with tag:'custom',customTag:'iframe' so Vimeo, Maps, and arbitrary embeds keep working. rel=0 in the embed URL sets noRelatedVideos:true; playsinline=1 sets playsinline:true; the title attribute maps to the title prop. - video: maps to base.video with videoUrl from <video src> or the first <source src> child, and all boolean attributes (controls, autoplay, loop, muted, playsinline). recurse:false so <source> children are consumed, not emitted as extra nodes. Inline YouTube host detection in rules.ts — src/core/ must not import from src/modules/, so parseYoutubeId from src/modules/base/video/youtube.ts is not imported. The importer only needs host-level detection to decide the module; base.video's render() re-parses the stored videoUrl at publish time. base.video (src/modules/base/video/) extended with two new props: - title (default: 'YouTube video') — used as the iframe title attribute, replacing the previous hardcoded string. Improves accessibility. - noRelatedVideos (default: false) — appends rel=0 to the YouTube embed URL to suppress recommended videos. youtubeEmbedUrl() updated to build the query string from autoplay and noRelatedVideos together. VideoEditor.tsx wired up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The chore/gitignore-superpowers branch had these files staged for deletion. When switching to fix/import-video-embeds-to-base-video the staged index was inherited and the previous commit swept them in unintentionally. Restore them from origin/main to keep this PR scope clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The gap:
HTML_TO_MODULE_RULEShad no rule for<iframe>or<video>, so they fell through to the catch-all*rule and becamebase.containernodes withtag:'custom', customTag:'iframe'(or'video').base.containerdeclares no CSP sources, so YouTube iframes stayed blocked by the published page'sframe-src 'none'even after PR #141's relaxation — that only fires whenbase.videorenders and callsrenderYoutube()which returnsYOUTUBE_CSP_SOURCES. The bug affects all three consumers of the importer: the paste-HTML modal, the full-site Super Import, and the AI agent'ssite_insert_html/site_replace_node_htmltools.The fix:
src/core/htmlImport/rules.ts— two new rules added before the catch-all*:iframerule: checks if thesrchostname is a YouTube domain (youtube.com,m.youtube.com,youtube-nocookie.com,youtu.be) via an inline host check (layering forbids importingparseYoutubeIdfromsrc/modules/). YouTube iframes map tobase.videowithvideoUrl,title,noRelatedVideos, andplaysinlineall read from the element. Any other iframe (Vimeo, Maps, forms, arbitrary) falls back tobase.containerwithtag:'custom', customTag:'iframe'— same as before, so non-video iframes continue to work and carry their attributes viahtmlAttributes.recurse: false.videorule: maps tobase.videowithvideoUrlfrom<video src>or the first<source src>child, and boolean attrscontrols,autoplay,loop,muted,playsinline.recurse: falseso<source>children are consumed, not emitted as extra nodes.src/modules/base/video/props.ts— two new props added toVideoPropsSchema:title(default'YouTube video') — replaces the hardcoded string in the iframetitleattribute, improving accessibility.noRelatedVideos(defaultfalse) — appendsrel=0to the YouTube embed URL to suppress recommended videos.src/modules/base/video/youtube.ts—youtubeEmbedUrl()updated to acceptnoRelatedVideosand build the query string from bothautoplayandnoRelatedVideostogether.src/modules/base/video/index.ts—renderYoutube()receivestitleandnoRelatedVideosfrom props and uses them. Both fields added to the moduleschemaso they appear in the editor panel.src/modules/base/video/VideoEditor.tsx— canvas preview iframe wired up withprops.titleandprops.noRelatedVideos.What still falls back to
base.container:Vimeo, Google Maps, and arbitrary third-party iframes still map to
base.containerwithcustomTag:'iframe'. Full iframe embedding for non-YouTube providers requires a future site-level trusted-frame-hosts allowlist soframe-srccan be relaxed safely.AI agent fix included:
site_insert_htmluses the same importer, so AI-pasted YouTube embeds now also land asbase.videoand render correctly in published pages.Verification
Targeted tests:
src/__tests__/htmlImport/mapping.test.ts— 18 new tests inbase.video — <iframe> import mappingandbase.video — <video> import mapping(YouTube URL variants, non-YouTube fallback, title/noRelatedVideos/playsinline mapping, source-child fallback, no children on leaf nodes)src/__tests__/base-modules.test.ts— 5 new tests fortitleandnoRelatedVideosprops; schema shape test updated to include both new fields