From dadebf8cd0ab70c85ea4cade919e5006ff97e58a Mon Sep 17 00:00:00 2001 From: Anthony <47945770+Tony0410@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:28:14 +0800 Subject: [PATCH] feat: Enhance article segment navigation Implement segment selection in ReaderView for user-driven playback control. This change allows users to click on specific segments within an article to jump to and play that segment directly. The Gemini service's HTML parsing has also been simplified by removing redundant selectors and focusing on essential tag removal for more efficient text extraction. --- App.tsx | 14 ++++++++++++ components/ReaderView.tsx | 20 ++++++++++------ services/geminiService.ts | 48 +++++++-------------------------------- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/App.tsx b/App.tsx index 8f8a46c..bd1b7dc 100644 --- a/App.tsx +++ b/App.tsx @@ -198,6 +198,18 @@ export default function App() { }); }, [playerState.currentArticleId]); + const handleSegmentSelect = useCallback((articleId: string, index: number) => { + setPlayerState(prev => ({ + ...prev, + currentArticleId: articleId, + isPlaying: true + })); + updateArticle(articleId, { + currentSegmentIndex: index, + status: PlaybackStatus.PLAYING + }); + }, []); + // -- Keyboard Shortcuts -- useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -478,6 +490,7 @@ export default function App() { article={viewingArticle} settings={settings} onToggleAutoScroll={() => setSettings(s => ({...s, autoScroll: !s.autoScroll}))} + onSegmentSelect={(index) => viewingArticle && handleSegmentSelect(viewingArticle.id, index)} /> @@ -490,6 +503,7 @@ export default function App() { article={viewingArticle} settings={settings} onToggleAutoScroll={() => setSettings(s => ({...s, autoScroll: !s.autoScroll}))} + onSegmentSelect={(index) => viewingArticle && handleSegmentSelect(viewingArticle.id, index)} /> diff --git a/components/ReaderView.tsx b/components/ReaderView.tsx index 01fdb71..4b0cb9a 100644 --- a/components/ReaderView.tsx +++ b/components/ReaderView.tsx @@ -7,9 +7,10 @@ interface ReaderViewProps { article?: Article | null; settings?: ReaderSettings; onToggleAutoScroll?: () => void; + onSegmentSelect?: (index: number) => void; } -export const ReaderView: React.FC = ({ article, settings, onToggleAutoScroll }) => { +export const ReaderView: React.FC = ({ article, settings, onToggleAutoScroll, onSegmentSelect }) => { const scrollRef = useRef(null); // Auto-scroll to active segment @@ -100,7 +101,7 @@ export const ReaderView: React.FC = ({ article, settings, onTog
{article.segments.length > 0 ? ( article.segments.map((segment, idx) => { @@ -109,11 +110,16 @@ export const ReaderView: React.FC = ({ article, settings, onTog
onSegmentSelect?.(idx)} + title="Click to play from here" + className={` + transition-all duration-200 whitespace-pre-wrap rounded-xl p-3 sm:p-4 -mx-2 sm:-mx-4 border-l-4 mb-2 + ${getLeadingClass()} + ${isActive + ? 'text-slate-900 dark:text-white bg-blue-50 dark:bg-blue-900/20 border-blue-500 shadow-sm' + : 'text-slate-700 dark:text-slate-300 border-transparent hover:bg-slate-100 dark:hover:bg-slate-800/50 cursor-pointer hover:border-slate-300 dark:hover:border-slate-600' + } + `} > {segment.text}
diff --git a/services/geminiService.ts b/services/geminiService.ts index 6b96f80..5ba85fb 100644 --- a/services/geminiService.ts +++ b/services/geminiService.ts @@ -49,52 +49,20 @@ function cleanAndMinifyHtml(rawHtml: string): string { const doc = parser.parseFromString(rawHtml, 'text/html'); // 1. Remove heavy technical tags + // We remove these because they consume tokens and provide no semantic value for text extraction. const technicalTags = ['script', 'style', 'noscript', 'iframe', 'svg', 'link', 'meta', 'button', 'input', 'form', 'img', 'picture', 'video']; technicalTags.forEach(tag => { const elements = doc.querySelectorAll(tag); elements.forEach(el => el.remove()); }); - // 2. Remove semantic layout tags that are usually clutter - const layoutTags = ['nav', 'footer', 'aside', 'header']; - layoutTags.forEach(tag => { - const elements = doc.querySelectorAll(tag); - elements.forEach(el => el.remove()); - }); + // NOTE: We intentionally DO NOT remove semantic tags like