diff --git a/components/InputBar.tsx b/components/InputBar.tsx index 8809662..c0b3373 100644 --- a/components/InputBar.tsx +++ b/components/InputBar.tsx @@ -17,13 +17,22 @@ const isSessionJsonUrl = (url: string): boolean => { ); }; +type InputMode = 'url' | 'paste'; + const InputBar: React.FC = () => { + const [mode, setMode] = useState('url'); const [url, setUrl] = useState(''); const [importProgress, setImportProgress] = useState(null); const [isImporting, setIsImporting] = useState(false); const fileInputRef = React.useRef(null); + // Paste text state + const [pasteTitle, setPasteTitle] = useState(''); + const [pasteContent, setPasteContent] = useState(''); + const [pasteLanguage, setPasteLanguage] = useState(''); + const handleFetch = useAppStore(state => state.handleFetch); + const importCustomText = useAppStore(state => state.importCustomText); const isLoading = useAppStore(state => state.isLoading.fetching); const error = useAppStore(state => state.error); const setError = useAppStore(state => state.setError); @@ -115,10 +124,23 @@ const InputBar: React.FC = () => { }; const handleExampleClick = (exampleUrl: string) => { + setMode('url'); setUrl(exampleUrl); handleFetch(exampleUrl); }; + const handlePasteSubmit = async () => { + if (!pasteContent.trim()) return; + await importCustomText( + pasteTitle.trim() || 'Custom Text', + pasteContent, + pasteLanguage.trim() || undefined, + ); + setPasteTitle(''); + setPasteContent(''); + setPasteLanguage(''); + }; + const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -174,39 +196,34 @@ const InputBar: React.FC = () => { const isAnyLoading = isLoading || isImporting; + const tabClass = (active: boolean) => + `px-4 py-2 text-sm font-medium rounded-t-lg transition ${ + active + ? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 border-b-2 border-blue-500' + : 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200' + }`; + return ( -
-
-
- setUrl(e.target.value)} - placeholder="Paste chapter URL or session JSON file URL to start reading..." - className="flex-grow w-full px-4 py-2 text-gray-800 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 border-2 border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition placeholder:text-gray-400 dark:placeholder:text-gray-500" - disabled={isAnyLoading} - /> -
- - -
-
-
+
+ {/* Tabs */} +
+ + +
{/* Hidden file input */} { className="hidden" /> - {/* Import Progress Bar */} - {importProgress && ( -
-
- - {importProgress.stage === 'downloading' && '📥 Downloading'} - {importProgress.stage === 'parsing' && '📋 Parsing'} - {importProgress.stage === 'importing' && '💾 Importing'} - {importProgress.stage === 'streaming' && '🌊 Streaming'} - {importProgress.stage === 'complete' && '✅ Complete'} - - - {importProgress.stage === 'streaming' && importProgress.chaptersLoaded !== undefined - ? `${importProgress.chaptersLoaded}${importProgress.totalChapters ? `/${importProgress.totalChapters}` : ''} chapters` - : `${importProgress.progress.toFixed(0)}%` - } - -
-
-
-
- {importProgress.message && ( -

- {importProgress.message} -

- )} - {importProgress.canStartReading && ( -

- ✓ You can start reading now! Remaining chapters loading in background... -

- )} -
- )} -
- Find the novel you want to read from these supported websites: - {' '} - {/* Group websites by category */} - {Object.entries( - SUPPORTED_WEBSITES_CONFIG.reduce((acc, site) => { - if (!acc[site.category]) acc[site.category] = []; - acc[site.category].push(site); - return acc; - }, {} as Record) - ).map(([category, sites], categoryIndex, categories) => ( - - {sites.map((site, siteIndex) => ( - - { - e.preventDefault(); - handleExampleClick(site.exampleUrl); - }} +
+ {/* URL Mode */} + {mode === 'url' && ( + <> +
+
+ setUrl(e.target.value)} + placeholder="Paste chapter URL or session JSON file URL to start reading..." + className="flex-grow w-full px-4 py-2 text-gray-800 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 border-2 border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition placeholder:text-gray-400 dark:placeholder:text-gray-500" + disabled={isAnyLoading} + /> +
+ + +
+
+
+ + {/* Import Progress Bar */} + {importProgress && ( +
+
+ + {importProgress.stage === 'downloading' && '📥 Downloading'} + {importProgress.stage === 'parsing' && '📋 Parsing'} + {importProgress.stage === 'importing' && '💾 Importing'} + {importProgress.stage === 'streaming' && '🌊 Streaming'} + {importProgress.stage === 'complete' && '✅ Complete'} + + + {importProgress.stage === 'streaming' && importProgress.chaptersLoaded !== undefined + ? `${importProgress.chaptersLoaded}${importProgress.totalChapters ? `/${importProgress.totalChapters}` : ''} chapters` + : `${importProgress.progress.toFixed(0)}%` + } + +
+
+
+
+ {importProgress.message && ( +

+ {importProgress.message} +

+ )} + {importProgress.canStartReading && ( +

+ ✓ You can start reading now! Remaining chapters loading in background... +

+ )} +
+ )} + +
+ + )} + + {/* Paste Text Mode */} + {mode === 'paste' && ( +
+
+
+ + setPasteTitle(e.target.value)} + placeholder="Custom Text" + className="w-full px-3 py-2 text-gray-800 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition" + disabled={isAnyLoading} + /> +
+
+ + setPasteLanguage(e.target.value)} + placeholder="e.g., Chinese" + className="w-full px-3 py-2 text-gray-800 dark:text-gray-200 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition" + disabled={isAnyLoading} + /> +
+
+ +
+ +