import React, { useEffect, useRef } from 'react'; import { Article, ReaderSettings } from '../types'; import { FileText, MousePointerClick, Share2, Quote } from 'lucide-react'; import { getDisplayUrl } from '../utils/url'; interface ReaderViewProps { article?: Article | null; settings?: ReaderSettings; onToggleAutoScroll?: () => void; onSegmentSelect?: (index: number) => void; } export const ReaderView: React.FC = ({ article, settings, onToggleAutoScroll, onSegmentSelect }) => { const scrollRef = useRef(null); // Auto-scroll to active segment useEffect(() => { if (!article || article.status !== 'PLAYING' || settings?.autoScroll === false) return; const activeEl = document.getElementById(`segment-${article.currentSegmentIndex}`); if (activeEl && scrollRef.current) { activeEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, [article?.currentSegmentIndex, article?.status, settings?.autoScroll]); // Default settings fallback const s = settings || { isDarkMode: false, fontSize: 'lg', lineHeight: 'relaxed', fontFamily: 'serif', autoScroll: true, readingTone: 'clean', pageWidth: 'standard', zenMode: false }; const getFontClass = () => { switch(s.fontFamily) { case 'sans': return 'font-sans'; case 'mono': return 'font-mono'; default: return 'font-serif'; } }; const getSizeClass = () => { switch(s.fontSize) { case 'sm': return 'text-sm'; case 'base': return 'text-base'; case 'xl': return 'text-xl'; case '2xl': return 'text-2xl'; default: return 'text-lg'; } }; const getLeadingClass = () => { switch(s.lineHeight) { case 'normal': return 'leading-normal'; case 'loose': return 'leading-loose'; default: return 'leading-relaxed'; } }; const getToneClasses = () => { switch (s.readingTone) { case 'sepia': return 'bg-amber-50/60 dark:bg-amber-900/30 border border-amber-100 dark:border-amber-800'; case 'night': return 'bg-slate-900 text-slate-100 border border-slate-800'; default: return 'bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800'; } }; const getWidthClass = () => { switch (s.pageWidth) { case 'cozy': return 'max-w-2xl mx-auto'; case 'wide': return 'max-w-6xl mx-auto'; default: return 'max-w-4xl mx-auto'; } }; const handleShareMoment = (text: string, index: number) => { const base = typeof window !== 'undefined' ? `${window.location.origin}${window.location.pathname}` : ''; const shareUrl = `${base}?segment=${index}`; const payload = `"${text.trim()}"\n${shareUrl}`; if (navigator?.clipboard?.writeText) { navigator.clipboard.writeText(payload).catch(() => console.warn('Unable to copy share link')); } }; if (!article) { return (

Select an article to read along

The text will appear here while you listen.

); } const displayUrl = getDisplayUrl(article.url); return (
{!s.zenMode && (

{article.title}

{displayUrl.hostname}
{/* Auto Scroll Toggle */}
)}
{article.segments.length > 0 ? ( article.segments.map((segment, idx) => { const isActive = article.currentSegmentIndex === idx; const isBuffering = isActive && article.status === 'PLAYING' && !segment.audioUrl; return (
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 ${isActive ? `bg-blue-50 dark:bg-blue-900/20 border-blue-500 shadow-sm ${isBuffering ? 'animate-pulse opacity-70' : 'text-slate-900 dark:text-white'}` : '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}

); }) ) : ( // Loading State skeleton
{[1,2,3,4].map(i => (
))}

Extracting article content...

)}
); };