mirror of https://github.com/usememos/memos
feat: youtube embeds
parent
29b683d5db
commit
41479c7adc
@ -0,0 +1,40 @@
|
||||
import { memo } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Props {
|
||||
videoIds: string[];
|
||||
}
|
||||
|
||||
const MemoYoutubeEmbedListView: React.FC<Props> = ({ videoIds }: Props) => {
|
||||
if (!videoIds || videoIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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; autoplay; 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>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(MemoYoutubeEmbedListView);
|
@ -0,0 +1,85 @@
|
||||
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||
|
||||
// Regular expressions to match various YouTube URL formats.
|
||||
const YOUTUBE_REGEXPS: RegExp[] = [
|
||||
/https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([^&\s]+)/i,
|
||||
/https?:\/\/(?:www\.)?youtu\.be\/([^?\s]+)/i,
|
||||
/https?:\/\/(?:www\.)?youtube\.com\/shorts\/([^?\s]+)/i,
|
||||
/https?:\/\/(?:www\.)?youtube\.com\/embed\/([^?\s]+)/i,
|
||||
];
|
||||
|
||||
/**
|
||||
* Extract the YouTube video ID from a given URL, if any.
|
||||
* @param url The URL string to parse.
|
||||
* @returns The video ID, or undefined if the URL is not a YouTube link.
|
||||
*/
|
||||
export const extractYoutubeIdFromUrl = (url: string): string | undefined => {
|
||||
for (const regexp of YOUTUBE_REGEXPS) {
|
||||
const match = url.match(regexp);
|
||||
if (match?.[1]) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract YouTube video IDs from markdown nodes.
|
||||
* @param nodes The array of markdown nodes to extract YouTube video IDs from .
|
||||
* @returns A deduplicated array of YouTube video IDs.
|
||||
*/
|
||||
export const extractYoutubeVideoIdsFromNodes = (nodes: Node[]): string[] => {
|
||||
const ids = new Set<string>();
|
||||
|
||||
const isNodeArray = (value: unknown): value is Node[] =>
|
||||
Array.isArray(value) &&
|
||||
value.length > 0 &&
|
||||
typeof value[0] === "object" &&
|
||||
value[0] !== null &&
|
||||
"type" in (value[0] as Record<string, unknown>);
|
||||
|
||||
// Collect all child Node instances nested anywhere inside the given node
|
||||
const collectChildren = (node: Node): Node[] => {
|
||||
const collected: Node[] = [];
|
||||
const queue: unknown[] = Object.values(node);
|
||||
|
||||
while (queue.length) {
|
||||
const item = queue.shift();
|
||||
if (!item) continue;
|
||||
|
||||
if (isNodeArray(item)) {
|
||||
collected.push(...item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(item)) {
|
||||
queue.push(...item);
|
||||
} else if (typeof item === "object") {
|
||||
queue.push(...Object.values(item as Record<string, unknown>));
|
||||
}
|
||||
}
|
||||
|
||||
return collected;
|
||||
};
|
||||
|
||||
const stack: Node[] = [...nodes];
|
||||
|
||||
while (stack.length) {
|
||||
const node = stack.pop()!;
|
||||
|
||||
if (node.type === NodeType.LINK && node.linkNode) {
|
||||
const id = extractYoutubeIdFromUrl(node.linkNode.url);
|
||||
if (id) ids.add(id);
|
||||
} else if (node.type === NodeType.AUTO_LINK && node.autoLinkNode) {
|
||||
const id = extractYoutubeIdFromUrl(node.autoLinkNode.url);
|
||||
if (id) ids.add(id);
|
||||
}
|
||||
|
||||
const children = collectChildren(node);
|
||||
if (children.length) {
|
||||
stack.push(...children);
|
||||
}
|
||||
}
|
||||
|
||||
return [...ids];
|
||||
};
|
Loading…
Reference in New Issue