From eced2890aee7a7faf85052126929d8ad6a7fa48b Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 11 Nov 2026 17:53:45 +0800 Subject: [PATCH] feat: complete YouTube-style site experience for YMB - rebuild home feed with sticky server-driven categories and skeletons - add full-screen shorts feed with likes, comments, saves and follows - rebuild watch page with same-drama episode swiping and locked states - wire search, watch history and episode stats to the app API - add about page with app store links and an upload video dialog - add SVG language flags, formatted view counts and site icons --- index.html | 3 + public/apple-touch-icon.png | Bin 0 -> 13128 bytes public/favicon-192x192.png | Bin 0 -> 14413 bytes public/favicon-32x32.png | Bin 0 -> 1441 bytes src/app/App.jsx | 4 +- src/components/CommentsSheet.jsx | 81 +++++ src/components/LanguageFlag.jsx | 40 +++ src/components/LanguageSwitcher.jsx | 41 ++- src/components/MediaCard.jsx | 9 +- src/components/SectionHeading.jsx | 2 +- src/components/Skeletons.jsx | 104 ++++++ src/components/UploadVideoDialog.jsx | 100 ++++++ src/components/player/VideoPlayer.jsx | 8 +- src/hooks/useFollowedChannels.js | 29 ++ src/i18n/about.js | 93 ++++++ src/i18n/index.js | 6 +- src/layouts/AppShell.jsx | 107 ++++-- src/lib/format.js | 39 +++ src/pages/AboutPage.jsx | 100 ++++++ src/pages/HistoryPage.jsx | 71 ++++ src/pages/HomePage.jsx | 127 ++++--- src/pages/ListingPage.jsx | 19 +- src/pages/SearchPage.jsx | 39 ++- src/pages/ShortsFeedPage.jsx | 242 +++++++++++++- src/pages/WatchPage.jsx | 247 ++++++++++++-- src/services/appApi.js | 20 ++ src/services/device.js | 21 ++ src/services/httpClient.js | 4 +- src/services/normalizers.js | 21 +- src/services/watchProgress.js | 21 ++ src/youtube-overrides.css | 455 ++++++++++++++++++++++---- 31 files changed, 1858 insertions(+), 195 deletions(-) create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon-192x192.png create mode 100644 public/favicon-32x32.png create mode 100644 src/components/CommentsSheet.jsx create mode 100644 src/components/LanguageFlag.jsx create mode 100644 src/components/Skeletons.jsx create mode 100644 src/components/UploadVideoDialog.jsx create mode 100644 src/hooks/useFollowedChannels.js create mode 100644 src/i18n/about.js create mode 100644 src/lib/format.js create mode 100644 src/pages/AboutPage.jsx create mode 100644 src/pages/HistoryPage.jsx create mode 100644 src/services/device.js create mode 100644 src/services/watchProgress.js diff --git a/index.html b/index.html index c04af66..a4fbf30 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,9 @@ + + + diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..288b45590e42e846395d2bfccf6128d6e4aad6fa GIT binary patch literal 13128 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 literal 0 HcmV?d00001 diff --git a/public/favicon-192x192.png b/public/favicon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bf2e5be0b4fc842b17831a9242735357b2e508 GIT binary patch literal 14413 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 literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..74622df59cad7c61ff7c01b33edff024452e252f GIT binary patch literal 1441 zcmV;S1z!4zP)4!d4ZD>&u6+bFMp-(<&5g$|(ABsMR zZ}vfa7pX#}Vo`#k5Smu3m|}`))qX(JkEF>a*_~vwJ2UsnnVl`0%_bPzU=J+J&fI%` z=bm%#Ip-1<7Xlyvc>KpOjHYln+za3_0NqNdW+F2An^MXqqN@PT0QkOGEcWH|`D=k5 z4S?ifi$Q9M2qqzwN^O=>z6#(eB5LG!O4ZZ_qLMnL)EIzcLWskYlaptwb;~T!^H{a00HgVw-c(BM&1SQ| zRvJN-@kt~S>x2*|iKt#F(ZqMa%f0dacFoIfIS_~pOx&m+(hO-g% z%wSfTE6PVLo6Wv2gy^bp`<)gx!eIupf)EJb3ez+T`v@K2KGd0n<` zpJ$j7LcGB-3I^N;oIt))mlyyjW--%$j>qGxbX_0joO8PdaP*XPAv9yjVF6>(b={3Z z*Y&+bl&VM~6|2!}tSE?*|GxCh<2-K`on4=xqw_;ZS%45>5NV5fK{Austfe=wsv^H~ z7IRT7PWXyTu;4ukP|}8Ua!{&RdX_enbWqpSjdaHoaOUPvDBOfo%v3NJc!7VtdQe!( zn=gPD5s9>5P5Vn2ANvk=?hG`|fRsh(+Incw7HDPx#iG;^bS!7PGJInUpR>Ru4Xi@eH<~X2kG^%qoJ*UkyFn?%?zVs=N61ze+Sw87ij1> zfK}<8NJd66_)`!1k2mAW$fsD>`vQ_p+u#;+3-%MUYn~a7e<+f~jf-C+>s~u8?ob&M%dGn(4tc?nxb$du(~ye=5E5tD{)-8@F$A7LAY}c#zua`J?j9oAr0xe zOkBVc+)UKmszQe>&fxbGA7DK>2%9$kgj_TtY*Gg=b#WaART; zf1EXuZhHl>wa;Ts?_nghX^dYy1uYz}6#|>fC3h|xs&fDjrf?WjX9pmfZ0PzqWG)Y& z|M)S4bRCxljv_kJ10f3-KJyD6J$C{a_zc%a)2KJMqj~dgw52oX`{*F-iED_)6HxM2 z74})orerdCgovI~CG&0_T#lFHWU=+Zuh7!|B>MZdVRYnYgd+wb(FnY`8UJ`{5d+0S z0n(EQha^bEpvZ!eFyZB|!nS7v_`KD7M8w|zlJ`JgxiVEm2A-EkBGH9d>^=;geiPZu zX(Utih!|17CDRNOB4YT@;YbA0cnlOW{Us1(Fgtw-MVDa2t!lWxzTnrlWGd!N#)*iN zjHdHRt=$XO*g>TY=XX}>} />} />} />} />} />} />} />; } +export function App() { return }>} />} />} />} />} />} />} />} />; } diff --git a/src/components/CommentsSheet.jsx b/src/components/CommentsSheet.jsx new file mode 100644 index 0000000..d27771b --- /dev/null +++ b/src/components/CommentsSheet.jsx @@ -0,0 +1,81 @@ +// Author: fruit.com.cn +import { useEffect, useState } from 'react'; +import { Send, X } from 'lucide-react'; +import { appApi } from '../services/appApi'; + +function formatTime(value, htmlLang) { + if (!value) return ''; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ''; + return date.toLocaleString(htmlLang, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); +} + +export function CommentsSheet({ video, locale, onClose, onPosted }) { + const [comments, setComments] = useState(null); + const [error, setError] = useState(''); + const [text, setText] = useState(''); + const [sending, setSending] = useState(false); + + const loadComments = () => appApi.comments({ dramaId: video.id, episodeId: video.preview?.id }) + .then((payload) => setComments(payload?.list || [])) + .catch((loadError) => { setError(loadError.message || 'Request failed'); setComments([]); }); + + useEffect(() => { + let active = true; + setComments(null); + setError(''); + appApi.comments({ dramaId: video.id, episodeId: video.preview?.id }) + .then((payload) => active && setComments(payload?.list || [])) + .catch((loadError) => active && setError(loadError.message || 'Request failed')); + return () => { active = false; }; + }, [video.id, video.preview?.id]); + + const submit = async (event) => { + event.preventDefault(); + const content = text.trim(); + if (!content || sending) return; + setSending(true); + setError(''); + try { + await appApi.publishComment({ dramaId: video.id, episodeId: video.preview?.id, content }); + setText(''); + await loadComments(); + onPosted?.(); + } catch (postError) { + setError(postError.message || 'Request failed'); + } finally { + setSending(false); + } + }; + + return ( + <> + + +
+ {comments === null &&

{locale.loading}

} + {error &&

{error}

} + {comments && !comments.length && !error &&

{locale.noComments}

} + {comments?.map((comment) => ( +
+ {comment.nickname?.[0] || 'Y'} +
+ {comment.nickname || 'Guest'} +

{comment.content}

+ {formatTime(comment.createdAt, locale.htmlLang)} +
+
+ ))} +
+
+ setText(event.target.value)} placeholder={locale.commentPlaceholder} aria-label={locale.commentPlaceholder} /> + +
+ + + ); +} diff --git a/src/components/LanguageFlag.jsx b/src/components/LanguageFlag.jsx new file mode 100644 index 0000000..a968b00 --- /dev/null +++ b/src/components/LanguageFlag.jsx @@ -0,0 +1,40 @@ +// Author: fruit.com.cn +// 用内联 SVG 绘制国旗,避免 Windows 等系统不渲染 emoji 国旗 +const STAR = 'M0,-1 L0.2245,-0.309 L0.951,-0.309 L0.363,0.118 L0.588,0.809 L0,0.382 L-0.588,0.809 L-0.363,0.118 L-0.951,-0.309 L-0.2245,-0.309 Z'; + +const FLAGS = { + id: ( + + ), + en: ( + + ), + zh: ( + + ), +}; + +export function LanguageFlag({ code }) { + return {FLAGS[code] || FLAGS.id}; +} diff --git a/src/components/LanguageSwitcher.jsx b/src/components/LanguageSwitcher.jsx index 4e8f778..5c9ed9f 100644 --- a/src/components/LanguageSwitcher.jsx +++ b/src/components/LanguageSwitcher.jsx @@ -1,10 +1,43 @@ // Author: fruit.com.cn -import { Check, ChevronDown, Globe2 } from 'lucide-react'; +import { Check, ChevronDown } from 'lucide-react'; import { useState } from 'react'; import { useAppStore } from '../app/store'; +import { LanguageFlag } from './LanguageFlag'; + +const languages = [ + { code: 'id', label: 'Bahasa Indonesia', short: 'ID' }, + { code: 'en', label: 'English', short: 'EN' }, + { code: 'zh', label: '简体中文', short: '中文' }, +]; +const titles = { id: 'Bahasa', en: 'Language', zh: '语言' }; -const languages = [{ code: 'id', label: 'Bahasa Indonesia', short: 'ID', flag: '🇮🇩' }, { code: 'en', label: 'English', short: 'EN', flag: '🇺🇸' }, { code: 'zh', label: '简体中文', short: '中文', flag: '🇨🇳' }]; export function LanguageSwitcher() { - const { lang, setLang } = useAppStore(); const [open, setOpen] = useState(false); const current = languages.find((item) => item.code === lang) || languages[0]; - return
{open && <>)}
}; + const { lang, setLang } = useAppStore(); + const [open, setOpen] = useState(false); + const current = languages.find((item) => item.code === lang) || languages[0]; + const title = titles[lang] || 'Language'; + return ( +
+ + {open && ( + <> + + ))} +
+ + )} + + ); } diff --git a/src/components/MediaCard.jsx b/src/components/MediaCard.jsx index f08238e..2ce0147 100644 --- a/src/components/MediaCard.jsx +++ b/src/components/MediaCard.jsx @@ -1,8 +1,13 @@ // Author: fruit.com.cn import { MoreVertical, Play } from 'lucide-react'; import { Link } from 'react-router-dom'; +import { useAppStore } from '../app/store'; +import { daysAgoLabel, formatViews } from '../lib/format'; -export function MediaCard({ video, compact = false }) { +export function MediaCard({ video, compact = false, to }) { + const { locale } = useAppStore(); const image = video.image?.startsWith('/') || video.image?.startsWith('http') ? video.image : `/assets/${video.image}`; - return
{video.title}{video.duration}{compact ? {video.title} :
{video.channel?.[0] || 'Y'}
{video.title}{video.channel}
{video.views} · 2 hari lalu
}
; + const target = to || `/watch/${video.id}`; + const meta = [formatViews(video.views, locale.code), daysAgoLabel(locale.code)].filter(Boolean).join(' · '); + return
{video.title}{video.duration}{compact ? {video.title} :
{video.channel?.[0] || 'Y'}
{video.title}{video.channel}
{meta}
}
; } diff --git a/src/components/SectionHeading.jsx b/src/components/SectionHeading.jsx index 0e0380d..35c0474 100644 --- a/src/components/SectionHeading.jsx +++ b/src/components/SectionHeading.jsx @@ -1,2 +1,2 @@ // Author: fruit.com.cn -export function SectionHeading({ title, action = 'Lihat semua →' }) { return

{title}

; } +export function SectionHeading({ title, action = null }) { return

{title}

{action && }
; } diff --git a/src/components/Skeletons.jsx b/src/components/Skeletons.jsx new file mode 100644 index 0000000..dd03054 --- /dev/null +++ b/src/components/Skeletons.jsx @@ -0,0 +1,104 @@ +// Author: fruit.com.cn +function Skeleton({ className = '' }) { + return