import React, { useState, useEffect } from 'react'; import { BookOpen, ChevronLeft, ChevronRight, Copy, ArrowRight, Download, } from 'lucide-react'; import { toast } from 'sonner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; import { useAppStore } from '@/store'; import { Plugin } from '@/types/plugin'; import { useEpubExport } from '@/hooks/useEpubExport'; type ParseNovelSectionProps = { onNavigateToParseChapter?: () => void; }; export default function ParseNovelSection({ onNavigateToParseChapter, }: ParseNovelSectionProps) { const plugin = useAppStore(state => state.plugin); const parseNovelPath = useAppStore(state => state.parseNovelPath); const shouldAutoSubmitNovel = useAppStore( state => state.shouldAutoSubmitNovel, ); const clearParseNovelPath = useAppStore(state => state.clearParseNovelPath); const setParseChapterPath = useAppStore(state => state.setParseChapterPath); const [novelPath, setNovelPath] = useState(''); const [sourceNovel, setSourceNovel] = useState< (Plugin.SourceNovel & { totalPages?: number }) | undefined >(); const [chapters, setChapters] = useState([]); const [loading, setLoading] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [fetchError, setFetchError] = useState(''); const { exportEpub, isExporting } = useEpubExport({ plugin: plugin || null, sourceNovel, chapters, novelPath, }); const fetchNovelByPath = async (path: string) => { if (plugin && path.trim()) { setLoading(true); setFetchError(''); try { const result = await plugin.parseNovel(path); setSourceNovel(result); setChapters(result.chapters || []); setCurrentPage(1); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to fetch novel'; setFetchError(errorMessage); console.error('Error parsing novel:', error); } finally { setLoading(false); } } }; const fetchNovel = async () => { await fetchNovelByPath(novelPath); }; const fetchPage = async (page: number) => { if (plugin && novelPath && sourceNovel?.totalPages) { setLoading(true); setFetchError(''); try { const result = await (plugin as Plugin.PagePlugin).parsePage( novelPath, page.toString(), ); setChapters(result.chapters); setCurrentPage(page); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to fetch page'; setFetchError(errorMessage); console.error('Error fetching page:', error); } finally { setLoading(false); } } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && novelPath.trim()) { fetchNovel(); } }; const copyToClipboard = (text?: string, label?: string) => { if (text) { navigator.clipboard.writeText(text); toast.success(`${label || 'Text'} copied to clipboard!`); } }; const handleParseChapter = (path: string) => { setParseChapterPath(path, true); onNavigateToParseChapter?.(); }; useEffect(() => { if (parseNovelPath) { setNovelPath(parseNovelPath); if (shouldAutoSubmitNovel && plugin) { fetchNovelByPath(parseNovelPath); } clearParseNovelPath(); } }, [parseNovelPath, shouldAutoSubmitNovel, plugin, clearParseNovelPath]); const formatDate = (dateString?: string | null) => { if (!dateString) return 'N/A'; try { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } catch { return dateString; } }; return (

Parse Novel

{plugin ? 'Enter a novel path to fetch details' : 'Select a plugin to parse novels'}

setNovelPath(e.target.value)} onKeyPress={handleKeyPress} className="flex-1" disabled={!plugin} />
{fetchError && (

{fetchError}

)} {loading && !sourceNovel ? (
) : !sourceNovel ? (

{plugin ? 'Ready to parse' : 'No plugin selected'}

{plugin ? 'Enter a novel path in the field above and click "Fetch" to load detailed information, including chapters, metadata, and more.' : 'Please select a plugin from the sidebar to get started.'}

) : sourceNovel ? (
{/* Novel Info */}
copyToClipboard(sourceNovel.cover, 'Cover URL') } > {sourceNovel.name}

{sourceNovel.name}

{sourceNovel.status && (

Status

{sourceNovel.status}
)} {sourceNovel.author && (

Author

{sourceNovel.author}

)} {sourceNovel.artist && (

Artist

{sourceNovel.artist}

)} {sourceNovel.rating && (

Rating

{sourceNovel.rating.toFixed(1)} / 5.0

)}

Copy novel path to clipboard

{isExporting ? 'Exporting chapters to EPUB...' : 'Export all chapters as EPUB file'}

{sourceNovel.genres && (

Genres

{sourceNovel.genres.split(/,\s*/).map((genre, index) => ( {genre} ))}
)} {sourceNovel.summary && (

Summary

{sourceNovel.summary}

)}
{/* Sidebar Info */}

Metadata

Total Chapters

{chapters.length}

{sourceNovel.totalPages && (

Total Pages

{sourceNovel.totalPages}

)} {chapters.length > 0 && chapters[0].releaseTime && (

First Chapter

{formatDate(chapters[0].releaseTime)}

)} {chapters.length > 0 && chapters[chapters.length - 1].releaseTime && (

Last Updated

{formatDate( chapters[chapters.length - 1].releaseTime, )}

)}
{/* Chapters Table */} {(chapters.length > 0 || sourceNovel.totalPages) && (

Chapters ({chapters.length})

{sourceNovel.totalPages && sourceNovel.totalPages > 1 && (
Page {currentPage} of {sourceNovel.totalPages}
)} {sourceNovel.totalPages && ( )}
{chapters.some(ch => ch.chapterNumber) && ( )} {chapters.map((chapter, index) => ( {chapters.some(ch => ch.chapterNumber) && ( )} ))}
# Name Actions Release Time Chapter #
{index} {chapter.name}

Copy chapter path

Open in Parse Chapter tab

{formatDate(chapter.releaseTime)} {chapter.chapterNumber || '-'}
)}
) : null}
); }