From 49978c5e3fa61cbf24e01b5ebb028734f9d4b9d8 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Tue, 1 Jul 2025 15:17:18 +0800 Subject: [PATCH] layout: add overlay scrollbar for TOC on Android (#1506) --- apps/readest-app/package.json | 2 + .../app/library/components/BookshelfItem.tsx | 4 ++ apps/readest-app/src/app/library/page.tsx | 57 ++++++++-------- .../reader/components/sidebar/BookMenu.tsx | 2 +- .../app/reader/components/sidebar/Content.tsx | 66 +++++++------------ .../reader/components/sidebar/SearchBar.tsx | 3 +- .../app/reader/components/sidebar/SideBar.tsx | 12 ++-- apps/readest-app/src/styles/globals.css | 10 --- pnpm-lock.yaml | 22 +++++++ 9 files changed, 90 insertions(+), 88 deletions(-) diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index 2396b5e3..5e90d328 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -76,6 +76,8 @@ "jwt-decode": "^4.0.0", "marked": "^15.0.12", "next": "15.3.3", + "overlayscrollbars": "^2.11.4", + "overlayscrollbars-react": "^0.5.6", "posthog-js": "^1.246.0", "react": "19.0.0", "react-color": "^2.19.3", diff --git a/apps/readest-app/src/app/library/components/BookshelfItem.tsx b/apps/readest-app/src/app/library/components/BookshelfItem.tsx index ea328220..a6112e41 100644 --- a/apps/readest-app/src/app/library/components/BookshelfItem.tsx +++ b/apps/readest-app/src/app/library/components/BookshelfItem.tsx @@ -117,6 +117,10 @@ const BookshelfItem: React.FC = ({ const makeBookAvailable = async (book: Book) => { if (book.uploadedAt && !book.downloadedAt) { if (await appService?.isBookAvailable(book)) { + if (!book.downloadedAt) { + book.downloadedAt = Date.now(); + updateBook(envConfig, book); + } return true; } let available = false; diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 7cb1da3c..fd49b695 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -4,6 +4,8 @@ import clsx from 'clsx'; import * as React from 'react'; import { useState, useRef, useEffect, Suspense } from 'react'; import { ReadonlyURLSearchParams, useRouter, useSearchParams } from 'next/navigation'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import 'overlayscrollbars/overlayscrollbars.css'; import { Book } from '@/types/book'; import { AppService } from '@/types/system'; @@ -604,34 +606,33 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP )} {libraryLoaded && (libraryBooks.some((book) => !book.deletedAt) ? ( -
- - -
+ +
+ + +
+
) : (
diff --git a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx index 01caea21..a792727b 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/BookMenu.tsx @@ -8,12 +8,12 @@ import { useReaderStore } from '@/store/readerStore'; import { useLibraryStore } from '@/store/libraryStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { useTranslation } from '@/hooks/useTranslation'; +import { useParallelViewStore } from '@/store/parallelViewStore'; import { isWebAppPlatform } from '@/services/environment'; import { eventDispatcher } from '@/utils/event'; import { DOWNLOAD_READEST_URL } from '@/services/constants'; import useBooksManager from '../../hooks/useBooksManager'; import MenuItem from '@/components/MenuItem'; -import { useParallelViewStore } from '@/store/parallelViewStore'; interface BookMenuProps { menuClassName?: string; diff --git a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx index d35e239f..8f2ddd99 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/Content.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/Content.tsx @@ -1,9 +1,12 @@ import clsx from 'clsx'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { BookDoc } from '@/libs/document'; import { useEnv } from '@/context/EnvContext'; import { useBookDataStore } from '@/store/bookDataStore'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import 'overlayscrollbars/overlayscrollbars.css'; + import TOCView from './TOCView'; import BooknoteView from './BooknoteView'; import TabNavigation from './TabNavigation'; @@ -13,38 +16,12 @@ const SidebarContent: React.FC<{ sideBarBookKey: string; }> = ({ bookDoc, sideBarBookKey }) => { const { appService } = useEnv(); - const scrollContainerRef = useRef(null); const { getConfig, setConfig } = useBookDataStore(); const config = getConfig(sideBarBookKey); const [activeTab, setActiveTab] = useState(config?.viewSettings?.sideBarTab || 'toc'); const [fade, setFade] = useState(false); const [targetTab, setTargetTab] = useState(activeTab); - useEffect(() => { - const container = scrollContainerRef.current; - if (!container) return; - - let scrollTimeout: ReturnType; - const showScrollbar = () => { - container.classList.remove('hidden-scrollbar'); - }; - const hideScrollbar = () => { - container.classList.add('hidden-scrollbar'); - }; - - hideScrollbar(); - const handleScroll = () => { - showScrollbar(); - clearTimeout(scrollTimeout); - scrollTimeout = setTimeout(hideScrollbar, 2000); - }; - container.addEventListener('scroll', handleScroll); - return () => { - container.removeEventListener('scroll', handleScroll); - clearTimeout(scrollTimeout); - }; - }, []); - useEffect(() => { if (!sideBarBookKey) return; const config = getConfig(sideBarBookKey!)!; @@ -74,23 +51,24 @@ const SidebarContent: React.FC<{ 'font-sans text-base font-normal sm:text-sm', )} > -
- {targetTab === 'toc' && bookDoc.toc && ( - - )} - {targetTab === 'annotations' && ( - - )} - {targetTab === 'bookmarks' && ( - - )} -
+ +
+ {targetTab === 'toc' && bookDoc.toc && ( + + )} + {targetTab === 'annotations' && ( + + )} + {targetTab === 'bookmarks' && ( + + )} +
+
= ({ if (isVisible && searchTerm) { handleSearchTermChange(searchTerm); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [isVisible]); useEffect(() => { @@ -98,7 +99,7 @@ const SearchBar: React.FC = ({ clearTimeout(searchTimeout.current); } }; - }, []); + }, [onHideSearchBar]); const handleInputChange = (e: React.ChangeEvent) => { const value = e.target.value; diff --git a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx index ff7d6092..a4ec016a 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/SideBar.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { impactFeedback } from '@tauri-apps/plugin-haptics'; import { useSettingsStore } from '@/store/settingsStore'; @@ -152,12 +152,13 @@ const SideBar: React.FC<{ }); }; - const handleHideSearchBar = () => { + const handleHideSearchBar = useCallback(() => { setIsSearchBarVisible(false); setSearchResults(null); setSearchTerm(''); getView(sideBarBookKey)?.clearSearch(); - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sideBarBookKey]); useShortcuts({ onToggleSearchBar: handleToggleSearchBar, onEscape: handleHideSearchBar }, [ sideBarBookKey, @@ -257,7 +258,10 @@ const SideBar: React.FC<{ )}
diff --git a/apps/readest-app/src/styles/globals.css b/apps/readest-app/src/styles/globals.css index 1becbbcf..82c70997 100644 --- a/apps/readest-app/src/styles/globals.css +++ b/apps/readest-app/src/styles/globals.css @@ -202,16 +202,6 @@ foliate-view { visibility: visible; } -.scroll-container { - overflow-y: auto; - scrollbar-width: thin; - -webkit-overflow-scrolling: touch; -} - -.scroll-container.hidden-scrollbar { - scrollbar-width: none; -} - .pull-indicator { position: fixed; width: 100%; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75d57cef..2d479b36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,6 +152,12 @@ importers: next: specifier: 15.3.3 version: 15.3.3(@babel/core@7.26.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + overlayscrollbars: + specifier: ^2.11.4 + version: 2.11.4 + overlayscrollbars-react: + specifier: ^0.5.6 + version: 0.5.6(overlayscrollbars@2.11.4)(react@19.0.0) posthog-js: specifier: ^1.246.0 version: 1.246.0 @@ -4787,6 +4793,15 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + overlayscrollbars-react@0.5.6: + resolution: {integrity: sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw==} + peerDependencies: + overlayscrollbars: ^2.0.0 + react: '>=16.8.0' + + overlayscrollbars@2.11.4: + resolution: {integrity: sha512-GKYQo3OZ1QWnppNjQVv5hfpn+glYUxc6+ufW+ivdXUyLWFNc01XoH2Z36KGM4I8e5pXYeA3ElNItcXiLvmUhnQ==} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -11836,6 +11851,13 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + overlayscrollbars-react@0.5.6(overlayscrollbars@2.11.4)(react@19.0.0): + dependencies: + overlayscrollbars: 2.11.4 + react: 19.0.0 + + overlayscrollbars@2.11.4: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0