layout: add overlay scrollbar for TOC on Android (#1506)

This commit is contained in:
Huang Xin
2025-07-01 15:17:18 +08:00
committed by GitHub
parent b959bf070c
commit 49978c5e3f
9 changed files with 90 additions and 88 deletions
+2
View File
@@ -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",
@@ -117,6 +117,10 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
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;
+29 -28
View File
@@ -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) ? (
<div
ref={containerRef}
className={clsx(
'scroll-container drop-zone flex-grow overflow-y-auto',
isDragging && 'drag-over',
)}
style={{
paddingTop: '0px',
paddingRight: `${insets.right}px`,
paddingBottom: `${insets.bottom}px`,
paddingLeft: `${insets.left}px`,
}}
>
<DropIndicator />
<Bookshelf
libraryBooks={libraryBooks}
isSelectMode={isSelectMode}
isSelectAll={isSelectAll}
isSelectNone={isSelectNone}
handleImportBooks={handleImportBooks}
handleBookUpload={handleBookUpload}
handleBookDownload={handleBookDownload}
handleBookDelete={handleBookDelete}
handleSetSelectMode={handleSetSelectMode}
handleShowDetailsBook={handleShowDetailsBook}
booksTransferProgress={booksTransferProgress}
/>
</div>
<OverlayScrollbarsComponent options={{ scrollbars: { autoHide: 'scroll' } }} defer>
<div
ref={containerRef}
className={clsx('scroll-container drop-zone flex-grow', isDragging && 'drag-over')}
style={{
paddingTop: '0px',
paddingRight: `${insets.right}px`,
paddingBottom: `${insets.bottom}px`,
paddingLeft: `${insets.left}px`,
}}
>
<DropIndicator />
<Bookshelf
libraryBooks={libraryBooks}
isSelectMode={isSelectMode}
isSelectAll={isSelectAll}
isSelectNone={isSelectNone}
handleImportBooks={handleImportBooks}
handleBookUpload={handleBookUpload}
handleBookDownload={handleBookDownload}
handleBookDelete={handleBookDelete}
handleSetSelectMode={handleSetSelectMode}
handleShowDetailsBook={handleShowDetailsBook}
booksTransferProgress={booksTransferProgress}
/>
</div>
</OverlayScrollbarsComponent>
) : (
<div className='hero drop-zone h-screen items-center justify-center'>
<DropIndicator />
@@ -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;
@@ -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<HTMLDivElement>(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<typeof setTimeout>;
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',
)}
>
<div
ref={scrollContainerRef}
className={clsx(
'scroll-container min-h-0 flex-1 overflow-y-auto transition-opacity duration-300 ease-in-out',
{ 'opacity-0': fade, 'opacity-100': !fade },
)}
>
{targetTab === 'toc' && bookDoc.toc && (
<TOCView toc={bookDoc.toc} bookKey={sideBarBookKey} />
)}
{targetTab === 'annotations' && (
<BooknoteView type='annotation' toc={bookDoc.toc ?? []} bookKey={sideBarBookKey} />
)}
{targetTab === 'bookmarks' && (
<BooknoteView type='bookmark' toc={bookDoc.toc ?? []} bookKey={sideBarBookKey} />
)}
</div>
<OverlayScrollbarsComponent options={{ scrollbars: { autoHide: 'scroll' } }} defer>
<div
className={clsx(
'scroll-container min-h-0 flex-1 transition-opacity duration-300 ease-in-out',
{ 'opacity-0': fade, 'opacity-100': !fade },
)}
>
{targetTab === 'toc' && bookDoc.toc && (
<TOCView toc={bookDoc.toc} bookKey={sideBarBookKey} />
)}
{targetTab === 'annotations' && (
<BooknoteView type='annotation' toc={bookDoc.toc ?? []} bookKey={sideBarBookKey} />
)}
{targetTab === 'bookmarks' && (
<BooknoteView type='bookmark' toc={bookDoc.toc ?? []} bookKey={sideBarBookKey} />
)}
</div>
</OverlayScrollbarsComponent>
</div>
<div
className={clsx(
@@ -79,6 +79,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
if (isVisible && searchTerm) {
handleSearchTermChange(searchTerm);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isVisible]);
useEffect(() => {
@@ -98,7 +99,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
clearTimeout(searchTimeout.current);
}
};
}, []);
}, [onHideSearchBar]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
@@ -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<{
<SidebarContent bookDoc={bookDoc} sideBarBookKey={sideBarBookKey!} />
)}
<div
className='drag-bar absolute right-0 top-0 -m-3 h-full w-0.5 cursor-col-resize p-3'
className={clsx(
'drag-bar absolute right-0 top-0 -m-3 h-full w-0.5 cursor-col-resize p-3',
isMobile && 'hidden',
)}
onMouseDown={handleHorizontalDragStart}
onTouchStart={handleHorizontalDragStart}
></div>
-10
View File
@@ -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%;
+22
View File
@@ -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