mirror of
https://github.com/Tony0410/News-reader-pro.git
synced 2026-05-24 21:31:44 +08:00
Splits article content into smaller audio segments. This allows for more granular control over playback, faster processing, and improved user experience by enabling auto-scrolling to the currently read segment. Updates `types.ts` to include `AudioSegment` interface and modify `Article` to hold `segments`, `currentSegmentIndex`, and `audioUrl` per segment. Introduces `segmentText` utility in `services/textUtils.ts` for robust text segmentation logic. Modifies `App.tsx` to utilize the new segmentation approach for fetching and processing audio. Enhances `components/ReaderView.tsx` to display and auto-scroll through segmented text, highlighting the current segment during playback.
84 lines
3.0 KiB
TypeScript
84 lines
3.0 KiB
TypeScript
|
|
import React, { useEffect, useRef } from 'react';
|
|
import { Article } from '../types';
|
|
import { FileText } from 'lucide-react';
|
|
|
|
interface ReaderViewProps {
|
|
article?: Article | null;
|
|
}
|
|
|
|
export const ReaderView: React.FC<ReaderViewProps> = ({ article }) => {
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Auto-scroll to active segment
|
|
useEffect(() => {
|
|
if (!article || article.status !== 'PLAYING') return;
|
|
|
|
const activeEl = document.getElementById(`segment-${article.currentSegmentIndex}`);
|
|
if (activeEl && scrollRef.current) {
|
|
activeEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
}, [article?.currentSegmentIndex, article?.status]);
|
|
|
|
if (!article) {
|
|
return (
|
|
<div className="h-full flex flex-col items-center justify-center text-slate-400 p-12 border-2 border-dashed border-slate-200 rounded-2xl bg-slate-50/50">
|
|
<FileText className="w-12 h-12 mb-4 opacity-50" />
|
|
<p className="text-lg font-medium">Select an article to read along</p>
|
|
<p className="text-sm">The text will appear here while you listen.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden h-[calc(100vh-12rem)] flex flex-col">
|
|
<div className="p-6 border-b border-slate-100 bg-white sticky top-0 z-10">
|
|
<h2 className="text-2xl font-bold text-slate-900 leading-tight">
|
|
{article.title}
|
|
</h2>
|
|
<a
|
|
href={article.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-sm text-blue-600 hover:underline mt-2 inline-block"
|
|
>
|
|
{new URL(article.url).hostname}
|
|
</a>
|
|
</div>
|
|
|
|
<div ref={scrollRef} className="flex-grow overflow-y-auto p-6 sm:p-8 space-y-6 custom-scrollbar bg-white">
|
|
{article.segments.length > 0 ? (
|
|
article.segments.map((segment, idx) => {
|
|
const isActive = article.currentSegmentIndex === idx;
|
|
return (
|
|
<p
|
|
key={segment.id}
|
|
id={`segment-${idx}`}
|
|
className={`text-lg leading-relaxed font-serif transition-colors duration-300 ${
|
|
isActive
|
|
? 'text-slate-900 bg-blue-50 p-2 rounded-lg -mx-2 border-l-4 border-blue-500'
|
|
: 'text-slate-700'
|
|
}`}
|
|
>
|
|
{segment.text}
|
|
</p>
|
|
);
|
|
})
|
|
) : (
|
|
// Loading State skeleton
|
|
<div className="space-y-4 animate-pulse">
|
|
{[1,2,3,4].map(i => (
|
|
<div key={i} className="space-y-2">
|
|
<div className="h-4 bg-slate-100 rounded w-full"></div>
|
|
<div className="h-4 bg-slate-100 rounded w-full"></div>
|
|
<div className="h-4 bg-slate-100 rounded w-3/4"></div>
|
|
</div>
|
|
))}
|
|
<p className="text-slate-400 italic mt-4">Extracting article content...</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|