From 41479c7adc30d0acd155e7790c7dd9a3a341e2b5 Mon Sep 17 00:00:00 2001 From: Renja Date: Wed, 9 Jul 2025 19:04:37 +0200 Subject: [PATCH] feat: youtube embeds --- web/src/components/MemoView.tsx | 7 +- .../components/MemoYoutubeEmbedListView.tsx | 40 +++++++++ web/src/utils/youtube.ts | 85 +++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 web/src/components/MemoYoutubeEmbedListView.tsx create mode 100644 web/src/utils/youtube.ts diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 50d2cc5f7..2c88637d1 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -1,6 +1,6 @@ import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react"; import { observer } from "mobx-react-lite"; -import { memo, useCallback, useState } from "react"; +import { memo, useCallback, useState, useMemo } from "react"; import { Link, useLocation } from "react-router-dom"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import useAsyncEffect from "@/hooks/useAsyncEffect"; @@ -20,6 +20,8 @@ import MemoEditor from "./MemoEditor"; import MemoLocationView from "./MemoLocationView"; import MemoReactionistView from "./MemoReactionListView"; import MemoRelationListView from "./MemoRelationListView"; +import MemoYoutubeEmbedListView from "./MemoYoutubeEmbedListView"; +import { extractYoutubeVideoIdsFromNodes } from "@/utils/youtube"; import { PreviewImageDialog } from "./PreviewImageDialog"; import ReactionSelector from "./ReactionSelector"; import UserAvatar from "./UserAvatar"; @@ -65,6 +67,8 @@ const MemoView: React.FC = observer((props: Props) => { workspaceMemoRelatedSetting.enableBlurNsfwContent && memo.tags?.some((tag) => workspaceMemoRelatedSetting.nsfwTags.some((nsfwTag) => tag === nsfwTag || tag.startsWith(`${nsfwTag}/`))); + const youtubeVideoIds = useMemo(() => extractYoutubeVideoIdsFromNodes(memo.nodes), [memo.nodes]); + // Initial related data: creator. useAsyncEffect(async () => { const user = await userStore.getOrFetchUserByName(memo.creator); @@ -245,6 +249,7 @@ const MemoView: React.FC = observer((props: Props) => { parentPage={parentPage} /> {memo.location && } + diff --git a/web/src/components/MemoYoutubeEmbedListView.tsx b/web/src/components/MemoYoutubeEmbedListView.tsx new file mode 100644 index 000000000..c6c85ccdb --- /dev/null +++ b/web/src/components/MemoYoutubeEmbedListView.tsx @@ -0,0 +1,40 @@ +import { memo } from "react"; +import { cn } from "@/lib/utils"; + +interface Props { + videoIds: string[]; +} + +const MemoYoutubeEmbedListView: React.FC = ({ videoIds }: Props) => { + if (!videoIds || videoIds.length === 0) { + return null; + } + + const EmbedCard = ({ videoId, className }: { videoId: string; className?: string }) => { + return ( +
+
+