-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat: youtube embeds #4841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: youtube embeds #4841
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request introduces YouTube video embedding functionality to the MemoView component by automatically detecting YouTube URLs in markdown content and rendering them as embedded video players.
- Adds utility functions to extract YouTube video IDs from various URL formats and traverse markdown nodes
- Creates a new component for rendering YouTube embeds in a horizontal scrollable list
- Integrates the YouTube embed functionality into the existing MemoView component
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
File | Description |
---|---|
web/src/utils/youtube.ts | New utility functions for YouTube URL parsing and video ID extraction from markdown nodes |
web/src/components/MemoYoutubeEmbedListView.tsx | New component that renders YouTube videos as embedded iframes in a horizontal layout |
web/src/components/MemoView.tsx | Updates to integrate YouTube embed functionality using the new utility and component |
const stack: Node[] = [...nodes]; | ||
|
||
while (stack.length) { | ||
const node = stack.pop()!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the non-null assertion operator (!) on stack.pop() assumes the stack is never empty, but this could lead to runtime errors if the stack becomes empty unexpectedly. Consider using a safer approach like checking if the popped value exists before proceeding.
const node = stack.pop()!; | |
const node = stack.pop(); | |
if (!node) continue; |
Copilot uses AI. Check for mistakes.
<div className="relative w-full pt-[56.25%] rounded-lg overflow-hidden border border-border/60 bg-popover"> | ||
<iframe | ||
className="absolute top-0 left-0 w-full h-full" | ||
src={`https://www.youtube.com/embed/${videoId}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The videoId is directly interpolated into the iframe src without validation or sanitization. Although the video IDs are extracted using regex patterns, consider adding additional validation to ensure the videoId contains only expected characters (alphanumeric, hyphens, underscores) to prevent potential XSS attacks.
Copilot uses AI. Check for mistakes.
className="absolute top-0 left-0 w-full h-full" | ||
src={`https://www.youtube.com/embed/${videoId}`} | ||
title="YouTube video player" | ||
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The iframe permissions are quite permissive, including clipboard-write and accelerometer access. Consider restricting permissions to only what's necessary for video playback (e.g., encrypted-media, picture-in-picture) to follow the principle of least privilege.
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" | |
allow="encrypted-media; picture-in-picture" |
Copilot uses AI. Check for mistakes.
const EmbedCard = ({ videoId, className }: { videoId: string; className?: string }) => { | ||
return ( | ||
<div className={cn("relative w-full", className)}> | ||
<div className="relative w-full pt-[56.25%] rounded-lg overflow-hidden border border-border/60 bg-popover"> | ||
<iframe | ||
className="absolute top-0 left-0 w-full h-full" | ||
src={`https://www.youtube.com/embed/${videoId}`} | ||
title="YouTube video player" | ||
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" | ||
allowFullScreen | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
return ( | ||
<div className="w-full flex flex-row justify-start overflow-auto gap-2"> | ||
{videoIds.map((id) => ( | ||
<div key={id} className="w-80 flex flex-col justify-start items-start shrink-0"> | ||
<EmbedCard videoId={id} className="max-h-64 grow" /> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EmbedCard component is defined inside the render function, which means it will be recreated on every render. Consider moving this component outside of the main component or using useCallback to optimize performance.
const EmbedCard = ({ videoId, className }: { videoId: string; className?: string }) => { | |
return ( | |
<div className={cn("relative w-full", className)}> | |
<div className="relative w-full pt-[56.25%] rounded-lg overflow-hidden border border-border/60 bg-popover"> | |
<iframe | |
className="absolute top-0 left-0 w-full h-full" | |
src={`https://www.youtube.com/embed/${videoId}`} | |
title="YouTube video player" | |
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" | |
allowFullScreen | |
/> | |
</div> | |
</div> | |
); | |
}; | |
return ( | |
<div className="w-full flex flex-row justify-start overflow-auto gap-2"> | |
{videoIds.map((id) => ( | |
<div key={id} className="w-80 flex flex-col justify-start items-start shrink-0"> | |
<EmbedCard videoId={id} className="max-h-64 grow" /> | |
</div> | |
))} | |
</div> | |
); | |
}; | |
const EmbedCard = memo(({ videoId, className }: { videoId: string; className?: string }) => { | |
return ( | |
<div className={cn("relative w-full", className)}> | |
<div className="relative w-full pt-[56.25%] rounded-lg overflow-hidden border border-border/60 bg-popover"> | |
<iframe | |
className="absolute top-0 left-0 w-full h-full" | |
src={`https://www.youtube.com/embed/${videoId}`} | |
title="YouTube video player" | |
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" | |
allowFullScreen | |
/> |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already commented on Copilots "suggestions" once.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Renja <76645494+renja-g@users.noreply.github.com>
d529be9
to
f97d525
Compare
If there is anything you want changed, let me know. |
Is discussed in #4834

This pull request introduces functionality to embed YouTube videos in the
MemoView
component by extracting video IDs from markdown nodes and rendering them using a newMemoYoutubeEmbedListView
component. The changes involve adding utility functions for video ID extraction, updating theMemoView
component to use these functions, and creating a dedicated component for rendering embedded YouTube videos.