diff --git a/apps/readest-app/next.config.mjs b/apps/readest-app/next.config.mjs index 03ca0e84..0302f5da 100644 --- a/apps/readest-app/next.config.mjs +++ b/apps/readest-app/next.config.mjs @@ -7,7 +7,7 @@ const internalHost = process.env.TAURI_DEV_HOST || 'localhost'; const nextConfig = { // Ensure Next.js uses SSG instead of SSR // https://nextjs.org/docs/pages/building-your-application/deploying/static-exports - output: 'export', + output: process.env['NEXT_PUBLIC_APP_PLATFORM'] === 'web' ? undefined : 'output', // Note: This feature is required to use the Next.js Image component in SSG mode. // See https://nextjs.org/docs/messages/export-image-api for different workarounds. images: { diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 232b4a85..4b0a34a3 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -1,6 +1,6 @@ { "name": "@readest/readest-app", - "version": "0.8.1", + "version": "0.8.2", "private": true, "scripts": { "dev": "dotenv -e .env.tauri -- next dev", diff --git a/apps/readest-app/src/app/library/components/Bookshelf.tsx b/apps/readest-app/src/app/library/components/Bookshelf.tsx index 45530b1d..6bdcdac3 100644 --- a/apps/readest-app/src/app/library/components/Bookshelf.tsx +++ b/apps/readest-app/src/app/library/components/Bookshelf.tsx @@ -11,6 +11,7 @@ import Image from 'next/image'; import { Book, BooksGroup } from '@/types/book'; import { useEnv } from '@/context/EnvContext'; import { useLibraryStore } from '@/store/libraryStore'; +import { navigateToReader } from '@/utils/nav'; import Alert from '@/components/Alert'; import Spinner from '@/components/Spinner'; @@ -68,7 +69,7 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp setClickedImage(id); setTimeout(() => setClickedImage(null), 300); setTimeout(() => setLoading(true), 200); - router.push(`/reader?ids=${id}`); + navigateToReader(router, [id]); } }; @@ -80,7 +81,7 @@ const Bookshelf: React.FC = ({ libraryBooks, isSelectMode, onImp const openSelectedBooks = () => { setTimeout(() => setLoading(true), 200); - router.push(`/reader?ids=${selectedBooks.join(',')}`); + navigateToReader(router, selectedBooks); }; const confirmDelete = () => { diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index e8c63824..f436df84 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { Book } from '@/types/book'; import { AppService } from '@/types/system'; +import { navigateToReader } from '@/utils/nav'; import { parseOpenWithFiles } from '@/helpers/cli'; import { isTauriAppPlatform } from '@/services/environment'; import { FILE_ACCEPT_FORMATS, SUPPORTED_FILE_EXTS } from '@/services/constants'; @@ -47,7 +48,7 @@ const LibraryPage = () => { console.log('Opening books:', bookIds); if (bookIds.length > 0) { - router.push(`/reader?ids=${bookIds.join(',')}`); + navigateToReader(router, bookIds); } }, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/apps/readest-app/src/app/page.tsx b/apps/readest-app/src/app/page.tsx index bf52f844..b1e44f0c 100644 --- a/apps/readest-app/src/app/page.tsx +++ b/apps/readest-app/src/app/page.tsx @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; +import { navigateToLibrary } from '@/utils/nav'; import Spinner from '@/components/Spinner'; @@ -9,7 +10,7 @@ const HomePage = () => { const router = useRouter(); useEffect(() => { - router.push('/library'); + navigateToLibrary(router); }, [router]); return ; diff --git a/apps/readest-app/src/app/reader/[ids]/page.tsx b/apps/readest-app/src/app/reader/[ids]/page.tsx new file mode 100644 index 00000000..94c4a8d9 --- /dev/null +++ b/apps/readest-app/src/app/reader/[ids]/page.tsx @@ -0,0 +1,6 @@ +import Reader from '../components/Reader'; + +export default async function Page({ params }: { params: Promise<{ ids: string }> }) { + const ids = decodeURIComponent((await params).ids); + return ; +} diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx new file mode 100644 index 00000000..6880ad2d --- /dev/null +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -0,0 +1,46 @@ +'use client'; + +import * as React from 'react'; +import { useEffect, Suspense, useRef } from 'react'; + +import { useEnv } from '@/context/EnvContext'; +import { useLibraryStore } from '@/store/libraryStore'; + +import ReaderContent from './ReaderContent'; +import { AboutWindow } from '@/components/AboutWindow'; +import { useSettingsStore } from '@/store/settingsStore'; + +const Reader: React.FC<{ ids?: string }> = ({ ids }) => { + const { envConfig } = useEnv(); + const { settings, setSettings } = useSettingsStore(); + const { library, setLibrary } = useLibraryStore(); + const isInitiating = useRef(false); + + useEffect(() => { + if (isInitiating.current) return; + isInitiating.current = true; + const initLibrary = async () => { + const appService = await envConfig.getAppService(); + const settings = await appService.loadSettings(); + setSettings(settings); + setLibrary(await appService.loadLibraryBooks()); + }; + + initLibrary(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + library.length > 0 && + settings.globalReadSettings && ( +
+ + + + +
+ ) + ); +}; + +export default Reader; diff --git a/apps/readest-app/src/app/reader/components/ReaderContent.tsx b/apps/readest-app/src/app/reader/components/ReaderContent.tsx index c52c0a7e..cb2f5751 100644 --- a/apps/readest-app/src/app/reader/components/ReaderContent.tsx +++ b/apps/readest-app/src/app/reader/components/ReaderContent.tsx @@ -13,6 +13,8 @@ import { SystemSettings } from '@/types/settings'; import { parseOpenWithFiles } from '@/helpers/cli'; import { tauriHandleClose } from '@/utils/window'; import { uniqueId } from '@/utils/misc'; +import { navigateToLibrary } from '@/utils/nav'; +import { BOOK_IDS_SEPARATOR } from '@/services/constants'; import useBooksManager from '../hooks/useBooksManager'; import useBookShortcuts from '../hooks/useBookShortcuts'; @@ -21,7 +23,7 @@ import SideBar from './sidebar/SideBar'; import Notebook from './notebook/Notebook'; import BooksGrid from './BooksGrid'; -const ReaderContent: React.FC<{ settings: SystemSettings }> = ({ settings }) => { +const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ ids, settings }) => { const router = useRouter(); const searchParams = useSearchParams(); const { envConfig } = useEnv(); @@ -40,7 +42,8 @@ const ReaderContent: React.FC<{ settings: SystemSettings }> = ({ settings }) => if (isInitiating.current) return; isInitiating.current = true; - const initialIds = (searchParams.get('ids') || '').split(',').filter(Boolean); + const bookIds = ids || searchParams.get('ids') || ''; + const initialIds = bookIds.split(BOOK_IDS_SEPARATOR).filter(Boolean); const initialBookKeys = initialIds.map((id) => `${id}-${uniqueId()}`); setBookKeys(initialBookKeys); const uniqueIds = new Set(); @@ -72,7 +75,7 @@ const ReaderContent: React.FC<{ settings: SystemSettings }> = ({ settings }) => const saveSettingsAndGoToLibrary = () => { saveSettings(envConfig, settings); - router.replace('/library'); + navigateToLibrary(router); }; const handleCloseBooks = () => { diff --git a/apps/readest-app/src/app/reader/hooks/useBooksManager.ts b/apps/readest-app/src/app/reader/hooks/useBooksManager.ts index 146085d6..d3c3e7bd 100644 --- a/apps/readest-app/src/app/reader/hooks/useBooksManager.ts +++ b/apps/readest-app/src/app/reader/hooks/useBooksManager.ts @@ -5,6 +5,7 @@ import { useReaderStore } from '@/store/readerStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { uniqueId } from '@/utils/misc'; import { useParallelViewStore } from '@/store/parallelViewStore'; +import { navigateToReader } from '@/utils/nav'; const useBooksManager = () => { const router = useRouter(); @@ -18,11 +19,9 @@ const useBooksManager = () => { useEffect(() => { if (shouldUpdateSearchParams) { - const ids = bookKeys.map((key) => key.split('-')[0]).join(','); + const ids = bookKeys.map((key) => key.split('-')[0]!); if (ids) { - const params = new URLSearchParams(searchParams.toString()); - params.set('ids', ids); - router.replace(`?${params.toString()}`, { scroll: false }); + navigateToReader(router, ids, searchParams.toString(), { scroll: false }); } setShouldUpdateSearchParams(false); } diff --git a/apps/readest-app/src/app/reader/page.tsx b/apps/readest-app/src/app/reader/page.tsx index 30321968..76c76aa1 100644 --- a/apps/readest-app/src/app/reader/page.tsx +++ b/apps/readest-app/src/app/reader/page.tsx @@ -1,46 +1,7 @@ 'use client'; -import * as React from 'react'; -import { useEffect, Suspense, useRef } from 'react'; +import Reader from './components/Reader'; -import { useEnv } from '@/context/EnvContext'; -import { useLibraryStore } from '@/store/libraryStore'; - -import ReaderContent from './components/ReaderContent'; -import { AboutWindow } from '@/components/AboutWindow'; -import { useSettingsStore } from '@/store/settingsStore'; - -const ReaderPage = () => { - const { envConfig } = useEnv(); - const { settings, setSettings } = useSettingsStore(); - const { library, setLibrary } = useLibraryStore(); - const isInitiating = useRef(false); - - useEffect(() => { - if (isInitiating.current) return; - isInitiating.current = true; - const initLibrary = async () => { - const appService = await envConfig.getAppService(); - const settings = await appService.loadSettings(); - setSettings(settings); - setLibrary(await appService.loadLibraryBooks()); - }; - - initLibrary(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - library.length > 0 && - settings.globalReadSettings && ( -
- - - - -
- ) - ); -}; - -export default ReaderPage; +export default function Page() { + return ; +} diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index ca1567b8..0f95748d 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -78,3 +78,5 @@ export const SANS_SERIF_FONTS = ['Roboto', 'Noto Sans', 'Open Sans', 'Helvetica' export const MONOSPACE_FONTS = ['Fira Code', 'Lucida Console', 'Consolas', 'Courier New']; export const ONE_COLUMN_MAX_INLINE_SIZE = 9999; + +export const BOOK_IDS_SEPARATOR = '+'; diff --git a/apps/readest-app/src/utils/nav.ts b/apps/readest-app/src/utils/nav.ts new file mode 100644 index 00000000..29b925fa --- /dev/null +++ b/apps/readest-app/src/utils/nav.ts @@ -0,0 +1,23 @@ +import { useRouter } from 'next/navigation'; +import { isWebAppPlatform } from '@/services/environment'; +import { BOOK_IDS_SEPARATOR } from '@/services/constants'; + +export const navigateToReader = ( + router: ReturnType, + bookIds: string[], + queryParams?: string, + navOptions?: { scroll?: boolean }, +) => { + const ids = bookIds.join(BOOK_IDS_SEPARATOR); + if (isWebAppPlatform()) { + router.push(`/reader/${ids}${queryParams ? `?${queryParams}` : ''}`, navOptions); + } else { + const params = new URLSearchParams(queryParams || ''); + params.set('ids', ids); + router.push(`/reader?${params.toString()}`, navOptions); + } +}; + +export const navigateToLibrary = (router: ReturnType) => { + router.push('/library'); +};