diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 97741936..cfceae15 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -49,7 +49,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: pnpm
- name: cache Next.js build
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c49f2fd5..09be1ff6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -159,7 +159,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: pnpm
- name: setup Java (for Android build only)
diff --git a/README.md b/README.md
index 58a36129..34ea2a6e 100644
--- a/README.md
+++ b/README.md
@@ -122,8 +122,8 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
For the best experience to build Readest for yourself, use a recent version of Node.js and Rust. Refer to the [Tauri documentation](https://v2.tauri.app/start/prerequisites/) for details on setting up the development environment prerequisites on different platforms.
```bash
-nvm install v22
-nvm use v22
+nvm install v24
+nvm use v24
npm install -g pnpm
rustup update
```
diff --git a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
index 87c79cf1..638e2e64 100644
--- a/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
+++ b/apps/readest-app/src/app/reader/components/notebook/Notebook.tsx
@@ -242,6 +242,7 @@ const Notebook: React.FC = ({}) => {
const hasSearchResults = filteredAnnotationNotes.length > 0 || filteredExcerptNotes.length > 0;
const hasAnyNotes = annotationNotes.length > 0 || excerptNotes.length > 0;
+ const isMobile = window.innerWidth < 640;
return isNotebookVisible ? (
<>
@@ -264,22 +265,14 @@ const Notebook: React.FC = ({}) => {
aria-label={_('Notebook')}
dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'}
style={{
- width: `${notebookWidth}`,
- maxWidth: `${MAX_NOTEBOOK_WIDTH * 100}%`,
- position: isNotebookPinned ? 'relative' : 'absolute',
+ width: isMobile ? '100%' : `${notebookWidth}`,
+ maxWidth: isMobile ? '100%' : `${MAX_NOTEBOOK_WIDTH * 100}%`,
+ position: isMobile ? 'fixed' : isNotebookPinned ? 'relative' : 'absolute',
paddingTop: systemUIVisible
? `${Math.max(safeAreaInsets?.top || 0, statusBarHeight)}px`
: `${safeAreaInsets?.top || 0}px`,
}}
>
-
{
}
};
+ const sidebarRef = useRef
(null);
+ const overlayRef = useRef(null);
+
useEffect(() => {
if (isSideBarVisible) {
updateAppTheme('base-200');
+ overlayRef.current = document.querySelector('.overlay') as HTMLDivElement | null;
} else {
updateAppTheme('base-100');
+ overlayRef.current = null;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSideBarVisible]);
@@ -102,18 +107,19 @@ const SideBar = ({}) => {
const newTop = Math.max(0.0, Math.min(1, heightFraction));
sidebarHeight.current = newTop;
- const sidebar = document.querySelector('.sidebar-container') as HTMLElement;
- const overlay = document.querySelector('.overlay') as HTMLElement;
+ const sidebar = sidebarRef.current;
+ const overlay = overlayRef.current;
if (sidebar && overlay) {
- sidebar.style.top = `${newTop * 100}%`;
+ sidebar.style.transition = 'none';
+ sidebar.style.transform = `translateY(${newTop * 100}%)`;
overlay.style.opacity = `${1 - heightFraction}`;
}
};
const handleVerticalDragEnd = (data: { velocity: number; clientY: number }) => {
- const sidebar = document.querySelector('.sidebar-container') as HTMLElement;
- const overlay = document.querySelector('.overlay') as HTMLElement;
+ const sidebar = sidebarRef.current;
+ const overlay = overlayRef.current;
if (!sidebar || !overlay) return;
@@ -122,8 +128,8 @@ const SideBar = ({}) => {
(data.velocity >= 0 && data.clientY >= window.innerHeight * 0.5)
) {
const transitionDuration = 0.15 / Math.max(data.velocity, 0.5);
- sidebar.style.transition = `top ${transitionDuration}s ease-out`;
- sidebar.style.top = '100%';
+ sidebar.style.transition = `transform ${transitionDuration}s ease-out`;
+ sidebar.style.transform = 'translateY(100%)';
overlay.style.transition = `opacity ${transitionDuration}s ease-out`;
overlay.style.opacity = '0';
setTimeout(() => setSideBarVisible(false), 300);
@@ -131,8 +137,8 @@ const SideBar = ({}) => {
impactFeedback('medium');
}
} else {
- sidebar.style.transition = 'top 0.3s ease-out';
- sidebar.style.top = '0%';
+ sidebar.style.transition = 'transform 0.3s ease-out';
+ sidebar.style.transform = 'translateY(0%)';
overlay.style.transition = 'opacity 0.3s ease-out';
overlay.style.opacity = '0.8';
if (appService?.hasHaptics) {
@@ -236,6 +242,7 @@ const SideBar = ({}) => {
/>
)}
{
aria-label={_('Sidebar')}
dir={viewSettings?.rtl && languageDir === 'rtl' ? 'rtl' : 'ltr'}
style={{
- width: `${sideBarWidth}`,
- maxWidth: `${MAX_SIDEBAR_WIDTH * 100}%`,
- position: isSideBarPinned ? 'relative' : 'absolute',
+ width: isMobile ? '100%' : `${sideBarWidth}`,
+ maxWidth: isMobile ? '100%' : `${MAX_SIDEBAR_WIDTH * 100}%`,
+ position: isMobile ? 'fixed' : isSideBarPinned ? 'relative' : 'absolute',
paddingTop: systemUIVisible
? `${Math.max(safeAreaInsets?.top || 0, statusBarHeight)}px`
: `${safeAreaInsets?.top || 0}px`,
@@ -259,14 +266,9 @@ const SideBar = ({}) => {