From a968f476404b18b8e82b73c42d67d511377cbd47 Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 16:35:40 +0800 Subject: [PATCH] feat: support mobile video previews --- src/App.tsx | 41 +++++++++--- src/components/LatestUpdateRow.tsx | 4 +- src/components/PopularRankList.tsx | 6 +- src/components/RecommendedCard.tsx | 4 +- src/components/SearchPanel.tsx | 4 +- .../messageStream/MessageInlineVideo.tsx | 4 +- .../messageStream/bubbles/VideoBubble.tsx | 21 ++++-- .../hooks/useVideoPreviewSource.ts | 34 ++++++++++ .../utils/videoPreviewSource.test.ts | 45 +++++++++++++ .../messageStream/utils/videoPreviewSource.ts | 17 +++++ src/languageRoutes.ts | 52 +++++++++++++++ src/layouts/PublicLayout.tsx | 65 ++++++++++++------- src/pages/Categories/index.tsx | 4 +- src/pages/Home/index.tsx | 6 +- src/types/post.ts | 2 + src/useLocalizedPath.ts | 13 ++++ 16 files changed, 275 insertions(+), 47 deletions(-) create mode 100644 src/components/messageStream/hooks/useVideoPreviewSource.ts create mode 100644 src/components/messageStream/utils/videoPreviewSource.test.ts create mode 100644 src/components/messageStream/utils/videoPreviewSource.ts create mode 100644 src/useLocalizedPath.ts diff --git a/src/App.tsx b/src/App.tsx index 2dd6d7a..f113a4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,19 +35,11 @@ export default function App() { }> + {/* English (root, no prefix) */} } /> - {localizedHomeRoutes.map((route) => ( - - } - /> - ))} } /> } /> } /> + + {/* Each non-English language gets its own nested tree. */} + {localizedHomeRoutes.map((route) => ( + + + } + /> + } /> + } + /> + } + /> + } + /> + } /> + } + /> + } /> + + ))} {adminEnabled ? ( diff --git a/src/components/LatestUpdateRow.tsx b/src/components/LatestUpdateRow.tsx index 273991e..cc90c3d 100644 --- a/src/components/LatestUpdateRow.tsx +++ b/src/components/LatestUpdateRow.tsx @@ -2,6 +2,7 @@ import { Link } from "react-router-dom"; import type { Resource } from "../api"; import { CategoryIcon } from "./CategoryIcon"; import { useI18n } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import { resourceTypeLabel } from "../resourceTypeLabels"; import { formatDateYmd } from "../utils/format"; @@ -16,10 +17,11 @@ export function LatestUpdateRow({ iconKey: string; }) { const { t } = useI18n(); + const lp = useLocalizedPath(); const dateStr = formatDateYmd(r.updatedAt); return ( - +
- navigate(`/browse?sort=popular&post=${encodeURIComponent(post.id)}`) + navigate( + lp(`/browse?sort=popular&post=${encodeURIComponent(post.id)}`), + ) } aria-label={r.title} className="absolute inset-0 z-0 rounded-2xl outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/70" diff --git a/src/components/RecommendedCard.tsx b/src/components/RecommendedCard.tsx index b550652..549866f 100644 --- a/src/components/RecommendedCard.tsx +++ b/src/components/RecommendedCard.tsx @@ -4,6 +4,7 @@ import { Link } from "react-router-dom"; import type { Resource } from "../api"; import { assetUrl } from "../api"; import { useI18n } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import { useMemo, useState } from "react"; import { formatDateYmd } from "../utils/format"; import { DownloadCloudIcon } from "./icons/DownloadCloudIcon"; @@ -49,6 +50,7 @@ export function RecommendedCard({ layout?: "carousel" | "grid"; }) { const { t } = useI18n(); + const lp = useLocalizedPath(); const { showToast } = useToast(); const [isDownloading, setIsDownloading] = useState(false); const figmaCover = @@ -105,7 +107,7 @@ export function RecommendedCard({ }`} > diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx index 2a6f14a..220acc6 100644 --- a/src/components/SearchPanel.tsx +++ b/src/components/SearchPanel.tsx @@ -10,6 +10,7 @@ import { import { Link } from "react-router-dom"; import { getJSON, itemsOrEmpty, readJSONCache } from "../api"; import { langQuery, type Lang } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import type { Post, PostListResponse } from "../types/post"; import { MessageBubble } from "./messageStream/MessageBubble"; import { postDisplayText, postTitleText } from "./messageStream/utils/postText"; @@ -126,6 +127,7 @@ export function SearchPanel({ onResultClick, }: SearchPanelProps) { const inputRef = useRef(null); + const lp = useLocalizedPath(); const [tags, setTags] = useState([]); const [selectedTag, setSelectedTag] = useState(""); const [tagPosts, setTagPosts] = useState([]); @@ -334,7 +336,7 @@ export function SearchPanel({ return ( diff --git a/src/components/messageStream/MessageInlineVideo.tsx b/src/components/messageStream/MessageInlineVideo.tsx index 0a3809b..799de35 100644 --- a/src/components/messageStream/MessageInlineVideo.tsx +++ b/src/components/messageStream/MessageInlineVideo.tsx @@ -9,6 +9,7 @@ import { } from "react"; import type { Attachment } from "../../types/post"; import { AttachmentDownloadPill } from "./AttachmentDownloadPill"; +import { useVideoPreviewSource } from "./hooks/useVideoPreviewSource"; import { useVideoPlayer } from "./overlays/VideoPlayer"; function pad2(n: number): string { @@ -127,6 +128,7 @@ export function MessageInlineVideo({ const [snapProgress, setSnapProgress] = useState(false); const t = TOKENS[size]; + const videoSrc = useVideoPreviewSource(attachment); useEffect(() => { const v = videoRef.current; @@ -270,7 +272,7 @@ export function MessageInlineVideo({ <>