Compare commits

..

1123 Commits

Author SHA1 Message Date
Huang Xin 83f0d135c5 release: version 0.9.99 (#3190) 2026-02-06 18:51:03 +01:00
Huang Xin 1ed84a3256 feat(tts): navigation in TTS mode with back to current button, closes #3100 (#3189) 2026-02-06 18:26:14 +01:00
Huang Xin 9d6394fe2b fix(layout): fixed page navigation buttons have higher z-index than TTS control button (#3184) 2026-02-06 11:00:56 +01:00
Huang Xin fe9603ffb8 fix(a11y): updating progress info when navigating with screen readers (#3182)
Also add some missing button labels for screen readers
2026-02-06 08:43:03 +01:00
boludo00 681e065ac4 fix(rsvp): fix position restoration and keyboard handling (#3150)
* feat: add RSVP speed reading feature

Implements Rapid Serial Visual Presentation (RSVP) speed reading mode:

- Display words one at a time with ORP (Optimal Recognition Point) highlighting
- Adjustable WPM speed (100-1000) with keyboard/button controls
- Punctuation pause settings (25-200ms)
- Progress tracking with chapter navigation
- Context panel showing surrounding text when paused
- Keyboard shortcuts (Space, Escape, arrows) and touch gestures
- Chapter selector for quick navigation
- Respects current theme colors
- Persists settings (WPM, pause duration, position) per book

New files:
- services/rsvp/RSVPController.ts - Main controller with playback logic
- services/rsvp/types.ts - TypeScript interfaces
- components/rsvp/RSVPOverlay.tsx - Full-screen RSVP reader UI
- components/rsvp/RSVPControl.tsx - Integration component
- components/rsvp/RSVPStartDialog.tsx - Start position selection

Closes #3111

* test(rsvp): add Playwright e2e tests for RSVP feature

- Set up Playwright test infrastructure with config
- Add comprehensive e2e tests for RSVP speed reading:
  - Opening RSVP from View menu
  - Start dialog options
  - Play/pause toggle
  - Speed adjustment
  - Skip navigation
  - Keyboard shortcuts
  - Progress bar
  - Chapter navigation
  - Accessibility tests
- Add test data attributes and ARIA labels to RSVPOverlay
- Add test scripts to package.json

* refactor: remove test files, unsure of use case for now

* chore: remove Playwright e2e test scripts from package.json

- Deleted e2e test scripts related to Playwright from package.json as they are no longer needed.
- Removed Playwright as a dev dependency to streamline the project.

* chore: sync pnpm-lock.yaml after removing @playwright/test

* fix: lint errors and timezone-aware date formatting

* fix(rsvp): restore correct position when exiting RSVP mode

Fix bug where exiting RSVP after changing speed would navigate to wrong
position (several pages backwards). The issue was that Range objects
become stale if the document re-renders, and the position recovery used
global word index instead of per-document index.

- Add docWordIndex to track word position within each document
- Add docTotalWords to RsvpStopPosition for accurate recovery
- Validate Range before using it, recreate from current DOM if stale
- Use same word extraction logic as RSVPController for consistency

* fix(rsvp): prevent arrow keys from changing chapter dropdown

Arrow keys during RSVP mode were inadvertently navigating the chapter
dropdown instead of controlling RSVP speed. Fixed by using capture phase
for keyboard events and stopping propagation to prevent native elements
from receiving the events.

* chore: remove playwright-mcp screenshots from repo

* chore: update gitignore

* fix(i18n): wrap all RSVP overlay strings with translation function

Restore missing translation wrappers for all user-facing strings:
- Close RSVP button labels and tooltips
- Select Chapter dropdown
- WPM display
- Context panel header
- Ready state text
- Chapter Progress label
- Words count and time remaining
- Reading progress slider
- Pause/punctuation label
- Skip back/forward buttons
- Play/Pause button
- Speed control buttons

* fix(rsvp): use CFI-based position tracking for accurate page positioning

- Add CFI generation for each extracted word during RSVP processing
- Implement CFI path and character offset comparison for precise position matching
- Update startFromCurrentPosition to use CFI matching instead of word index
- Add extractFullDocPathWithOffset to handle CFI character offsets
- Simplify RSVPControl by removing unused helper functions
- Remove word index from RsvpPosition in favor of CFI-only tracking

* fix(ViewMenu): re-enable Speed Reading Mode in production environment

* refactor(ViewMenu, RSVPController): clean up code formatting and improve readability

* refactor(ViewMenu): remove unused IoSpeedometer import

* refactor(RSVPController): streamline CFI handling and improve section comparison

- Removed redundant logic and re-using CFI util functions for CFI operations

* refactor(RsvpPosition): remove legacy wordIndex field for cleaner type definition
2026-02-05 18:19:13 +01:00
Huang Xin f64fc5723e fix(a11y): double tap to focus on current paragraph and update page info with TalkBack, closes #2276 (#3179) 2026-02-05 18:18:45 +01:00
Huang Xin a9a1dc8e70 fix(layout): fixed regression of vertical alignment, closes #3012 (#3178) 2026-02-05 17:46:47 +01:00
Huang Xin b1a1e35790 fix(layout): move sync options inside of account section in settings menu (#3176) 2026-02-05 10:08:17 +01:00
Huang Xin 3c538c3746 feat(sync): add manual books sync in the main menu (#3175) 2026-02-05 08:26:33 +01:00
Huang Xin 9834bd57cf feat(toc): add page number for nested TOC items, closes #2953 (#3174) 2026-02-05 08:19:18 +01:00
Huang Xin b89171a4d8 compat(iOS): disable native CompressionStream/DecompressionStream API in zip.js for iOS 15.x (#3170) 2026-02-04 13:18:36 +01:00
Huang Xin 92116e7455 fix(bookshelf): merge groups and ungrouped books then sort them together (#3169) 2026-02-04 12:32:29 +01:00
Huang Xin 8dfab2c963 feat(a11y): always show page navigation buttons in reader page for screen readers, closes #3036 (#3167) 2026-02-04 10:07:37 +01:00
Huang Xin 9f261f12e9 fix(kosync): don't show conflict prompt when progress diff is less than 0.01% (usually from the same device) (#3166)
This should close #3137.
2026-02-04 08:28:15 +01:00
Huang Xin e74615ac1d feat(export): add an option to export annotations as plain text, closes #3147 (#3165) 2026-02-04 07:50:40 +01:00
Huang Xin 3b350d6945 fix(layout): responsive select mode actions on smaller screens (#3164) 2026-02-04 07:04:29 +01:00
Huang Xin 9e0e3fde7d fix(layout): fix aligment of options in settings menu, closes #3151 (#3163) 2026-02-04 06:42:14 +01:00
Adam Charron 52c49ddef1 Add enhanced grouping and sorting functionality to the library view (#3146)
* feat(library): implement grouping of books by series or author

- Added functionality to group books in the library by series or author.
- Introduced new utility functions for creating book groups and parsing author strings.
- Updated the LibraryPageContent to track the current group for navigation.
- Created a new GroupHeader component to display the current group context.
- Enhanced the Bookshelf component to support displaying grouped books.
- Updated settings to include grouping options in the view menu.

New files:
- src/__tests__/utils/libraryUtils.test.ts - Unit tests for new utility functions.
- src/app/library/components/GroupHeader.tsx - Component for displaying group context.
- src/app/library/utils/libraryUtils.ts - Utility functions for grouping and parsing authors.

* Use group by and sort by constants, and split sort/group by menus into their own elements

* Format code with autoformatter

* Remove any casting from tests

* Translate missing strings

* refactor: cleaner layout with collapsible view menu options

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-02-03 14:51:50 +01:00
Huang Xin e3d52891fb fix(a11y): add missing aria labels and fix iOS safe insets (#3149) 2026-02-03 12:50:24 +01:00
Huang Xin 9cd88fe839 fix(progress): show correct progress info for subsections in FB2, closes #3136 (#3145) 2026-02-02 17:53:26 +01:00
Huang Xin fca3917a12 fix(toc): prevent unintentional scrolling in the TOC, closes #3124 (#3144) 2026-02-02 14:28:11 +01:00
Adam Charron c90de6967b Change setup command for vendors in CONTRIBUTING.md (#3139)
Updated instructions for setting up dependencies in the project.
2026-02-02 04:41:54 +01:00
boludo00 bbbd378f9b feat: rsvp speed reading (#3121)
* feat: add RSVP speed reading feature

Implements Rapid Serial Visual Presentation (RSVP) speed reading mode:

- Display words one at a time with ORP (Optimal Recognition Point) highlighting
- Adjustable WPM speed (100-1000) with keyboard/button controls
- Punctuation pause settings (25-200ms)
- Progress tracking with chapter navigation
- Context panel showing surrounding text when paused
- Keyboard shortcuts (Space, Escape, arrows) and touch gestures
- Chapter selector for quick navigation
- Respects current theme colors
- Persists settings (WPM, pause duration, position) per book

New files:
- services/rsvp/RSVPController.ts - Main controller with playback logic
- services/rsvp/types.ts - TypeScript interfaces
- components/rsvp/RSVPOverlay.tsx - Full-screen RSVP reader UI
- components/rsvp/RSVPControl.tsx - Integration component
- components/rsvp/RSVPStartDialog.tsx - Start position selection

Closes #3111

* fix(rsvp): use portal to fix overlay stacking context issue

- Render RSVP overlay and start dialog via React Portal to document.body
- This ensures the overlay appears above all other content regardless of
  parent component CSS transforms or stacking contexts
- Add fallback colors for theme values to ensure solid background

* fix(rsvp): improve UX with progress sync, sentence highlight, and better dialogs

- Fix start dialog transparency by using solid opaque background with proper
  fallback colors for both light and dark modes
- Increase context words from 50 to 100 words before/after current word
- Add progress sync on RSVP exit - navigates reader to the last word position
- Add temporary bright green sentence underline on exit (5 second duration)
  to easily locate where reading left off
- Helper function to expand word range to full sentence boundaries

* fix(rsvp): store full BookNote for proper annotation removal

The addAnnotation API requires the full BookNote object (including CFI)
when removing annotations, not just the ID. Changed tempHighlightIdRef
to tempHighlightRef to store the complete BookNote object.

* test(rsvp): add Playwright e2e tests for RSVP feature

- Set up Playwright test infrastructure with config
- Add comprehensive e2e tests for RSVP speed reading:
  - Opening RSVP from View menu
  - Start dialog options
  - Play/pause toggle
  - Speed adjustment
  - Skip navigation
  - Keyboard shortcuts
  - Progress bar
  - Chapter navigation
  - Accessibility tests
- Add test data attributes and ARIA labels to RSVPOverlay
- Add test scripts to package.json

* fix(rsvp): clarify start dialog option labels

Update the RSVP start dialog to use clearer language:
- "From Beginning" → "From Chapter Start" (since it starts from chapter beginning)
- "From Current Position" → "From Current Page" (starts from visible page)

* fix(rsvp): use correct theme colors from themeCode

The RSVP components were using incorrect palette key names (camelCase
like `base100` instead of hyphenated like `base-100`), causing the
fallback colors to always be used instead of the reader's actual theme.

Fix by using themeCode.bg, themeCode.fg, and themeCode.primary directly,
which are already resolved from the palette with correct keys.

* fix(rsvp): use theme accent color for sentence underline and persist until page change

- Change underline color from hardcoded green (#22c55e) to theme accent
  color (themeCode.primary), matching the ORP focal point highlight
- Remove 5-second timeout that auto-removed the underline
- Add cleanup on page navigation, new RSVP session start, and unmount
- Add removeRsvpHighlight helper function for consistent cleanup

* fix(rsvp): transition to next chapter when reaching end of section

When RSVP reached the end of a chapter, it would restart the current
chapter instead of moving to the next one. This happened because RSVP
extracts ALL words from the current section via renderer.getContents(),
so when words run out, the entire chapter is done.

- Always use view.renderer.nextSection() when RSVP exhausts words
- This moves to the next chapter instead of staying in the current one

* refactor: remove test files, unsure of use case for now

* chore: remove Playwright e2e test scripts from package.json

- Deleted e2e test scripts related to Playwright from package.json as they are no longer needed.
- Removed Playwright as a dev dependency to streamline the project.

* fix(rsvp): ensure CFI retrieval occurs before navigation

- Updated comments to clarify the necessity of obtaining CFI for both the navigation and sentence highlight before invoking the goTo() method, as it may re-render the document and invalidate Range objects.
- Introduced a new variable for sentence text to enhance readability and maintainability of the code.

* chore: sync pnpm-lock.yaml after removing @playwright/test

* style: format RSVP components

* fix: lint errors and timezone-aware date formatting

* i18n: support CJK text and add translations

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-02-01 18:22:24 +01:00
Huang Xin 9f7147f8f8 fix(layout): fix z index for dropdown (#3135) 2026-02-01 16:36:03 +01:00
Huang Xin 8a468a6d1f fix(opds): more robust parsing for authors in metadata, closes #3120 (#3134) 2026-02-01 15:57:10 +01:00
Huang Xin c848a319ff fix(layout): fix centered dropdown menu (#3133) 2026-02-01 15:52:51 +01:00
Huang Xin 5cc80db438 feat(gesture): support two finger swipe left/right to paginate on trackpad, closes #3127 (#3132) 2026-02-01 09:37:13 +01:00
Huang Xin 94930baa1b feat(sync): more intuitive pull down to sync gesture and toast info, closes #3128 (#3131) 2026-02-01 09:23:24 +01:00
Huang Xin 1294ef90c1 fix(macOS): fix traffic lights position and visibility on macOS, closes #3129 (#3130) 2026-02-01 05:38:40 +01:00
Huang Xin 570598520f fix(a11y): fix accessibility in dropdown menus for TalkBack, closes #3035 (#3126) 2026-01-31 19:05:56 +01:00
Huang Xin 7b4fc91994 fix(layout): fixed layout of book reading progress and reader footer bar (#3125) 2026-01-31 18:16:17 +01:00
Huang Xin fb387c2384 fix(layout): fixed layout of book reading progress and reader footer bar (#3117) 2026-01-30 06:54:20 +01:00
Mohammed Efaz 4ce1ebe477 feat: paragraph by paragraph reading mode (#3096)
* feat: add index

* feat: add bottom nav bar

* feat: add paragraph iterator

* feat: add para mode shortcut

* feat: add paragraph control into foliateviewer

* feat: add paragraph mode toggle to view menu

* feat: add paragraph bar for navigation controls

* feat: add paragraph control wrapper component

* feat: add pargraph overlay for the focused display

* feat: integrate paragraph mode into keyboard navigation

* feat: add paragraph mode state management hook

* feat: add paragraph mode type definition

* feat: add default paragraph mode config

* fix: replace previous storage system with sytem one and fix sync issues

* fix: format
2026-01-30 05:24:23 +01:00
Huang Xin c1460f4b85 fix(css): mix blend only inline images, closes #3112 (#3116) 2026-01-30 05:17:16 +01:00
Huang Xin bc94f3f790 refactor: responsive SetStatusAlert on smaller screens (#3110) 2026-01-29 15:53:20 +01:00
Huang Xin c8c761b017 fix(sync): escape unsafe character in filename (#3109) 2026-01-29 15:13:02 +01:00
Huang Xin bdb25999c9 fix(sync): more robust books sync for stale library, closes #3099 (#3108) 2026-01-29 14:05:58 +01:00
Huang Xin c58e172a54 fix(bookshelf): don't show badge or progress for unread book, closes #3103 (#3107) 2026-01-29 12:48:21 +01:00
Huang Xin 160efcd37b chore: bump next.js to the latest version (#3106) 2026-01-29 12:29:17 +01:00
Huang Xin d7470f4139 fix(iOS): disable online catalogs on iOS from appstore channel (#3102) 2026-01-29 05:52:59 +01:00
Mohammed Efaz 01b4e3530d feat: toggle finished manually (#3091) 2026-01-28 06:26:57 +01:00
Huang Xin 09c62d442f fix(css): no default mix blend mode for hr, closes #3086 (#3093) 2026-01-27 15:03:17 +01:00
Huang Xin d62ad60ce8 chore: bump opennextjs and wrangler to the latest versions (#3092) 2026-01-27 14:57:28 +01:00
Jair Goh 8cd34c8aaa feat(assistant): Add embedding model option to AIPanel (#3090)
* Add embedding model option to AIPanel

* Minor code reformatting

* Edit tests to account for embedding model

* Minor reformatting
2026-01-27 14:28:18 +01:00
Mohammed Efaz a52d9e3a2b feat: add fuzzy search for searching across all settings (#3085)
* feat: enable linking to settings items

* feat: integrate command palette with global state and styles

* feat: add command pallete ui and fuzzy search

* feat: add command palette with global state and styles

* feat: add command registry and search dependencies

* fix: open command palette with shortcuts in reader page

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-26 18:32:04 +01:00
Huang Xin 64a1ea531a fix(bookshelf): fixed conflicts of group names with common prefix (#3084) 2026-01-26 13:38:01 +01:00
Mohammed Efaz 19f9f5ea24 feat: auto position reader ruler to top of page and some fixes (#3075)
* fix(settings): ensure latest config is used when saving view settings

* fix: reader ruler appearing before other book reading contents

* feat(reader): dispatch reader-closing event during book close

* feat(reader) add auto reposition to top when changing pages and position persistence on book close

* fix(reader): add rtl prop to ReadingRuler component

* fix(reader): handle vertical ltr writing mode in ruler auto positioning

* chore: revert redundant changes to settings.ts

* refactor: remove redundant reader-closing

* refactor: use store progress for ruler positioning with throttled saving
2026-01-26 08:38:06 +01:00
Huang Xin ffc51e75de feat(ruler): support vertical ruler and transparent ruler (#3076) 2026-01-25 20:15:47 +01:00
Huang Xin 2100071991 feat(eink): support color E-ink mode to display more colors, closes #3037 (#3074) 2026-01-25 17:44:41 +01:00
Huang Xin 563d3478ba feat(gamepad): support gamepad input to paginate, closes #2432 (#3073) 2026-01-25 16:32:05 +01:00
Huang Xin 2c54e9ae2f feat(updater): in-app updater for AppImage (#3072) 2026-01-25 14:45:37 +01:00
Huang Xin c2eb2e2fcc doc: update donation link via Stripe (#3071) 2026-01-25 11:24:10 +01:00
Huang Xin e592f40429 layout: don't show upload icon if auto upload is disabled for cleaner UI (#3068) 2026-01-25 09:22:30 +01:00
Huang Xin 4bd7cfe57c chore: fix dockerfile (#3067) 2026-01-25 08:51:40 +01:00
Mohammed Efaz c31ece6742 feat: more highlight colours (#3062)
* feat(highlight): extend types and constants for custom hex colours

* feat(highlight): update colour to support hex strings

* refactor(annotator): update component to accept hex colours

* feat(highlight): add colour picker with max 4 custom colors

* feat(settings): add custom colour editor with limit

* refactor: custom highlight colors can only be modified in settings

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-25 07:52:29 +01:00
Mohammed Efaz aba9e87fc1 feat: line highlight to guide reading (reading ruler) (#3063)
* feat: add increment decrement numbers for opacity

* feat: add reading ruler colours

* feat: add reading ruler config to book config

* feat: reading Ruler settings panel (in layout section)

* feat: draggable reading ruler

* feat: reading ruler in reader view
2026-01-25 06:48:13 +01:00
Huang Xin 2c4df601d8 refactor: rename components/ui to components/primitives (#3064)
Move Radix/shadcn-based UI primitives into a dedicated
`components/primitives` directory to better reflect
their low-level, composable nature.
Imports remain ergonomic via an alias to `components/ui`.
App-level components should continue to use PascalCase filenames.
2026-01-25 06:41:18 +01:00
Huang Xin 8bfc90a5b0 chore: fix nunjucks bundling (#3061) 2026-01-24 18:20:15 +01:00
Mohammed Efaz 8bea05478a feat(ai): AI reading assistant phase 2.1 (minor UI/UX updates) (#3059)
* refactor(ai): remove chat icons from conversation list items

* feat(ai): add scroll-to-bottom button with animations and dynamic spacer

* fix(ai): align connection status icon with text in settings

* feat(ai): scroll to bottom on chat panel open and improve scroll behavior

* feat(ai): add loading overlay with blur transition for chat history

* feat(ai): pass loading state through component chain for history loading
2026-01-24 17:57:00 +01:00
Huang Xin 42fee90a27 feat(annotation): export annotations with style and color fields in annotation template, closes #1734 (#3060) 2026-01-24 17:44:40 +01:00
Huang Xin 45e57c3943 chore: update wrangler and next config (#3058) 2026-01-24 14:36:04 +01:00
Huang Xin 51b0b8483f chore: less build concurrency on cloudflare to get rid of build container OOM (#3056) 2026-01-24 13:27:11 +01:00
Huang Xin cbf7a501b7 chore: pin pnpm version for vercel and cloudflare deployment (#3055) 2026-01-24 12:35:03 +01:00
Huang Xin edfcb75ba5 chore: refresh pnpm lockfile (#3054) 2026-01-24 12:08:52 +01:00
Huang Xin ce76843ccc chore: fix patched dependencies (#3053) 2026-01-24 11:52:28 +01:00
Mohammed Efaz 5bbc5ceccc feat(ai): AI reading assistant phase 2 (#3023)
* feat(ai): add dependencies

* chore: bump zod version to default version

* feat(ai): define types and model constants

* feat(ai): ollama provider for local LLM

* feat(ai): implement openrouter provider for cloud models

* feat(settings): register ai settings panel in global dialog

* refactor(ai): expose provider factory and service layer entry point

* test(ai): add unit tests for the providers

* test(ai): add unit tests for the providers

* feat(ai): settings panel for ai configurations

* refactor(ai): rewrite aipanel with autosave and greyed out disabled state

* fix: remove unused onClose prop from aipanel

* test(ai): update mock data

* refactor(ai): remove models

* refactor: use centralised defaults in system defaults

* chore(ai): remove comments

* fix(ai): merge default ai settings on load to prevent undefined values

* refactor(ai): rewrite settings panel with autosave and model input

* feat(ai): add ai tab with simplified highlighting

* feat(sidebar): render AIAssistant for ai tab

* feat(ai): add chat UI

* feat(ai); add chat service with RAG context

* feat(ai): temp debug logger

* feat(ai): add RAG service

* feat(ai): add text chunking utility

* feat(ai): add structured method

* feat(ai): add chatstructured method

* feat(ai): add rag types nd structured output schema

* feat(ai): add aistore, indexdb, bm25

* fix: update lock file

* feat(ai): update types for AI SDK v5

* feat(ai): add placeholder gateway model constants

* refactor(ai): update OllamaProvider for AI SDK

* feat(ai): add native gateway provider

* refactor(ai): update provider exports

* refactor(ai): use streamText from AI SDK

* refactor(ai): use embed from AI sdk

* refactor(ai): update provider factory exports

* feat(ai): add AI Elements and shadcn components

* config: add shadcn component config

* deps: add AI SDK and AI Elements dependencies

* config: add ai packages to transpilePackages

* refactor(ai): remove OpenRouterProvider and old tests

* feat(ai):add assistant-ui components

* feat(ai): add TauriChatAdapter for assistant-ui runtime

* refactor(ai): remove ai-elements components

* dep(ai): install assistant-ui and update next config

* chore(ai): export adapters from service index

* feat(ui): enhance ui components for assistant integration

* feat(settings): migrate ai settings to gateway

* feat(sidebar): integrate assistant-ui

* feat: add ai settings toggle to sidebar content

* feat: conditionally show ai tab in sidebar navigation

* feat: update ai model constants for cheaper options

* feat: add gateway provider with proxied embedding

* feat: add timeouts to ollama provider health checks

* feat: add retry logic to rag service embeddings

* feat: add error recovery to ai store

* feat: add ai feature tests

* feat: add ai api endpoints

* feat: add proxied gateway embedding provider

* feat: add ai runtime utilities

* feat: add ai retry utilities

* feat: add tauri env example template

* feat: add web env example template

* chore: add env

* feat(ai): update models and pricing, remove GLM-4.7-FlashX

* feat(ai): improve system prompt with official headings and no numeric citations

* feat(ai): optimize system prompt for tauri chat

* feat(ui): refine ai chat UI and relocate sources

* feat(ui): update ai settings panel with model pricing and custom model support

* feat(ai): add custom model support to ai settings

* test(ai): update constants tests for removed model

* feat(api): implement ai chat proxy route

* feat(api): implement ai embedding proxy route

* feat(ai): implement ai gateway health check and proxy logic

* feat(ai): simplify proxied embedding provider

* feat(ui): improve markdown text rendering

* feat(ui): add input group component

* test(ai): update ai provider tests

* feat(ai): add pageNumber to text chunk schema

* feat(ai): implement page-based chunking with 1500 char formula

* feat(ai): bump db to v2 and add store reset migration

* feat(ai): transition rag pipeline to page level spoiler filtering

* feat(ai): overhaul readest persona and antijailbreak prompt

* feat(ai): update tauri adapter for page tracking and persona

* chore(ai): export aiStore and logger from core index

* feat(reader): integrate page tracking and manual index reset

* feat(ui): add re-index action and reset logic to chat

* chore: sync pnpm lockfile with ai dependencies

* feat(utils): add browser-safe file utilities for web builds

* refactor(utils): use dynamic tauri fs import to prevent web crashes

* refactor(services): defer osType call to init() for web compatibility

* refactor(services): import RemoteFile from file.web

* refactor(services): import ClosableFile from file.web

* fix(libs): cast Entry to any for getData access

* fix(annotator): cast Overlayer to any for bubble access

* refactor(ai): replace SparklesIcon with BookOpenIcon for index prompt

* test(ai): add pageNumber to TextChunk mocks

* test(ai): fix chunkSection signature in tests

* chore: update files

* fix(ai): prevent useLocalRuntime crash when adapter is null

* refactor: optimize annotator overlay drawing

* feat: stabilize AI assistant runtime and adapter

* refactor: improve document zip loader type safety

* feat: update tauri chat adapter for dynamic options

* fix: restore architecture comments and refine platform properties

* build: update lockfile with assistant-ui patch

* fix(library): patch @assistant-ui/react for runtime initialization

* build: update dependencies in readest-app

* build: update root dependencies and patch configuration

* fix(ai): patch @assistant-ui/react for thread deletion and runtime init

* fix(ai): update assistant-ui patch with dist guards and deletion fallback

* build: sync lockfile with assistant-ui patch updates

* chore(env): update .gitignore by removing .env files from it

* chore(env): update .gitignore by adding .env.local

* chore(env): update .gitignore by adding .env*.local

* fix: restore static osType import

* chore: sync submodules with upstream/main

* refactor: remove redundant file.web module and revert import

* chore: update pnpm-lock.yaml

* refactor: revert guards

* refactor; remove deprecated codes and extract prompts.ts

* refactor(ai): remove unused ragservice exports

* refactor: remove unused ollama and embedding models

* refactor: remove unused type

* test: remove test for the now deleted constants

* refactor: remove unused export

* style: fix ui component formatting

* style: fix core and style file formatting

* test: fix broken ai provider import

* fix: typescript error

* fix: add eslint disable command

* fix(deps): remove unused ai sdk provider util after v6 ai sdk migration

* fix(patch): add lookbehind regex patch

* feat(dep): upgrade vercel ai sdk to v6 and ai-sdk-ollama to v3

* chore: update lockfile for vercel ai sdk v6

* refactor(ai): remove EmbeddingModel generic for ai sdk v6

* refactor(ai): remove EmbeddingModel generic for ai sdk v6

* test(ai): update mock to use embeddingModel

* fix(patch): add lookbehind regex patch for email autolinks in markdown

* refactor(ai): use ai sdk v6 syntax

* fix: prettier formatting

* chore: revert cargo.lock

* fix(ai): update proxied embedding model to v3 spec

* feat(ai): add aiconversation types for chat persistence

* feat(ai): add conversation/message indexeddb and crud operations

* feat(ai): create aiChatStore zustand store for chat state management

* feat(notebook): add notebookactivetab state for Notes/AI

* refactor(ai): refine conversation and message types for persistence

* feat(types): add notebookActiveTab to ReadSettings type

* chore: update deps

* feat: add notebookactive tab default value

* feat: add hook for ai chat

* feat: update left side panel with history/chat icon

* feat: integrate ChatHistoryView into sidebar content

* feat: create UI for managing AI chat history

* feat: implement persistent history with assistant-ui adapter

* feat: create tab navigation component for notes and AI

* feat: add tab navigation and AI assistant view

* feat: update header to display active tab title

* fix: formatting

* feat: remove title and update new chat button

* fix: formatting

* fix: revert tooltip and styling

* feat: implement cross-platform ask dialog bridge

* feat(ai): preserve history during ui clear & use native dialogs

* fix: align notebook navigation height with sidebar tabs

* fix(ai): add missing dependency to handleDeleteConversation hook

* docs: update PROJECT.md with session highlights

* chore: delete projectmd

* chore: update package.json and lock file

* chore: update package.json

* chore: remove patch

* chore: upgrade react types to 19 and show ai features only in development mode for now

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-24 11:38:48 +01:00
Huang Xin 224acd68b1 fix(sanitizer): add XHTML11 doc type to recognize nbsp entity, closes #3024 (#3043) 2026-01-23 17:49:06 +01:00
Huang Xin 6c0f1b8b5f fix(annotator): fix instant action on Android (#3042) 2026-01-23 17:18:51 +01:00
Huang Xin 481d8198e9 fix(tts): set playback rate after play only on Linux (#3040) 2026-01-23 12:14:13 +01:00
Huang Xin b1153ba051 fix(sync): correctly update last sync timestamp when the bookshelf has no books yet (#3039) 2026-01-23 04:39:22 +01:00
Huang Xin aad532bfdd fix(sync): hotfix for the initial race condition for books sync (#3038) 2026-01-23 04:32:03 +01:00
Huang Xin a71347c897 fix(layout): more responsive layout on smaller screens, closes #3029 (#3034) 2026-01-22 17:32:34 +01:00
Huang Xin 034f41ca10 fix(shortcuts): prevent system search bar from showing with ctrl+f, closes #3013 (#3033) 2026-01-22 15:59:21 +01:00
Huang Xin 539ad8dea2 fix(layout): correctly constrain the maximum width of fixed-layout tables, closes #3028 and closes #3017 (#3032) 2026-01-22 14:56:35 +01:00
Huang Xin 48920a87bf chore(opds): disable popular online opds catalogs in certain regions on App Store (#3031) 2026-01-22 11:42:16 +01:00
xijibomi-coffee d1d0d2d59c chore: enforce prettier, ignore submodules and vendor files (#3018)
* chore: enforce prettier, ignore submodules and vendor files

* chore: add format check in CI

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-21 14:53:02 +01:00
xijibomi-coffee ea811c90c6 feat(ui): switch typography to Inter (#3009) 2026-01-21 13:49:37 +01:00
Jon Volkmar 5cd5b65e83 fix: Conditionally set Cache-Control header to prevent caching in development environments. (#3010) 2026-01-21 03:18:08 +01:00
Huang Xin ea24b5a97a fix(account): redirect to auth page if unauth user is in user page (#3008) 2026-01-20 21:55:53 +01:00
Huang Xin 06d620db83 chore: fixed signing of the portable version for Windows (#3007) 2026-01-21 03:23:39 +08:00
Huang Xin 0c25b85a8a release: version 0.9.98 (#3006) 2026-01-20 18:54:06 +01:00
Huang Xin c536450ab0 fix(fonts): host fonts in Readest CDN for more stable web fonts distribution (#3005) 2026-01-20 18:44:22 +01:00
Huang Xin c83e380c5a chore(doc): use png resources for sponsors logo (#3003) 2026-01-20 07:47:28 +01:00
Huang Xin 894a7551aa chore(doc): update sponsors info (#3002) 2026-01-20 07:42:06 +01:00
xijibomi-coffee f875ba88ac perf: use native walkdir for recursive imports from directory (#2993)
* fix(perf): replace JS recursion with native Rust walkdir for imports

* fix: implement security scope check in rust recursive scanner

* refactor and format code

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-20 06:22:04 +01:00
Huang Xin d9a6cffe78 feat(discord): support show reading status with Discord Rich Presence, closes #1538 (#2998) 2026-01-19 17:42:19 +01:00
Huang Xin 1704736bc8 feat(account): support social login with Discord account (#2995) 2026-01-19 05:22:17 +01:00
Huang Xin 6ce9a263ee fix(a11y): add missing aria labels for some buttons on TTS panel (#2994) 2026-01-19 04:29:02 +01:00
Huang Xin 038eca4267 fix(eink): resolved an issue where the dropdown menu would occasionally fail to expand on some Eink devices (#2991) 2026-01-18 19:57:09 +01:00
Huang Xin f0722ec0fe feat(bookshelf): show one line of book description in list view of the bookshelf, closes #2955 (#2990) 2026-01-18 17:16:54 +01:00
Huang Xin fd299a61a7 feat(notes): export notes with custom template, closes #1734 (#2989) 2026-01-18 16:56:32 +01:00
Huang Xin 36b7386e30 fix(portable): in-app update the portable version app, closes #2983 (#2988) 2026-01-18 11:20:15 +01:00
Huang Xin 2670b0dc0b fix(linux): use new AppImage format on Linux, closes #190 (#2985)
See https://github.com/tauri-apps/tauri/pull/12491
2026-01-18 09:50:51 +01:00
Huang Xin c44705e269 perf(pdf): pre-render next page for PDFs (#2984)
This should close #1911 and #13.
2026-01-18 08:58:00 +01:00
Huang Xin 7e618d456e chore: bump pdf.js to the latest version (#2982) 2026-01-17 13:45:34 +01:00
Huang Xin b28ac99a9e feat(pdf): support panning on PDFs (#2981)
This also closes #2978 and closes #2875.
2026-01-17 09:11:33 +01:00
Huang Xin 2d7d6b08a9 compat(opds): parse attachment filename from download requests, closes #2969 (#2976) 2026-01-16 17:13:30 +01:00
Huang Xin b1419f9c01 chore: bump tauri to latest dev (#2975) 2026-01-16 14:52:22 +01:00
Huang Xin 32ea42a835 fix: remove non-breaking space in book title (#2974) 2026-01-16 12:02:41 +01:00
Huang Xin 6083de3293 fix(import): read permissions of nested directories when importing books, closes #2954 (#2972) 2026-01-16 08:04:07 +01:00
Huang Xin 1c9cfa49b3 fix(flathub): use custom dbus id for single instance on Linux (#2971) 2026-01-16 05:59:25 +01:00
Huang Xin f85d6d4293 fix(eink): avoid scrolling if animation is turned off or in eink mode, closes #2957 (#2967) 2026-01-15 17:37:38 +01:00
Huang Xin 017d0b0b39 fix(tts): fix setting playback rate on Linux, closes #2950 (#2966) 2026-01-15 17:29:26 +01:00
Huang Xin 3146ae48a7 fix(proofread): support replace text with space (#2965) 2026-01-15 14:10:05 +01:00
Huang Xin 7f26e45ae7 feat(sync): cloud deletion in transfer queue (#2964) 2026-01-15 13:50:24 +01:00
Huang Xin 7d97826e4b feat(tts): replace words for TTS, closes #2057 (#2952) 2026-01-14 16:42:11 +01:00
Huang Xin 9c9c79176d feat(bookshelf): add an option to sort books by publish date, closes #2925 (#2949) 2026-01-14 13:40:07 +01:00
Huang Xin ed476a4fce fix(ios): fix wakelock on iOS, closes #2453 (#2948) 2026-01-14 13:06:16 +01:00
Huang Xin 44b4f7995b fix(android): fix annotator on Android, closes #2927 (#2946) 2026-01-14 11:56:38 +01:00
Huang Xin da5e3a0bd3 perf(fonts): cache first for font cache (#2945) 2026-01-14 07:34:00 +01:00
Huang Xin ba4678cc23 fix(fonts): fix CORS access error 403 of deno.dev with Origin: tauri://localhost (#2944) 2026-01-14 05:35:27 +01:00
Huang Xin e5eb3014b4 chore(unittest): test makeSafeFilename (#2943) 2026-01-14 04:08:30 +01:00
Huang Xin ed77b0bc7f fix(android): only dismiss unpinned sidebar and notebook with back key, closes #2920 (#2939) 2026-01-13 15:42:03 +01:00
Huang Xin 434a44e62c feat(proofread): add an option for whole word replacement, closes #2934 (#2938) 2026-01-13 15:35:57 +01:00
Huang Xin aaee04c290 fix(opds): probe filename when downloading PDFs and CBZ files, closes #2921 (#2932) 2026-01-12 17:55:50 +01:00
Huang Xin 48e2bfa82c fix(android): avoid occasional crash on start on Android (#2931) 2026-01-12 16:27:25 +01:00
Huang Xin c04f19ffb4 feat: add support for exporting book files in book details dialog, closes #2919 (#2930) 2026-01-12 16:26:32 +01:00
Huang Xin 4a624e3902 fix(settings): avoid stale viewSettings after saving, closes #2912 (#2916) 2026-01-11 16:47:36 +01:00
Huang Xin f53cee9616 release: version 0.9.97 (#2909) 2026-01-10 15:51:20 +01:00
Huang Xin 30385ee5ec fix(settings): correctly setting configs for selected book in parallel read, closes #2825 (#2908) 2026-01-10 15:01:04 +01:00
江夏尧 b30dfb3e23 fix(fonts): update font CDN links to use deno.dev for improved reliability (#2906) 2026-01-10 13:47:19 +01:00
Huang Xin c0d6102857 fix(css): primary btn style for e-ink mode (#2905) 2026-01-10 13:13:32 +01:00
Huang Xin 4537c55e84 fix(css): respect css for page break and minimum font size, closes #2895 (#2903) 2026-01-10 10:30:21 +01:00
Huang Xin aff94c0ab8 fix(windows): update shortcut to point to current installation, closes #2878 (#2902) 2026-01-10 10:26:32 +01:00
Huang Xin fd70836308 fix(windows): update shortcut to point to current installation, closes #2878 (#2901) 2026-01-10 09:53:40 +01:00
Huang Xin 1b0c94b9a5 fix(opds): temporary workaround for self-signed cert for OPDS server, closes #2871 (#2900)
Related to https://github.com/seanmonstar/reqwest/issues/1554
2026-01-10 08:47:12 +01:00
Huang Xin 7cb523eefc fix(metadata): no need to download book to display book details, closes #2857 (#2897) 2026-01-09 15:58:01 +01:00
Huang Xin 941be80cc4 fix(css): table and header layout, closes #2874 (#2896) 2026-01-09 15:06:37 +01:00
Huang Xin 5eecc735aa fix(proofread): support text replacement for text with no word boundaries, closes #2889 (#2894) 2026-01-09 10:27:38 +01:00
Huang Xin cfe51d01ee fix(sanitizer): normalize non-breaking spaces for WebView compatibility (#2893)
Convert &nbsp; to &#160; before sanitization and restore after XMLSerializer
serialization. This handles the platform difference where XMLSerializer
produces different representations of non-breaking spaces (&nbsp;, &#160;,
or \u00A0) across different WebView implementations.
2026-01-09 09:30:04 +01:00
Huang Xin 20ae09c52d perf(sync): persist partial library sync to prevent full retry on failure (#2892) 2026-01-09 09:23:02 +01:00
Huang Xin b868146129 feat(config): add an option to toggle footbar by tapping on the footbar (#2891) 2026-01-09 03:22:34 +01:00
Hermotimus a312080f7c fix(tts): footnotes anchors and superscript filtering (#2890) 2026-01-09 02:16:07 +01:00
Huang Xin 462ca46fee feat(eink): optimize color and layout for e-ink mode (#2887) 2026-01-08 17:29:33 +01:00
Huang Xin 93228c4b2a fix: avoid hydration mismatch for tauri app (#2884) 2026-01-08 04:05:10 +01:00
Huang Xin 2ff10f781f feat(annotator): support vertical layout for annotation editor (#2882) 2026-01-07 16:18:57 +01:00
Huang Xin 9614c62360 feat(annotator): instant highlighting with mouse, touch or pen (#2881) 2026-01-07 16:13:37 +01:00
lcd1232 5ffaac5e67 fix(login): fix login message field (#2879) 2026-01-07 12:55:11 +01:00
Huang Xin 71af91608f feat(annotator): support editing text range of highlights (#2870) 2026-01-05 16:25:58 +01:00
Huang Xin 9603b61776 refactor(annotatot): more cleaner text selector on Android (#2867) 2026-01-05 08:53:38 +01:00
Huang Xin eed84a059a fix(search): should be able to terminate search when sidebar is invisible (#2863) 2026-01-04 17:10:49 +01:00
Huang Xin 604ef65719 perf(search): use cache for search results (#2861) 2026-01-04 12:56:40 +01:00
Huang Xin 483d536ca4 feat(search): support search terms history, closes #2836 (#2859) 2026-01-04 07:47:43 +01:00
Huang Xin 69a51c5880 compat(footnote): support alt footnote inside placeholder anchor (#2858) 2026-01-04 06:06:24 +01:00
Huang Xin ca8d25341e compat(opds): support Komga OPDS v1.2 and v2, closes #2839 (#2851) 2026-01-03 18:04:39 +01:00
Huang Xin 8a263235ed feat(search): add annotations navigation bar, closes #2060 (#2849) 2026-01-03 14:57:10 +01:00
Huang Xin fb41ff5d0c fix(tts): reduce the pause duration between sentences in Edge TTS, closes #2837 (#2844) 2026-01-03 08:44:15 +01:00
Huang Xin a5e09e8454 feat(search): add search results navigation bar, closes #1183 (#2842)
Add a floating navigation component that appears when search results exist.
The component includes:
   - Top bar: displays search term and current section with TOC and close buttons
   - Bottom bar: search results, previous, and next navigation buttons
   - Page-based navigation using isCfiInLocation to skip between pages with results
2026-01-03 08:10:23 +01:00
Huang Xin 730ee21651 fix(android): don't navigate back with the back button for more predictable navigation (#2835)
This will revert the link navigation with back button in #2454.
This will close #2833 and more user reports that found back link navigation confusing.
2026-01-02 15:44:47 +01:00
Huang Xin dea43445c3 fix(layout): set image width for vertical inline images (#2832) 2026-01-02 14:54:02 +01:00
Huang Xin 4f0ae78d43 fix(ios): fixed error when importing file with urlencoded names (#2831) 2026-01-02 14:35:11 +01:00
Huang Xin d9116d619a feat(css): support overriding link color (#2830) 2026-01-02 14:15:02 +01:00
Huang Xin c080e6fdd3 fix(metadata): fixed sometimes svg book cover is rendered blank (#2829) 2026-01-02 14:13:24 +01:00
Huang Xin d3752dadc6 fix(library): upload files also for open with import and opds import, closes #2826 (#2827) 2026-01-02 13:32:52 +01:00
André Angelantoni e21ef53a3d fix(settings): ensure global settings sync across all open panes (#2818)
* fix(settings): ensure global settings sync across all open panes

Previously, changing a global setting (like font size) only updated the currently active book pane, causing split-view panes to desynchronize.

This commit updates the settings logic to propagate global changes to all open book instances immediately. It also ensures that settings UI panels correctly re-render when preferences are updated from outside the component.

* refactor to reuse some code

* fix: pointer in doc check

---------

Co-authored-by: André Angelantoni <andre.angelantoni@performantlabs.com>
Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2026-01-01 07:12:07 +01:00
Huang Xin 71e97998b6 feat(transfer): add a background transfer queue system for book uploads/downloads (#2821) 2025-12-31 08:40:28 +01:00
Huang Xin 22f9c45232 fix(annotator): delay highlight action to prevent immediate onShowAnnotation trigger (#2820) 2025-12-31 08:35:26 +01:00
Huang Xin a42897ec5c feat(annotator): support quick actions for text selection, closes #2505 (#2813) 2025-12-30 10:32:01 +01:00
Huang Xin 58a5c1625c fix(css): only override background color of hr when it has background image (#2808) 2025-12-30 10:24:37 +01:00
Huang Xin d53fc09e1e chore: bump opennext, wrangler and serwist to the latest versions (#2807) 2025-12-29 05:12:32 +01:00
Huang Xin 11fecb5dc0 chore: bump tauri to latest dev (#2805) 2025-12-29 04:07:58 +01:00
Huang Xin f4587663b5 chore(ios): drop support for iOS below 15.0 (#2804) 2025-12-29 03:41:12 +01:00
Huang Xin b76a2adf61 compat(epub): detect missing writing direction for some EPUBs (#2803) 2025-12-28 19:43:49 +01:00
Huang Xin 988fbc8c85 fix(settings): override book text align of left (#2802) 2025-12-28 18:02:56 +01:00
Huang Xin f944ad9b9f feat(annotator): support rendering vertical annotation bubbles (#2801) 2025-12-28 13:52:12 +01:00
Huang Xin 335d91b9c9 fix(layout): dismiss annotation popup on mobile when navigating (#2799) 2025-12-28 07:18:03 +01:00
Huang Xin 173404eaad feat(annotator): show notes popup when annotation bubble icon is clicked, closes #1845 (#2798) 2025-12-27 17:31:47 +01:00
Huang Xin 674fed5230 fix(annotator): adjust underline position to the center of two lines, closes #2772 (#2793) 2025-12-26 09:51:02 +01:00
Huang Xin 12d284cd22 fix(layout): fixed line clamp for config items on small screens (#2792) 2025-12-26 05:05:37 +01:00
Huang Xin 77037c8adb fix(sw): use NetworkFirst for navigation to prevent blank page on updates (#2784) 2025-12-24 15:19:14 +01:00
Huang Xin 6984393ed1 refactor(proofread): refactor UI and i18n for proofread tool (#2783) 2025-12-24 14:46:33 +01:00
Huang Xin 3abef6ea75 fix(opds): correctly parse file extension for CBZ files from OPDS servers, closes #2765 (#2771) 2025-12-23 08:53:50 +01:00
Huang Xin 4ae1ab7dd0 fix(layout): workaround for hardcoded image container (#2769) 2025-12-23 06:31:09 +01:00
Huang Xin 69fb22119b fix(layout): tweak insets for vertical layout (#2767) 2025-12-23 06:01:06 +01:00
Huang Xin 3a6c18c6d5 feat(font): support OpenType feature of vrt2 and vert for vertical replacement of some punctuations (#2764) 2025-12-22 18:39:09 +01:00
Huang Xin 7db1bc460d chore(pwa): migrate from next-pwa to serwist (#2762) 2025-12-22 16:10:09 +01:00
Huang Xin a460e609fa refactor(file): avoid eviction race of chunks cache (#2761) 2025-12-22 05:59:40 +01:00
Huang Xin 4b4ebdbdaa fix(pdf): avoid frequent eviction of chunk cache from worker thread, closes #2757 (#2759) 2025-12-21 17:47:41 +01:00
Huang Xin 9358a06839 compat(layout): grid layout with fit covers on older browsers, closes #2745 (#2756) 2025-12-21 08:52:57 +01:00
Huang Xin a7baf6cc9f fix(tts): prompt users to log in to use Edge TTS on the web version (#2749) 2025-12-19 17:54:17 +01:00
Huang Xin 15dc669f35 chore: update app metainfo (#2744) 2025-12-19 04:54:24 +01:00
Huang Xin c853957512 release: version 0.9.96 (#2743) 2025-12-19 03:54:17 +01:00
Huang Xin 8a4e22e423 refactor: temporarily disable the proofreading feature for a hotfix release ahead of a major refactor (#2742) 2025-12-19 03:45:42 +01:00
Huang Xin 8a43c58fd4 fix(tts): resolve Edge TTS being blocked in certain regions (#2741)
This should close #2739 and close #1821.
2025-12-19 03:24:51 +01:00
Qianxue Ge 54fdf5f1fd feat(replacement): text replacement feature for EPUB books (#2725)
* add: basic ui replacement menu

* feat(replacement): modified ViewSettings interface and added Replacement type

* add: frontend menu ui to annotation settings
- create replacementoptions file for 4 fix options: fix once, fix in library, fix in book, fix in library
-integrate with annotator.tsx
only frontend changes, but initialzied in backend

* add: delete global option and click gear option to get rid of menu

* docs: add test cases for replacementoptions file

* edits to enable readest to build

* basic changes for rule types

* replacement transformer file added

* additional support code added

* interim updates to replacement.ts file

* adding console log statements to confirm functionality without frontend

* adding more console logs for debugging; i think i got my replacement working, will clean console logs and add actual tests now.

* figured out how to get my transformer to work. replacement doesnt actually work yet. figuring that out rn. committing before i destroy something, lol

* replcement logic working with hard coded tests. code is cleaned up with minimal console logs. actual replacement logic + testing is next :)

* test suite built, and fully passing. made consle log edits too.

* added more replacement rules, but figuring out why they arent being implemented by my code.

* cleaning up test suite to not break when there are 0 rules; test is commited with 1 local rule. not sure if that rule is going to copy over when i merge.

* feat(replacement): Add text field, case sensitivity checkbox, and confirmation dialog to ReplacementOptions

- Add text input field for replacement text with placeholder
- Add 'Case Sensitive' checkbox (default: unchecked/case-insensitive)
- Implement two-step confirmation flow with Back/Confirm buttons
- Show preview of original text, replacement text, scope, and case sensitivity
- Disable scope buttons until replacement text is entered
- Display truncated preview for long selected text (>50 chars)
- Export ReplacementConfig type for use in parent components

* feat(replacement): Add 30-word limit and integrate new ReplacementOptions component

- Add MAX_REPLACEMENT_WORDS constant (30 words)
- Add getWordCount() utility function for word counting
- Show warning toast when word limit exceeded on Text Replacement click
- Replace old fix handlers with single handleReplacementConfirm()
- Integrate with new ReplacementConfig (replacementText, caseSensitive, scope)
- Display success toast with scope and case sensitivity info on confirm

* fix(build): Add ReplacementMenu placeholder component

- Create placeholder component to fix missing import error in reader/page.tsx
- Component returns null for now, to be implemented with global replacement rules

* test(replacement): Add comprehensive tests for ReplacementOptions and word limit

ReplacementOptions.test.tsx:
- Test rendering of text input, checkbox, and scope buttons
- Test case sensitivity checkbox toggle and state
- Test disabled buttons when no replacement text entered
- Test confirmation dialog flow and Back/Confirm buttons
- Test click outside and Cancel button behavior
- Test full replacement flow with all options

wordLimit.test.ts:
- Test word counting with various inputs (spaces, newlines, unicode)
- Test 30-word limit boundary conditions
- Test case-sensitive vs case-insensitive matching logic
- Test edge cases (empty string, long words, punctuation)

* refactor: removed unused initial definition of Replacement

* feat: added replacement rules window in bookmenu

* test: added tests to verify the replacement rules window renders book and global replacement rules, and it opens when bookmenu item is clicked

* feat: added Replacement tab in SettingsDialog, displays global rules

* feat(replacement): connected front-end to functions. todo: fix the automatic reload functionality.

* fix(replacement): simplified re-rendering logic, doesn't fail on epubs anymore.

* test: add integration tests for text replacement functionality

* fix: added single rules section to ReplacementRulesWindow

* fix(replacement): added null checks to some unsafe calls in integration tests

* fix(replacements): added non-null assertion operator for a previously initialized variable

* refactor: created ReplacementPanel and edited style of inputs

* feat: disable the edit feature for selected phrase

* refactor: use toast instead of banner for confirmation msg

* feat: automatically reload the page to apply changes

* feat: disable global rule for book if deleted in book view

* fix(replacement): Improve popup positioning and eliminate ghost animation

- Add viewport boundary detection to keep popup within visible area
- Calculate position only once on mount to prevent jumping when other UI appears
- Use visibility: hidden until position is calculated to eliminate ghost animation
- Add max-height with overflow-y: auto for scrollable content
- Popup now appears directly in correct position without two-step animation

* fix: implement single-instance replacement with persistence

- Add sectionHref to TransformContext for section tracking
- Add singleInstance, sectionHref, occurrenceIndex fields to ReplacementRule
- Pass section name from FoliateViewer to transformer context
- Switch transformer from DOM-based to string-based replacement
- Handle single-instance rules with section matching and occurrence tracking
- Update Annotator to track occurrence index and apply direct DOM changes
- Persist single-instance rules for refresh survival

Single-instance replacements now:
1. Apply immediately via direct DOM modification
2. Store occurrence index and section for precise targeting
3. Persist across page refreshes

* fix: allow multiple single-instance replacements for same word

Single-instance rules now always create new entries instead of merging.
This fixes the issue where replacing multiple occurrences of the same
word would overwrite previous rules.

The transformer applies rules in sequence, so each rule targets
occurrence index 0 of the current (modified) string, allowing
cascading replacements to work correctly after refresh.

* fix: prevent cascading replacements and add wholeWord support

- Add wholeWord field to ReplacementRule for word boundary matching
- Track replaced regions to prevent replacement text from being re-matched
- Fix cascading replacement issue where replacement text was matched again
- Apply replacements from right to left to preserve positions
- Support whole word matching with \b boundaries for both single-instance and regular rules

* Fix whole-word matching for replacement rules

- Auto-enforce whole-word matching for simple word patterns (letters only)
- Add HTML tag boundary checks to prevent matching across tags
- Add double-check validation for whole-word matches
- Prevent matching 'and' inside words like 'England', 'stand', 'understand'
- Add comprehensive logging for debugging replacement issues

* test: added rAF in setup to for ReplacementOptions tests

* fix: only allow replacement for epubs, remove replacement rendering for non-epubs, add test cases

* refactor: refactored replacement logic for case sensitivity and word boundaries

* test: added tests for scope precedence and case sensitivity across scopes

* refactor: removed unnecessary code from testing

* feat: able to display, edit, and delete single-instance rules in book settings

* fix: connected case sensitive checkbox to backend, fixed merge and delete logic

* test: updated test cases to reflect changes on case sensitivity and rules rendering

* test: modified ReplacementOptions test to remove unnecessary case sensitive check from merge

* fix: add logic for grayed out button for non-epubs

* chore: update foliate-js submodule from upstream merge

* fix: resolve all TypeScript/ESLint linting errors

- Fix prefer-const error in ReplacementOptions.tsx
- Fix set-state-in-effect error in ReplacementRulesWindow.tsx (use lazy initializer)
- Replace all @typescript-eslint/no-explicit-any with proper types (ReplacementRule, unknown, etc.)
- Fix unused error variables in replacement.ts (prefix with _)
- Remove unused eslint-disable directives
- Add missing ReplacementRule import in ReplacementPanel.tsx

* fix: add localStorage mock to vitest setup

- Fixes test failures in ReplacementRulesWindow and SettingsDialog tests
- localStorage mock ensures all Storage API methods are available in test environment

* fix: resolve ESLint and TypeScript build errors

- Fix all remaining @typescript-eslint/no-explicit-any errors in test files
- Fix unused error variables in replacement.ts (prefix with _)
- Fix TypeScript error in ReplacementRulesWindow.tsx (move @ts-ignore to correct location)
- All ESLint checks now pass
- Web and Tauri builds compile successfully

* fix: remove lookbehind regex for browser compatibility

- Replace lookbehind assertions (?<!...) with manual boundary checking
- Add isUnicodeWordChar helper function for manual Unicode word boundary detection
- Apply manual boundary checks in applyMultiReplacement and applySingleInstance
- Fixes build_web_app check failures by avoiding lookbehind in compiled output
- Maintains whole-word matching functionality for both ASCII and Unicode patterns

* fix: update tauri-utils version to 2.8.1 to resolve duplicate symbol error

- Update local tauri-utils version from 2.8.0 to 2.8.1 to match crates.io version
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linker error
- Ensures all dependencies use the same tauri-utils version

* fix: use local tauri path directly to resolve version conflicts

- Change tauri dependency to use local path instead of version requirement
- This ensures all dependencies use the same local tauri version (2.9.3)
- Fixes 'links = Tauri' conflict error in Rust linting
- The patch.crates-io should still work for transitive dependencies

* fix: use version requirement with patch for tauri dependency

- Revert to using version requirement '2' instead of direct path
- Rely on [patch.crates-io] to use local tauri version
- Remove Cargo.lock to force fresh dependency resolution
- This should resolve the 'links = Tauri' conflict by ensuring
  all tauri dependencies (direct and transitive) use the patched version

* fix: remove plugin patches that cause resolution errors

- Remove all tauri-plugin git patches from [patch.crates-io]
- Keep only tauri, tauri-utils, and tauri-build patches
- Plugins from crates.io will use the patched tauri via transitive dependencies
- Fixes error: patch for tauri-plugin-oauth failed to resolve

* fix: update tauri submodule with tauri-utils version fixes

* fix: revert tauri submodule and update tauri-utils to 2.8.0

- Revert submodule changes that can't be pushed to remote
- Update local tauri-utils version to 2.8.0 to match other packages
- This avoids the need to modify the submodule

* fix: add tauri-plugin to workspace and patch to resolve duplicate symbol error

- Add packages/tauri/crates/tauri-plugin to workspace members
- Add tauri-plugin patch to [patch.crates-io]
- This ensures all tauri dependencies use local versions
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linking error

* chore: restore Cargo.lock from upstream

- Restore the original Cargo.lock from readest/readest main branch
- This ensures reproducible builds and matches upstream
- The lock file will be updated by cargo when dependencies change

* fix: resolve TypeScript errors in test files

- Fix ReplacementOptions.test.tsx: add optional chaining for possibly undefined values
- Fix ReplacementRulesWindow.test.tsx: use proper type assertions for store setState calls
- Use (store.setState as unknown as (state: unknown) => void) pattern for partial state updates

* fix: prevent race condition when deleting replacement rules rapidly

- Add isReloading state to track ongoing delete/edit operations
- Prevent multiple rapid deletions that cause runtime errors during page reload
- Show warning toast when user tries to delete while reload is in progress
- Add finally blocks to ensure isReloading is always reset
- This prevents the 'book doesn't finish rerendering' error

* fix: allow phrases and lines with quotes for single-instance replacements

- Updated isWholeWord() to allow phrases (text with spaces or punctuation)
- Phrases are always allowed for single-instance replacements
- Only single words are checked for partial word matches
- Fixes issue where lines with quotes couldn't be replaced
- Added detailed logging for debugging phrase detection

* fix: allow selections with boundary punctuation and fix pattern matching for punctuation

- Updated isWholeWord() to explicitly allow selections that start or end with punctuation (e.g., 'tis, off;, look,)
- Fixed normalizePattern() to handle patterns with leading/trailing punctuation correctly
- Word boundaries are now only added around the word part, not the punctuation
- Fixes issue where replacements like 'scholar;' were not matching correctly

* fix: escape HTML entities in replacement text to preserve angle brackets

- Added escapeHtmlEntities() function to escape HTML special characters
- Apply HTML escaping to replacement text in both multi and single-instance replacements
- Fixes issue where replacement text like '<<AND>>' was being interpreted as HTML tags
- Angle brackets and other HTML entities are now properly escaped and displayed correctly

* fix: revert Tauri backend changes and resolve package.json conflict

- Revert Cargo.toml and src-tauri/Cargo.toml to match upstream/main
- Resolve @tauri-apps/cli version conflict (2.9.5 -> 2.9.6)
- These changes are not related to the replacement feature implementation

* fix: update pnpm-lock.yaml to match @tauri-apps/cli 2.9.6

* removed useless tests and backend tests from ReplacementOptions integration testing suite

* chore: revert foliate-js submodule to match readest/readest main

* fix: refactored wordLimit logic into a separate util file

* fix: removed additional pr description

* refactor: rewrite replacement transformer to use DOM-based approach
replace string manipulation with DOMParser and TreeWalker
follow pattern from simpleecc transformer

* style: format code with prettier

* fix: remove unused string-manipulation functions

* fix: refactored display dialog logic to match other dialogs

* fix: enabled global rule deletion in book menu

* fix: removed ReplacementPanel from library settings

* fix: deleted SettingsDialog.replacement.test.tsx since we no longer need to display replacements in library settings

* fix: removed text replacement tab from settings dialog

* fix: applied prettier code formatter to replacement rules window

* chore: fix formatting and remove unused file listed by chrox

* chore: format all changed files from pr 2693 and revert pnpm-lock

* rebased Cargo.lock, package.json, pnpm-lock.yaml to upstream main
edits to enable readest to build

* basic changes for rule types

* replacement transformer file added

* additional support code added

* interim updates to replacement.ts file

* adding console log statements to confirm functionality without frontend

* adding more console logs for debugging; i think i got my replacement working, will clean console logs and add actual tests now.

* figured out how to get my transformer to work. replacement doesnt actually work yet. figuring that out rn. committing before i destroy something, lol

* replcement logic working with hard coded tests. code is cleaned up with minimal console logs. actual replacement logic + testing is next :)

* test suite built, and fully passing. made consle log edits too.

* added more replacement rules, but figuring out why they arent being implemented by my code.

* cleaning up test suite to not break when there are 0 rules; test is commited with 1 local rule. not sure if that rule is going to copy over when i merge.

* add: basic ui replacement menu

* add: frontend menu ui to annotation settings
- create replacementoptions file for 4 fix options: fix once, fix in library, fix in book, fix in library
-integrate with annotator.tsx
only frontend changes, but initialzied in backend

* add: delete global option and click gear option to get rid of menu

* docs: add test cases for replacementoptions file

* feat(replacement): modified ViewSettings interface and added Replacement type

feat(replacement): modified viewsettings interface and added ReplacementRulesConfig

* feat(replacement): Add text field, case sensitivity checkbox, and confirmation dialog to ReplacementOptions

- Add text input field for replacement text with placeholder
- Add 'Case Sensitive' checkbox (default: unchecked/case-insensitive)
- Implement two-step confirmation flow with Back/Confirm buttons
- Show preview of original text, replacement text, scope, and case sensitivity
- Disable scope buttons until replacement text is entered
- Display truncated preview for long selected text (>50 chars)
- Export ReplacementConfig type for use in parent components

* feat(replacement): Add 30-word limit and integrate new ReplacementOptions component

- Add MAX_REPLACEMENT_WORDS constant (30 words)
- Add getWordCount() utility function for word counting
- Show warning toast when word limit exceeded on Text Replacement click
- Replace old fix handlers with single handleReplacementConfirm()
- Integrate with new ReplacementConfig (replacementText, caseSensitive, scope)
- Display success toast with scope and case sensitivity info on confirm

* fix(build): Add ReplacementMenu placeholder component

- Create placeholder component to fix missing import error in reader/page.tsx
- Component returns null for now, to be implemented with global replacement rules

* test(replacement): Add comprehensive tests for ReplacementOptions and word limit

ReplacementOptions.test.tsx:
- Test rendering of text input, checkbox, and scope buttons
- Test case sensitivity checkbox toggle and state
- Test disabled buttons when no replacement text entered
- Test confirmation dialog flow and Back/Confirm buttons
- Test click outside and Cancel button behavior
- Test full replacement flow with all options

wordLimit.test.ts:
- Test word counting with various inputs (spaces, newlines, unicode)
- Test 30-word limit boundary conditions
- Test case-sensitive vs case-insensitive matching logic
- Test edge cases (empty string, long words, punctuation)

* refactor: removed unused initial definition of Replacement

* feat: added replacement rules window in bookmenu

* test: added tests to verify the replacement rules window renders book and global replacement rules, and it opens when bookmenu item is clicked

* feat: added Replacement tab in SettingsDialog, displays global rules

* fix: added single rules section to ReplacementRulesWindow

* refactor: created ReplacementPanel and edited style of inputs

* feat(replacement): connected front-end to functions. todo: fix the automatic reload functionality.

* fix(replacement): simplified re-rendering logic, doesn't fail on epubs anymore.

* test: add integration tests for text replacement functionality

* fix(replacement): added null checks to some unsafe calls in integration tests

* fix(replacements): added non-null assertion operator for a previously initialized variable

* feat: disable the edit feature for selected phrase

* refactor: use toast instead of banner for confirmation msg

* feat: automatically reload the page to apply changes

* feat: disable global rule for book if deleted in book view

* fix(replacement): Improve popup positioning and eliminate ghost animation

- Add viewport boundary detection to keep popup within visible area
- Calculate position only once on mount to prevent jumping when other UI appears
- Use visibility: hidden until position is calculated to eliminate ghost animation
- Add max-height with overflow-y: auto for scrollable content
- Popup now appears directly in correct position without two-step animation

* fix: only allow replacement for epubs, remove replacement rendering for non-epubs, add test cases

* fix: add logic for grayed out button for non-epubs

* fix: resolve all TypeScript/ESLint linting errors

- Fix prefer-const error in ReplacementOptions.tsx
- Fix set-state-in-effect error in ReplacementRulesWindow.tsx (use lazy initializer)
- Replace all @typescript-eslint/no-explicit-any with proper types (ReplacementRule, unknown, etc.)
- Fix unused error variables in replacement.ts (prefix with _)
- Remove unused eslint-disable directives
- Add missing ReplacementRule import in ReplacementPanel.tsx

* fix: add localStorage mock to vitest setup

- Fixes test failures in ReplacementRulesWindow and SettingsDialog tests
- localStorage mock ensures all Storage API methods are available in test environment

* fix: implement single-instance replacement with persistence

- Add sectionHref to TransformContext for section tracking
- Add singleInstance, sectionHref, occurrenceIndex fields to ReplacementRule
- Pass section name from FoliateViewer to transformer context
- Switch transformer from DOM-based to string-based replacement
- Handle single-instance rules with section matching and occurrence tracking
- Update Annotator to track occurrence index and apply direct DOM changes
- Persist single-instance rules for refresh survival

Single-instance replacements now:
1. Apply immediately via direct DOM modification
2. Store occurrence index and section for precise targeting
3. Persist across page refreshes

* fix: allow multiple single-instance replacements for same word

Single-instance rules now always create new entries instead of merging.
This fixes the issue where replacing multiple occurrences of the same
word would overwrite previous rules.

The transformer applies rules in sequence, so each rule targets
occurrence index 0 of the current (modified) string, allowing
cascading replacements to work correctly after refresh.

* fix: prevent cascading replacements and add wholeWord support

- Add wholeWord field to ReplacementRule for word boundary matching
- Track replaced regions to prevent replacement text from being re-matched
- Fix cascading replacement issue where replacement text was matched again
- Apply replacements from right to left to preserve positions
- Support whole word matching with \b boundaries for both single-instance and regular rules

* Fix whole-word matching for replacement rules

- Auto-enforce whole-word matching for simple word patterns (letters only)
- Add HTML tag boundary checks to prevent matching across tags
- Add double-check validation for whole-word matches
- Prevent matching 'and' inside words like 'England', 'stand', 'understand'
- Add comprehensive logging for debugging replacement issues

* refactor: refactored replacement logic for case sensitivity and word boundaries

* test: added tests for scope precedence and case sensitivity across scopes

* refactor: removed unnecessary code from testing

* feat: able to display, edit, and delete single-instance rules in book settings

* fix: connected case sensitive checkbox to backend, fixed merge and delete logic

* test: updated test cases to reflect changes on case sensitivity and rules rendering

* test: modified ReplacementOptions test to remove unnecessary case sensitive check from merge

* fix: resolve ESLint and TypeScript build errors

- Fix all remaining @typescript-eslint/no-explicit-any errors in test files
- Fix unused error variables in replacement.ts (prefix with _)
- Fix TypeScript error in ReplacementRulesWindow.tsx (move @ts-ignore to correct location)
- All ESLint checks now pass
- Web and Tauri builds compile successfully

* fix: update tauri-utils version to 2.8.1 to resolve duplicate symbol error

- Update local tauri-utils version from 2.8.0 to 2.8.1 to match crates.io version
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linker error
- Ensures all dependencies use the same tauri-utils version

* fix: use local tauri path directly to resolve version conflicts

- Change tauri dependency to use local path instead of version requirement
- This ensures all dependencies use the same local tauri version (2.9.3)
- Fixes 'links = Tauri' conflict error in Rust linting
- The patch.crates-io should still work for transitive dependencies

* fix: use version requirement with patch for tauri dependency

- Revert to using version requirement '2' instead of direct path
- Rely on [patch.crates-io] to use local tauri version
- Remove Cargo.lock to force fresh dependency resolution
- This should resolve the 'links = Tauri' conflict by ensuring
  all tauri dependencies (direct and transitive) use the patched version

* fix: remove plugin patches that cause resolution errors

- Remove all tauri-plugin git patches from [patch.crates-io]
- Keep only tauri, tauri-utils, and tauri-build patches
- Plugins from crates.io will use the patched tauri via transitive dependencies
- Fixes error: patch for tauri-plugin-oauth failed to resolve

* fix: add tauri-plugin to workspace and patch to resolve duplicate symbol error

- Add packages/tauri/crates/tauri-plugin to workspace members
- Add tauri-plugin patch to [patch.crates-io]
- This ensures all tauri dependencies use local versions
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linking error

* chore: restore Cargo.lock from upstream

- Restore the original Cargo.lock from readest/readest main branch
- This ensures reproducible builds and matches upstream
- The lock file will be updated by cargo when dependencies change

* fix: resolve TypeScript errors in test files

- Fix ReplacementOptions.test.tsx: add optional chaining for possibly undefined values
- Fix ReplacementRulesWindow.test.tsx: use proper type assertions for store setState calls
- Use (store.setState as unknown as (state: unknown) => void) pattern for partial state updates

* fix: allow selections with boundary punctuation and fix pattern matching for punctuation

- Updated isWholeWord() to explicitly allow selections that start or end with punctuation (e.g., 'tis, off;, look,)
- Fixed normalizePattern() to handle patterns with leading/trailing punctuation correctly
- Word boundaries are now only added around the word part, not the punctuation
- Fixes issue where replacements like 'scholar;' were not matching correctly

* fix: prevent race condition when deleting replacement rules rapidly

- Add isReloading state to track ongoing delete/edit operations
- Prevent multiple rapid deletions that cause runtime errors during page reload
- Show warning toast when user tries to delete while reload is in progress
- Add finally blocks to ensure isReloading is always reset
- This prevents the 'book doesn't finish rerendering' error

* fix: escape HTML entities in replacement text to preserve angle brackets

- Added escapeHtmlEntities() function to escape HTML special characters
- Apply HTML escaping to replacement text in both multi and single-instance replacements
- Fixes issue where replacement text like '<<AND>>' was being interpreted as HTML tags
- Angle brackets and other HTML entities are now properly escaped and displayed correctly

* fix: revert Tauri backend changes and resolve package.json conflict

- Revert Cargo.toml and src-tauri/Cargo.toml to match upstream/main
- Resolve @tauri-apps/cli version conflict (2.9.5 -> 2.9.6)
- These changes are not related to the replacement feature implementation

* fix: update pnpm-lock.yaml to match @tauri-apps/cli 2.9.6

* removed useless tests and backend tests from ReplacementOptions integration testing suite

* chore: revert foliate-js submodule to match readest/readest main

* fix: refactored display dialog logic to match other dialogs

* fix: enabled global rule deletion in book menu

* fix: removed ReplacementPanel from library settings

* fix: deleted SettingsDialog.replacement.test.tsx since we no longer need to display replacements in library settings

* fix: removed text replacement tab from settings dialog

* fix: applied prettier code formatter to replacement rules window

* fix: refactored wordLimit logic into a separate util file

* fix: removed additional pr description

* style: format code with prettier

* chore: fix formatting and remove unused file listed by chrox

* chore: format all changed files from pr 2693 and revert pnpm-lock

* fix: fixed inconsistencies from rebase

* refactor: removed unused code

* refactor: removed unintentional formatting changes

* fix: set upstream for packages/tauri-plugins to the readest branch

* fix: used original Cargo.lock file

* fix: got Cargo.lock from upstream

* fix: fetched SettingsDialog from upstream main

* fix: pointed tauri-plugins to the same commit as upstream

* chore: remove unnecssary comments from replacement.ts

* chore: fixed more unnecessary comments

---------

Co-authored-by: fatbiscuit247 <fatbiscuit247@github.com>
Co-authored-by: joon <your.email@example.com>
Co-authored-by: jarchenn <jerryc2@andrew.cmu.edu>
Co-authored-by: joon0429 <68578999+joon0429@users.noreply.github.com>
Co-authored-by: Jerry Chen <50bmg@Jerrys-MacBook-Pro-9.local>
Co-authored-by: Alicia Chen <aliciach@andrew.cmu.edu>
Co-authored-by: Jerry Chen <50bmg@MacBook-Pro-7.local>
Co-authored-by: Jerry Chen <50bmg@macbook-pro-158.wifi.local.cmu.edu>
Co-authored-by: fatbiscuit247 <136537548+fatbiscuit247@users.noreply.github.com>
2025-12-17 10:06:59 +08:00
Huang Xin fe50b513b3 fix(layout): line clamp opds url, closes #2726 (#2731) 2025-12-16 17:03:15 +01:00
Huang Xin 17c7fa8f41 fix: make sidebar and notebook pin states persist after refresh (#2730) 2025-12-16 16:16:21 +01:00
Huang Xin 2533560d11 fix(layout): fix bleed layout for images (#2729) 2025-12-16 15:46:13 +01:00
Huang Xin 5850a16afd fix: add stats API and fix fd leak, closes #2323 (#2723) 2025-12-16 06:51:48 +01:00
Huang Xin 7063d62b13 fix(settings): screen brightness setting only applies to the reader page, closes #2717 (#2720) 2025-12-15 06:04:29 +01:00
dependabot[bot] 0bd6a217ae chore(deps): bump actions/cache from 4 to 5 in the github-actions group (#2719)
Bumps the github-actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 4 to 5
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 05:14:57 +01:00
Huang Xin b7df294d78 feat(bookshelf): add group books button in context menu, closes #2698 (#2718) 2025-12-15 05:02:52 +01:00
Huang Xin 5aa78f2554 fix(opds): expose X-Content-Length header for CORS requests (#2715) 2025-12-14 19:06:09 +01:00
Huang Xin e740571c33 feat(opds): instant search bar for opds catalog, closes #2707 (#2714) 2025-12-14 18:25:47 +01:00
Huang Xin e6d9913f4e fix(layout): make sure annotation popups can be accessible in some edge cases, closes #2704 (#2713) 2025-12-14 05:52:55 +01:00
Huang Xin 1869a863a3 fix(layout): fixed max inline width not applied for EPUBs, closes #2706 (#2711) 2025-12-13 18:52:26 +01:00
Huang Xin 524de92f5e feat(macOS): add open file global menu for macOS, closes #2692 (#2708) 2025-12-13 17:36:25 +01:00
Huang Xin c1530cc5c4 feat(iOS): support open file with Readest in Files App, closes #2334 (#2705) 2025-12-13 13:28:03 +01:00
Huang Xin 730fadb834 fix(web): fixed router glitches for library page after returned from reader (#2703) 2025-12-13 08:08:42 +01:00
Huang Xin 383e5c61b1 chore: bump next.js to version 16.0.10 (#2702) 2025-12-13 07:54:15 +01:00
Huang Xin 6d42086fa7 fix(layout): fixed the layout of the selector of the translator providers (#2701) 2025-12-13 05:14:12 +01:00
mikepmiller 5a20fae204 feat(ui): progress info with cycleable display modes (#2682)
* Hideable Progress View
* feat: cycle between progress info modes

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-12-12 08:19:07 +01:00
Huang Xin 34fd64c5c4 fix(sync): handle special characters in filenames when downloading (#2694) 2025-12-11 19:20:27 +01:00
Huang Xin 0874fb0764 chore: bump tauri to the latest dev branch (#2690) 2025-12-11 13:46:41 +01:00
Huang Xin e03ed5b604 fix(layout): fix responsive layout for footnote popup (#2688) 2025-12-11 10:15:56 +01:00
Huang Xin 41edc89ac7 fix(pwa): don't cache api requests and cache client-side navigation routes (#2687) 2025-12-11 07:30:19 +01:00
Huang Xin f0a470398d chore(pwa): more aggressive offline cache for the web version (#2686) 2025-12-11 05:24:45 +01:00
Huang Xin 51008d81fb fix(opds): select proper opds search link (#2683) 2025-12-10 19:56:27 +01:00
Huang Xin 9828904674 feat(opds): added books catalog from standardebooks.org (#2681) 2025-12-10 18:20:56 +01:00
Huang Xin 2670d835b3 fix(comic): fixed layout for comic books, closes #2672 (#2680) 2025-12-10 18:08:11 +01:00
Huang Xin 8b7bafc4b6 chore(koplugin): add version info in the meta file (#2679) 2025-12-10 16:52:28 +01:00
Huang Xin ca759e0246 fix(tts): avoid false default en language code for TTS (#2678) 2025-12-10 16:24:01 +01:00
Huang Xin 5141be1c3f fix(iap): don't initialize billing on Android without google play service, closes #2630 (#2677) 2025-12-10 15:34:23 +01:00
Huang Xin 669d3950e2 chore: repackaging readest koplugin for updater, closes #2669 (#2676) 2025-12-10 13:37:32 +01:00
Huang Xin b95895cecf compat(opds): fallback to Basic auth if no WWW-Authenticate challenge in the response headers, closes #2656 (#2673) 2025-12-10 09:57:44 +01:00
Huang Xin 80e11bb0ce refactor(layout): refactor page margins for pixel precision, closes #2652 (#2663) 2025-12-09 16:19:14 +01:00
Huang Xin de3a539621 fix(footnote): add custom attributes for footnote in sanitizer, closes #2651 (#2657) 2025-12-09 05:27:52 +01:00
jacobi petrucciani b425bfdc89 chore: add rust and node deps to the nix devshell (#2655) 2025-12-09 04:44:53 +01:00
Huang Xin 1d1fbdffdb fix(macOS): delay writing to clipboard to ensure it won't be overridden by system clipboard actions, closes #2647 (#2649) 2025-12-08 14:05:27 +01:00
Huang Xin 50cd7f80c6 feat: refresh account info after managing cloud storage (#2648) 2025-12-08 13:13:20 +01:00
Huang Xin 6eb7d91122 release: version 0.9.95 (#2646) 2025-12-08 08:59:21 +01:00
Huang Xin 1fb468b3a6 fix(epub): support SVG cover for ebooks from standardebooks.org (#2645) 2025-12-08 08:52:17 +01:00
Huang Xin 3c7d95cf10 fix(footnote): responsive popup size so that on small screen it won't overflow (#2644) 2025-12-08 07:40:30 +01:00
Huang Xin ba3f060cc4 feat: add support for importing from a directory recursively, closes #179 (#2642) 2025-12-08 07:16:57 +01:00
Huang Xin 11bc7497e8 feat: add support for renaming bookshelf groups (#2639) 2025-12-07 10:04:23 +01:00
Huang Xin fb5d149413 fix: enable shared-intent event listener only on Android for now (#2638) 2025-12-07 06:45:56 +01:00
Huang Xin 42b47d73b7 feat: support cloud storage management (#2636) 2025-12-06 20:25:40 +01:00
Huang Xin 00f36af03a feat(opds): add support to search in OPDS, closes #2598 (#2634) 2025-12-06 12:13:45 +01:00
Huang Xin b78466ca93 fix(opds): relax img-src CSP to support images served from arbitrary HTTP/HTTPS hosts and ports, closes #2631 (#2633) 2025-12-06 04:22:15 +01:00
Huang Xin 4e6f146b8f feat(android): support opening shared files from other apps, closes #2484 (#2628) 2025-12-05 18:13:06 +01:00
Huang Xin cbdd4940d0 fix(android): intercept back button press for Android 15+, closes #2454 (#2626) 2025-12-05 16:27:09 +01:00
Huang Xin d022cb984a chore: bump various dependencies (#2624) 2025-12-05 07:48:03 +01:00
Huang Xin 8de6fa267e fix(cache): invalidate config and doc cache, closes #2595 and closes #2572 (#2623) 2025-12-05 07:03:57 +01:00
Huang Xin b08b7de8e9 fix(tts): fixed highlighting of current sentence for native tts on Android, closes #2620 (#2621) 2025-12-05 04:05:18 +01:00
Huang Xin a232a39f0e fix(pdf): Fixed zoomed layout and hand tool event handling, closes #2596 (#2617) 2025-12-04 19:22:01 +01:00
Huang Xin fad7966fc4 fix(layout): auto two-column layout for unfolded screen, closes #2588 (#2615) 2025-12-04 06:56:23 +01:00
Huang Xin a1487fd60c fix: get rid of the context menu for touch screen or stylus device when selecting text, closes #2579 (#2614) 2025-12-04 06:04:06 +01:00
Huang Xin 9606e315d4 fix(layout): hide overflow of children elements in duokan bleed, closes #2597 (#2613) 2025-12-04 03:40:43 +01:00
Huang Xin 978673268b chore: bump next.js to version 16.0.7 (#2612) 2025-12-04 02:34:10 +01:00
Huang Xin 70158a7f15 refactor(opds): use catalog id instead of credentials in url params, closes #2599 (#2606) 2025-12-03 08:50:55 +01:00
Huang Xin 18d65a2c5b fix(annotator): don't copy selection to notebook with keyboard shortcut by default, closes #2603 (#2605) 2025-12-03 07:08:00 +01:00
Huang Xin 1b0c2afad7 fix(layout): fixed scrollable layout in the about readest window, closes #2593 (#2604) 2025-12-03 06:23:02 +01:00
Huang Xin cef444d374 fix: disable saving last book cover with playstore variant, closes #2600 (#2602) 2025-12-03 05:41:44 +01:00
Huang Xin 75f6efe27a compat(opds): add User-Agent header to fix downloads from Calibre Web OPDS (#2592) 2025-12-02 10:27:33 +01:00
Huang Xin 852f9f40ec chore: fix cross compiling of thumbnail extension (#2587) 2025-12-02 02:27:03 +08:00
Huang Xin b9dadc0f4f chore: update flathub metainfo (#2586) 2025-12-01 18:07:29 +01:00
Huang Xin d120cf6573 release: version 0.9.94 (#2585) 2025-12-01 17:46:33 +01:00
Huang Xin d2b3c165c7 fix(opds): use custom header for content length when streaming content (#2584) 2025-12-01 17:28:36 +01:00
Huang Xin 742f06ce05 fix(toc): scroll to current toc item and anchor in TTS mode, closes #2574 (#2583) 2025-12-01 15:50:46 +01:00
Huang Xin e28dabce65 fix(layout): refactor full height for edge to edge env and in browser env (#2582) 2025-12-01 13:48:14 +01:00
Huang Xin 33ef781e21 feat: also convert quotation marks between Chinese variants (#2578) 2025-12-01 09:27:22 +01:00
Huang Xin 02e885fbb7 fix(opds): workaround parsing of encoded params in proxied url in cloudflare (#2575) 2025-12-01 05:49:04 +01:00
Huang Xin 2ca3561093 feat(opds): support downloading ebooks from OPDS catalogs (#2571) 2025-11-30 21:22:54 +01:00
Huang Xin 97f021da87 fix(extensions): add missing tauri conf for windows build (#2563) 2025-11-28 05:56:45 +01:00
Huang Xin bd2b45e1c1 refactor(extensions): move windows-thumbnail to extensions (#2562) 2025-11-28 05:44:11 +01:00
AlI 2b6f7b71b0 feat(windows): Add explorer thumbnail registration and installer hook… (#2557)
* feat(windows): Add Windows Explorer thumbnail support for ebooks (closes #2534)

- Implement IThumbnailProvider COM handler for Windows Shell integration
- Support EPUB, MOBI, AZW, AZW3, KF8, FB2, CBZ, CBR formats
- Add cover extraction with Readest icon overlay
- Register thumbnail handler via NSIS installer hooks
- Only show thumbnails when Readest is the default app for the file type

* chore: clean up build script for thumbnail extension

---------

Co-authored-by: chrox <chrox.huang@gmail.com>
2025-11-27 21:31:52 +01:00
Huang Xin 0bad380a50 fix(layout): fixed navigation parameters not synced with settings (#2559) 2025-11-27 08:06:43 +01:00
Huang Xin ecc20e8521 feat: apply progress style to footerbar progress (#2556) 2025-11-26 16:58:26 +01:00
Huang Xin d5c813fac1 fix(layout): avoid cascading refresh when cleaning up query parameters (#2555) 2025-11-26 15:52:29 +01:00
Huang Xin d9b3757b1c fix(layout): fix layout for import book item in grid fit mode (#2554) 2025-11-26 14:49:07 +01:00
Huang Xin fd7f90236d compat(ios): avoid lookbehind assertions for iOS older than 16.4 (#2553) 2025-11-26 11:31:31 +01:00
Huang Xin df9d21393d fix(layout): layout of the grid columns setting (#2552) 2025-11-26 10:19:29 +01:00
Huang Xin 1f73f15ad8 feat: add view option to set cover image size in grid mode, closes #2545 (#2551) 2025-11-26 07:49:51 +01:00
Huang Xin ba2aa4bee6 fix(layout): skip applying safe area insets when the iframe content is reloaded (#2550) 2025-11-26 04:10:52 +01:00
Huang Xin f709a657fa compat(css): sanitize insane absolute position, closes #2547 (#2549) 2025-11-25 17:19:29 +01:00
Huang Xin 514780a572 feat(shortcut): add shortcuts for annotation tools, closes #2270 (#2548) 2025-11-25 16:10:18 +01:00
Huang Xin 0c51a625f3 i18n: add translations for Bahasa Melayu(ms) (#2543) 2025-11-25 10:35:29 +01:00
Huang Xin 6f8b2d1dc4 fix(android): support back action for grouping modal (#2542) 2025-11-25 10:10:56 +01:00
Huang Xin 0087ce2f19 feat: add option to clear custom fonts in Font Panel (#2541) 2025-11-25 09:19:24 +01:00
Huang Xin b98c2796c8 feat: add option to apply page margins in scrolled mode, closes #2014 (#2540) 2025-11-25 08:55:33 +01:00
Huang Xin 37d56b3205 fix(android): support back key in menu, dialog and alert widgets, closes #2454 (#2539) 2025-11-25 08:01:17 +01:00
Huang Xin 5a54c0fb60 compat(css): override blockquote bg color in dark mode, closes #2281 (#2538) 2025-11-25 04:22:02 +01:00
StepanSad 99b259836b Update Ukrainian translations for consistency (#2535) 2025-11-25 09:28:12 +08:00
Huang Xin a54daaaa90 feat(shortcut): add ctrl + mouse wheel to zoom in/out, closes #2011 (#2533) 2025-11-24 17:19:28 +01:00
Huang Xin fa66e6fca6 feat(pdf): add support for hand tool panning for PDFs, closes #2518 (#2532) 2025-11-24 14:37:52 +01:00
Huang Xin b7864dded2 fix(eink): adjust sidebar and notebook background color for eink, closes #2497 (#2529) 2025-11-24 09:56:26 +01:00
Huang Xin 72d9698f38 fix: prevent file corruption using a robust backup strategy, closes #2512 (#2528) 2025-11-24 08:58:42 +01:00
Huang Xin a7937cd657 fix(translator): resolve occasional translation failures when navigating to new chapters (#2527)
Closes #2451.
2025-11-24 07:47:03 +01:00
Huang Xin 998b14c5b0 fix(layout): avoid clipping text because of negative indent, closes #2498 (#2526) 2025-11-24 06:11:29 +01:00
dependabot[bot] bff9c2a770 chore(deps): bump actions/checkout in the github-actions group (#2525)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 05:35:17 +01:00
Huang Xin b5acdffc87 fix(annotator): fixed layout shift when selecting text in paginated mode on Android (#2524) 2025-11-24 04:06:25 +01:00
Huang Xin 127609160c chore: add more test cases for simplecc (#2522) 2025-11-23 17:41:54 +01:00
Huang Xin 8f22a5c570 feat: select directory to save last book cover image (#2521) 2025-11-23 17:00:49 +01:00
Huang Xin c792c18e01 feat: support text conversion between simplifed and traditional Chinese, closes #2508 (#2520) 2025-11-23 09:56:12 +01:00
Huang Xin cd614e3845 feat(cjk): add an option to replace quotation marks in vertical layout for CJK languages (#2511) 2025-11-22 14:09:20 +01:00
Huang Xin 40673f9cb8 docs: update README and app metadata (#2509) 2025-11-22 07:08:07 +01:00
Huang Xin 911aa4c73d chore(flathub): verify the readest app on flathub (#2507) 2025-11-22 04:07:10 +01:00
Huang Xin 74b4cc2ceb chore(flathub): update oars content attribute (#2504) 2025-11-21 16:44:19 +01:00
Huang Xin c0c463977d fix: proper code indentation (#2503) 2025-11-21 16:24:45 +01:00
Huang Xin 273bcafe01 fix: resolve endless loading indicator (#2502) 2025-11-21 14:58:55 +01:00
Huang Xin 9028059919 chore: fix cf deploy scripts (#2501) 2025-11-21 13:03:56 +01:00
Huang Xin c86af457e7 chore: bump Next.js to 16.0.3 (#2496) 2025-11-21 12:49:36 +01:00
Huang Xin b8e979be55 fix(layout): fixed some rendering problems on Next 16 (#2495) 2025-11-21 08:23:42 +01:00
Huang Xin 8849c19e8e fix(flathub): fix releases notes (#2494) 2025-11-21 06:20:04 +01:00
Huang Xin f1b4d02323 chore: sync app metadata release notes (#2492) 2025-11-21 04:44:50 +01:00
Huang Xin 4aa8847037 chore: bump tauri to version 2.9.3 (#2488) 2025-11-20 14:18:27 +01:00
Huang Xin d2389400a9 fix(annotator): don't scroll page when annotator is shown in TTS mode, closes #2479 (#2487) 2025-11-20 13:24:58 +01:00
Huang Xin 721e70d027 release: version 0.9.93 (#2486) 2025-11-20 06:33:58 +01:00
Huang Xin 3c1a7eac86 fix(library): fixed occasional freeze when navigating back to the library (#2485)
Closes #2482.
2025-11-20 06:15:49 +01:00
Huang Xin f8e21dbd4c fix(sync): fixed sync issues affecting new accounts, closes #2481 (#2483) 2025-11-20 04:28:41 +01:00
Huang Xin d16a35d26f fix(layout): constrain image height within table cells in paginated mode (#2480)
Closes #2472.
2025-11-19 18:06:39 +01:00
Huang Xin f881caf794 chore(pwa): config workbox to skip precaching next.js internal files (#2478) 2025-11-19 14:38:04 +01:00
54wedge eb6fa276de fix(txt): register style.css to content.opf (#2476)
* fix(txt): register style.css to content.opf

* refactor: code styling

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-11-19 13:59:04 +01:00
Huang Xin e987c8b37a chore(pwa): get rid of public headers requests (#2477) 2025-11-19 13:31:21 +01:00
Huang Xin 26d76e27ac fix(txt): more tolerant encoding detection for utf-8 (#2475) 2025-11-19 07:47:10 +01:00
Huang Xin 975549fca0 fix(font): avoid overriding monospace fonts for code blocks, closes #1805 (#2474) 2025-11-19 06:57:45 +01:00
Huang Xin 9a6bed52dd fix(sync): propagate book delete status to other devices (#2473) 2025-11-19 05:47:59 +01:00
Huang Xin efd9ad12a9 release: version 0.9.92 (#2469) 2025-11-18 15:15:27 +01:00
Huang Xin 93a71a0fd0 fix(bookshelf): ensure 'select all' only targets books in the active group (#2468) 2025-11-18 14:34:32 +01:00
Huang Xin d373d7ac28 fix(txt): handle chapter title patterns (#2467) 2025-11-18 14:09:03 +01:00
Huang Xin e0e991b599 fix(kosync): proxy kosync request only for the web app, closes #2440 (#2466) 2025-11-18 13:44:30 +01:00
Huang Xin 6d5b16ea8b fix(iap): open external app for payment (#2465) 2025-11-18 12:07:43 +01:00
Huang Xin bfac67d3f0 fix: ger rid of the AbortSignal polyfill (#2463) 2025-11-18 09:10:53 +01:00
Huang Xin 0fd316d775 fix(sync): retry sync books for previous failed sync, closes #2441 (#2462) 2025-11-17 18:52:48 +01:00
Huang Xin c42fcf9ac4 fix: save reading progress when closing app directly in reader page, closes #2346 (#2461) 2025-11-17 16:35:56 +01:00
Huang Xin 04ade02a06 fix(layout): enlarge clickable area for the close button in the reader page (#2459) 2025-11-17 15:22:05 +01:00
Huang Xin 8ee53d3367 fix(mobi): properly handle empty fragments for MOBI (#2456) 2025-11-17 05:09:09 +01:00
Huang Xin 42111945b8 fix(layout): scale table to fit within column constraints, closes #2445 (#2455) 2025-11-17 04:52:23 +01:00
Huang Xin 6fde157047 feat(bookshelf): support nested groups in the bookshelf, closes #568 (#2449) 2025-11-16 13:59:37 +01:00
Huang Xin 65ec5c9842 fix(library): avoid invalid regular expression in library search (#2439) 2025-11-16 14:07:29 +08:00
Huang Xin a6444b5b33 feat: supported updating email address for Readest account (#2437) 2025-11-12 19:03:02 +01:00
Huang Xin c7238cb04c fix(css): overriding book color for more semantic tags, closes #2433 (#2436) 2025-11-11 19:44:13 +01:00
Huang Xin 366e5fafad fix: fixed multiple windows are opened when opening with readest, closes #2429 (#2435) 2025-11-11 19:36:28 +01:00
Huang Xin fb5710c134 fix(pdf): disabled pagination with swipe gesture below minimum velocity for PDFs, closes #2428 (#2434) 2025-11-11 18:20:45 +01:00
ByteFlow c4d9652335 fix(annotator): enhance PDF context menu for translation and improve touch handling (#2430)
* fix(annotator): enhance PDF context menu for translation and improve touch handling

* feat(annotator, shortcuts): add keyboard shortcuts for selection actions

* feat(annotator): reposition popups on scroll to enhance user experience

* feat(annotator): disable currently unsupported annotator functions for PDFs

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-11-11 15:10:44 +01:00
Huang Xin df6027cf9f fix(eink): avoid gray color in E-Ink mode (#2427) 2025-11-09 19:17:35 +01:00
Huang Xin e3f7abf7f1 fix(tts): fixed incorrectly selected voices, closes #2386 (#2426) 2025-11-09 19:11:12 +01:00
Huang Xin c539917d7e compat(footnote): support more footnote formats (#2425) 2025-11-09 18:07:39 +01:00
Huang Xin 5eb870c978 fix(layout): don't hide horizontal scrollbar, closes #2418 (#2424) 2025-11-09 17:03:24 +01:00
Huang Xin b075c46ba3 compat(sync): compatibility for kosync server implementations that do not return timestamp (#2422) 2025-11-09 14:42:07 +01:00
Huang Xin 4b6c7776f5 fix(eink): no shade in eink mode (#2421) 2025-11-09 14:34:26 +01:00
Huang Xin 745a14195d fix(layout): fixed header bar z index (#2413) 2025-11-06 10:56:28 +01:00
Huang Xin fb0f660eaf fix(android): fixed back gesture to close config panel, closes #2406 (#2411) 2025-11-05 22:45:13 +01:00
Huang Xin 04a3030b79 fix(metadata): fixed changing cover image in Readest apps, closes #2402 (#2410) 2025-11-05 20:43:42 +01:00
Huang Xin 5a3114de48 fix(android): get brightness of the current window on Android, closes #2389 (#2409) 2025-11-05 19:36:45 +01:00
Huang Xin b808fc5954 fix: default to not override book color for PDF, closes #2397 (#2404) 2025-11-04 18:46:37 +01:00
Huang Xin 0ec4e37c13 fix(css): support style transformer for inline css (#2403)
* fix(css): support style transformer for inline css

* fix(eink): bold text for current chapter in the TOC, closes #2401
2025-11-04 18:36:46 +01:00
Huang Xin 9d301631ca fix(layout): restrict image container within column boundaries, closes #2390 (#2398) 2025-11-03 16:35:08 +01:00
Huang Xin ec7145bb01 fix(upload): sanitize filenames before uploading to cloud bucket (#2396) 2025-11-03 11:17:24 +01:00
Huang Xin f79d84b5fa fix(eink): more readablility for E-Ink mode on the library page, closes #2364 (#2395)
This also closes #2391 and closes #2394.
2025-11-03 09:23:35 +01:00
Huang Xin d00f1e0def feat(import): import files directly into the current book group (#2393) 2025-11-03 05:47:26 +01:00
Huang Xin 637a813732 fix(settings): don't show system fonts on Android, closes #2381 (#2388) 2025-11-02 09:49:25 +01:00
Huang Xin 4f33c9280b fix(layout): fine tuning of column width in scrolled mode and vertical mode, closes #2383 (#2387) 2025-11-02 09:34:57 +01:00
Huang Xin d54c752637 fix(koplugin): properly refresh access token (#2384) 2025-11-02 04:50:54 +01:00
Huang Xin 984d5d198d fix(css): avoid overriding table background color by default, closes #2377 (#2379) 2025-11-01 15:09:46 +01:00
Huang Xin 2568778a87 feat(layout): support for duokan bleed layout, closes #2374 (#2378) 2025-11-01 14:26:48 +01:00
Huang Xin 0d805a64f6 compat: polyfill AbortController/AbortSignal on older browsers (#2371) 2025-10-31 06:15:59 +01:00
Huang Xin 066d1c5b1e fix(import): resolve import failures for certain EPUB files, closes #230 (#2370)
If the publisher has already been parsed, don’t remap it from the author or contributor fields again.
This prevents failures when parsing metadata like <dc:creator opf:role="pbl" />.
2025-10-31 05:12:24 +01:00
Huang Xin cdc1950c3a fix(layout): various fixes on the book and reader styles and layouts, closes #2368 (#2369) 2025-10-30 19:34:55 +01:00
Huang Xin 7142874513 fix(ui): display exact storage capacity in the user profile (#2366) 2025-10-30 11:50:39 +01:00
Huang Xin 409c2f62be release: version 0.9.91 (#2362) 2025-10-29 16:37:43 +01:00
Huang Xin 82922c5053 feat(eink): add an option to save last book cover on Android, closes #1414 (#2361) 2025-10-29 16:15:08 +01:00
Huang Xin 9c5bcb13e2 feat(plan): show life-time plan name in user profile (#2360) 2025-10-29 15:36:12 +01:00
Huang Xin 1d87dcdb7f feat: add options to customize the style of highlighted text of current sentence of TTS (#2358) 2025-10-29 10:17:36 +01:00
Huang Xin c7ba44a91e eink: disable all shadows in E-Ink mode (#2354) 2025-10-29 06:46:17 +01:00
Huang Xin 19b79c5b35 fix(android): fixed Android launcher icon size on some Android systems (#2353) 2025-10-29 06:19:02 +01:00
Huang Xin 45216763e3 fix(compat): use xml serializer for sanitized doc for better compatibility (#2352) 2025-10-29 05:12:19 +01:00
Huang Xin 123293dc0e fix: fixed crash when switching allow script (#2351) 2025-10-29 03:17:17 +01:00
Huang Xin 78e61060d2 fix(tts): fixed listener of tts events (#2349) 2025-10-28 18:36:57 +01:00
Huang Xin dd5371d2fd release: version 0.9.90 (#2344) 2025-10-28 05:44:07 +01:00
Huang Xin da041d1f38 fix(ui): hover to display footer bar in non-maximized windows on desktop (#2343) 2025-10-28 05:30:41 +01:00
Huang Xin d01599a2b4 feat(security): sanitize XHTML safely to prevent XSS when scripts are disabled, closes #2316 (#2342) 2025-10-28 04:28:48 +01:00
Huang Xin e80a2268ac feat(ui): add non-linear scale to brightness slider for better low-value control (#2338)
Implement logarithmic mapping for screen brightness slider to improve
usability at low brightness levels where small percentage changes have
significant visual impact.
2025-10-27 13:45:43 +01:00
Huang Xin 3b86229c8f perf(sync): batched updating notes and books when syncing (#2337) 2025-10-27 13:37:04 +01:00
Huang Xin c4d498d183 perf: much faster sync of the whole library (#2336) 2025-10-27 05:33:12 +01:00
Huang Xin 9bc46914e2 feat(iap): support expanding cloud storage with IAP on iOS and Android (#2331) 2025-10-26 10:18:56 +01:00
AlI 75f982e677 i18n(fa): refactor Farsi translation (#2330)
Restructure the Farsi translation to align with the format and key organization used in other translation files. This improves consistency and maintainability.
2025-10-26 08:58:57 +01:00
AlI f58bfeb1d0 fix(layout): Fix popup menu for RTL languages (#2327) 2025-10-26 08:51:25 +08:00
AlI ebb077f6e0 i18n: add translations for Persian/Farsi(fa), closes #2321 (#2326) 2025-10-25 15:32:24 +02:00
Huang Xin fdf6908fc6 feat: expand cloud storage with one-time payment (#2325) 2025-10-25 11:46:51 +02:00
Huang Xin 7936660868 docs: add sponsor links in README (#2318) 2025-10-24 15:22:08 +02:00
Huang Xin 554dd3798f fix: more explanatory error message when importing books, closes #2309 (#2314) 2025-10-24 07:15:41 +02:00
Huang Xin 7f40d8e12c fix(eink): more readability for progress info and section info in E-Ink mode, closes #2311 (#2313) 2025-10-24 06:14:25 +02:00
Huang Xin 31d6f44ff5 fix(iOS): restore last page if app is terminated in the background (#2312) 2025-10-24 05:42:52 +02:00
Huang Xin 86efcde927 feat(tts): add an option to select target language for TTS on translated books (#2310) 2025-10-23 19:34:43 +02:00
Huang Xin 4acbc33762 fonts: add PT font families (#2308) 2025-10-23 16:05:28 +02:00
Huang Xin 7cd3ebfcba compat: support .fb.zip files as FBZ format (#2307)
Although .fb.zip is not an officially recognized extension, many FBZ files use this format, so it's now treated as FBZ for compatibility.
2025-10-23 15:38:44 +02:00
Huang Xin 55c97cab7e perf: improve smoothness of Arrow Up/Down scrolling, closes #2080 (#2306) 2025-10-23 15:32:51 +02:00
Huang Xin 5bde091704 feat(settings): add an option to click both sides of the screen to paginate forward, closes #1951 (#2305) 2025-10-23 13:57:03 +02:00
Huang Xin 70489b46a5 fix(search): correctly display results when searching from selected text (#2304) 2025-10-23 11:52:49 +02:00
Huang Xin d7e89cc01f fix(layout): responsive layout for the custom highlight colors options (#2303) 2025-10-23 11:51:31 +02:00
Huang Xin a1f6030e75 fix(iOS): detect and recover from WebContent process termination (#2302) 2025-10-23 10:37:07 +02:00
Huang Xin e7b2d8435e feat(eink): set underline text decoration for links in E-Ink mode, closes #2293 (#2299) 2025-10-22 18:52:30 +02:00
Huang Xin f890a9633b feat(settings): add an option to set auto screen brightness (#2297) 2025-10-22 18:45:51 +02:00
Huang Xin 5bdcd3124b chore: bump tauri to version 2.9.0 (#2296) 2025-10-22 17:51:35 +02:00
Huang Xin 752b3f8e4c chore: bump nextjs, opennextjs and supabasejs (#2295) 2025-10-22 16:19:06 +02:00
Huang Xin 001e836ae3 chore: bump eslint-config-next to version 16 (#2294) 2025-10-22 15:57:04 +02:00
Huang Xin 50d7f9a9dd feat(android): support custom data location on external sdcard (#2292) 2025-10-22 10:14:41 +02:00
Huang Xin 34a5e58872 feat: add keyboard shortcuts to go prev/next sections, closes #2287 (#2291) 2025-10-22 07:41:33 +02:00
Huang Xin 34fbea6a44 css: properly handle theme color in dark mode for code blocks, closes #2281 (#2290) 2025-10-22 06:56:53 +02:00
Huang Xin 8737535b90 feat: support Android IAP (#2286) 2025-10-21 18:24:50 +02:00
Huang Xin 28ef414c3d feat: supported footnotes for definition list, closes #2278 (#2279) 2025-10-20 10:38:46 +02:00
dependabot[bot] 16279949c1 chore(deps): bump actions/setup-node in the github-actions group (#2277) 2025-10-20 12:25:42 +08:00
Huang Xin 444e22dd0e chore: i18n for highlight color settings (#2275) 2025-10-19 18:59:26 +02:00
仿生猫梦见苦力怕 a231506713 fix(css): support at rules of CSS and fix CSS formatter (#2269)
* refactor: replace manual CSS validator with PostCSS

* Revert "refactor: replace manual CSS validator with PostCSS"

This reverts commit 609ed7dd5a50debcea0303cc1043440af9849ddc.

* fix: support at rules of CSS and fix CSS formatter

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-10-19 18:09:04 +02:00
airwish 5dcabba73c chore: add contribution settings (#2274) 2025-10-19 17:14:20 +02:00
AlI cd71c494da feat(settings): add custom highlight color picker (#2273)
Add ability to customize highlight colors with hex color picker. Users can now set custom colors for all five highlight styles (red, violet, blue, green, yellow) in the settings panel.

Fixes #2271
2025-10-19 17:03:59 +02:00
Huang Xin f66642b8ec tts: avoid using AnaNeural as default English voice (#2272) 2025-10-19 14:58:51 +02:00
Huang Xin 2283e1b0f2 fix(layout): fix inline image, closes #2263 (#2266) 2025-10-18 17:25:37 +02:00
Daniil Leontev e0eb725d8b fix: add more elements to color overrides (#2265) 2025-10-18 22:25:02 +08:00
Huang Xin 05e9549410 release: version 0.9.88 (#2258) 2025-10-17 18:06:00 +02:00
Huang Xin 35fbce3506 feat(settings): added an option to disable animation in E-Ink mode, closes #2253 (#2257) 2025-10-17 18:00:58 +02:00
Huang Xin c1d6825793 fix: more sensitive snap to paginate, closes #2252 (#2256) 2025-10-17 16:05:39 +02:00
Huang Xin ae6c6970b4 feat: localize number in progress info in vertical layout (#2255) 2025-10-17 12:17:36 +02:00
Huang Xin f8fef57cf3 fix(layout): overriding book layout no longer overrides explicit text alignment, closes #2249 (#2254) 2025-10-17 11:44:32 +02:00
Huang Xin 4dc191735d fix(layout): prevent TTS indicator from disappearing too quickly to open configuration panel, closes #2247 (#2248) 2025-10-16 16:49:05 +02:00
Huang Xin 4f11f437e1 release: version 0.9.87 (#2245) 2025-10-16 10:27:34 +02:00
Huang Xin 5773e31990 compat(tts): fix crash with TTS on some versions of Android systems (#2244) 2025-10-16 10:10:04 +02:00
Huang Xin 9dda5d4c88 fix(tts): scroll more accurately to the highlighted text in TTS when header/footer bars are shown (#2242) 2025-10-15 17:50:56 +02:00
Huang Xin 7f5b6959e1 font: update font LXGW WenKai to the latest version (#2240) 2025-10-15 15:17:53 +02:00
Huang Xin 126981d085 fix(theme): fixed auto theme mode for new reader window (#2238) 2025-10-15 14:36:02 +02:00
Huang Xin 854a578929 compat: support parsing more footnotes, closes #2234 (#2237) 2025-10-15 11:59:27 +02:00
Huang Xin 6dd0daa597 txt: add chapter title pattern, closes #2232 (#2233) 2025-10-14 18:01:23 +02:00
Huang Xin fd24a5e9ac release: version 0.9.86 (#2230) 2025-10-14 14:08:45 +02:00
Huang Xin 948b35f244 perf: support concurrent book uploading, closes #2208 (#2229) 2025-10-14 14:00:31 +02:00
Huang Xin 96cc182a8c feat: support overriding html language code with language code in metadata, closes #2222 (#2227) 2025-10-14 13:09:23 +02:00
Huang Xin e2291ed5b1 feat(koplugin): register sync actions in gesture manager closes #2224 (#2226) 2025-10-14 09:54:08 +02:00
Huang Xin 60ddb17547 feat: add support for per-book background image settings in addition to global settings, also closes #2223 (#2225) 2025-10-14 08:54:26 +02:00
Huang Xin 9a991a31ba fix: handle empty custom textures (#2221) 2025-10-13 20:08:22 +02:00
Huang Xin 6de10c6c3b release: version 0.9.85 (#2220) 2025-10-13 19:15:55 +02:00
Huang Xin c7d49c1504 fix: load custom texture before applying it (#2219) 2025-10-13 19:04:45 +02:00
Huang Xin c721bd4d97 chore: enable turbopack by default in development env (#2218) 2025-10-13 16:39:17 +02:00
Huang Xin 5b0a1968d8 chore: add readest.koplugin in the release workflow (#2217) 2025-10-13 16:26:11 +02:00
Huang Xin ad9064fa46 fix(toc): fixed issues of scroll to active items in unpinned sidebar, closes #2213 (#2216) 2025-10-13 15:29:51 +02:00
Huang Xin f1c92e5702 feat: support customizing background images, closes #2018 (#2214) 2025-10-13 14:04:13 +02:00
Huang Xin 92c1441922 translator: add translator target language of Persian, closes #2206 (#2212) 2025-10-12 13:53:23 +02:00
Huang Xin 3820950c7c fix: show only the filtered books in groups (#2209) 2025-10-12 11:38:47 +02:00
Huang Xin 08e558bd41 fix: actually allow script in the transform target, closes #2200 (#2203) 2025-10-11 12:59:55 +02:00
Huang Xin 00af4a3d60 layout: make TTS icon less distracting during read-aloud mode (#2198) 2025-10-10 19:14:57 +02:00
Huang Xin ada427b134 feat(settings): add settings to adjust screen brightness when reading, closes #1532 (#2197) 2025-10-10 19:02:35 +02:00
Huang Xin f696b9a573 fix: handle scrolling/panning in zoomed in PDFs, closes #2184 (#2194) 2025-10-10 07:56:41 +02:00
Huang Xin 195beeacac chore: don't init posthog if users opt-out the usage data telemetry, closes #2187 (#2192) 2025-10-10 07:03:49 +02:00
Huang Xin 5b23ed35b6 txt: properly detect chapters in TXT files with dot-number format, closes #2188 (#2191) 2025-10-10 06:45:50 +02:00
Huang Xin 42c8702704 fix(cbz): fix issue where CBZ files fail to reopen after being closed, closes #2183 (#2190) 2025-10-10 05:50:40 +02:00
Huang Xin ad81376da8 fix(css): auto height of inline images with object-fit style (#2189) 2025-10-10 04:10:05 +02:00
Huang Xin 562fea3a36 fix(layout): fix inconsistent background color for unpinned drag bars (#2186) 2025-10-09 18:35:00 +02:00
Huang Xin b69d9ed69f tts: improve media session control compatibility across more Android systems (#2185) 2025-10-09 17:53:07 +02:00
Huang Xin ad6a21a68f refactor: split FooterBar into modular components for desktop and mobile (#2181) 2025-10-08 07:47:39 +02:00
Huang Xin 173aa0fcd1 layout: fix dragbar flickering when opening a book (#2177) 2025-10-06 05:52:36 +02:00
Huang Xin 788e08a75a shortcut: use shift+space to go backward in the reader page (#2176) 2025-10-06 05:31:16 +02:00
Huang Xin ec397a2daf fix: draw annotations of the current section on load, closes #2163 (#2174) 2025-10-05 17:47:34 +02:00
Huang Xin 2a5758fbc4 compat: disable customize root dir for macOS App Store builds (#2171)
CustomizeRootDir has a blocker on macOS App Store builds due to Security Scoped Resource restrictions.
See: https://github.com/tauri-apps/tauri/issues/3716
2025-10-05 08:28:45 +02:00
Huang Xin 562ccd4b9e feat: add keyboard shortcuts for toggling bookmarks/sidebar, importing books, closing window (#2170)
And make the shortcuts compatible with GNOME Human Interface Guidelines.
This closes #2079 and closes #2168.
2025-10-04 16:12:31 +02:00
make_aguess 4d577774f3 Adds adaptive icon for Android launcher (#2162)
Introduces an adaptive icon for the Android launcher, defining
separate layers for background, foreground, and monochrome
elements. Enhances visual consistency and supports adaptive
icon features on modern Android devices.
2025-10-04 09:00:33 +02:00
Huang Xin 4c10c16491 i18n: add translations for Swedish(sv) (#2159) 2025-10-02 14:25:01 +02:00
Huang Xin 7695f31a5a translator: translate to and from Norwegian Bokmål(nb) (#2158) 2025-10-02 14:09:01 +02:00
Huang Xin 06b27f8d39 chore: fix portable exe build for Windows (#2156) 2025-10-02 00:11:21 +08:00
Huang Xin e1691661d9 release: version 0.9.82 (#2155) 2025-10-01 16:45:01 +02:00
Huang Xin bc8e419d51 compat(koplugin): make meta_hash extraction more robust for calibre conversions and metadata edits, fixes #1838 (#2154) 2025-10-01 16:38:29 +02:00
make_aguess ccad1ca0d9 Add support for mono theme (#2122) (#2153) 2025-10-01 15:30:09 +02:00
Huang Xin 75c315fca8 fix(koplugin): only prompt network connection during interactive push progress, closes #2137 (#2152) 2025-10-01 14:57:58 +02:00
Huang Xin bdef593cdd feat: support global settings from library menu, closes #2140 (#2151) 2025-10-01 09:59:37 +02:00
Huang Xin d8943fc0c5 compat: normalize OKLCH color syntax for older iOS Safari, closes #990 (#2150) 2025-10-01 07:59:45 +02:00
Huang Xin cdd274e5ca compat: add support for WebView down to version 92, closes #2139 (#2145) 2025-09-30 19:14:35 +02:00
Huang Xin 2230741779 fix(i18n): apply system language on app start, closes #2141 (#2144) 2025-09-30 10:11:18 +02:00
Huang Xin 0e6950a60f i18n: localization for notification title and text (#2143) 2025-09-30 10:03:33 +02:00
Huang Xin 1d4541e353 feat: request manage external storage permission when changing data directory to sdcard root on Android (#2142) 2025-09-30 08:59:16 +02:00
Huang Xin 0a1e0212e2 feat: supported background TTS with media session controls, closes #2099 and closes #964 (#2138) 2025-09-29 19:45:17 +02:00
Huang Xin 25b44176b9 fix(tts): parse default language in ssml without translated text (#2135) 2025-09-27 17:52:54 +02:00
Huang Xin e1deff341d feat: supported changing data location on Android (#2131) 2025-09-26 17:32:16 +02:00
Huang Xin 3fc4c05e50 feat: build portable Windows binaries with app data kept within the executable directory (#2126) 2025-09-26 11:23:55 +02:00
Huang Xin 1843a74dbb feat: supported changing data location on desktop platforms, closes #2047 and closes #478 (#2125) 2025-09-26 10:22:41 +02:00
Huang Xin 9aa6a8e6b9 chore: bump tauri to the latest version (#2117) 2025-09-25 09:32:55 +02:00
Huang Xin 1de89069ce fix: handle pointer events for window dragging on touch screen Windows, closes #2010 (#2116)
* fix: handle pointer events for window dragging on touch screen Windows, closes #2010

* chore: fix rust-lint
2025-09-25 06:41:08 +02:00
Huang Xin 1e6525d085 fix(sync): handle incomplete config data from koreader (#2115) 2025-09-25 11:49:43 +08:00
Huang Xin d9d71b7d38 fix(css): overriding hard-coded font weight (#2113) 2025-09-24 17:03:17 +02:00
Huang Xin e85dec2b49 fix(sync): force a full sync after a certain period, closes #2111 (#2112) 2025-09-24 16:52:17 +02:00
Huang Xin c0f77b3368 fix(tts): abortable prefetch of tts data, closes #2037 (#2110) 2025-09-24 12:19:07 +02:00
Huang Xin 8c9855d167 fix(layout): avoid changing the safe area insets when virtual keyboard pops up on Android, closes #2105 (#2108) 2025-09-24 07:28:59 +02:00
Huang Xin 7c12bc8595 fix(mobi): fixed footnotes parsing for some MOBI/AZW files (#2106) 2025-09-23 08:53:43 +02:00
Huang Xin 5102f6b0ff fix(tts): handle und language code, closes #2100 (#2102) 2025-09-22 18:15:28 +02:00
Huang Xin dd40b9bae0 fix(layout): adjust font size instead of zooming HTML, closes #2088 (#2101)
Prevents issues with zoom handling in WebKit-based browsers such as Safari and WebKitGTK.
2025-09-22 17:43:22 +02:00
Huang Xin 98b4026990 fix(layout): scrollable book view and about window, closes #2090 (#2098) 2025-09-22 13:44:54 +02:00
Huang Xin b0640ba55b fix(fonts): higher priority for CJK fonts so that it can override system CJK glyphs (#2093) 2025-09-21 08:22:49 +02:00
Huang Xin c9a1557674 refactor: support custom root dir for readest file system (#2092) 2025-09-21 07:59:27 +02:00
Huang Xin 855f98722c tts: add more languages for edge tts (#2089) 2025-09-20 01:58:11 +02:00
Huang Xin 21a82b70c7 fix(a11y): better contrast for disabled buttons, closes #2083 (#2084) 2025-09-19 13:40:45 +02:00
Huang Xin 519e222883 release(hotfix): version 0.9.81 (#2082) 2025-09-19 13:20:48 +02:00
Huang Xin 488a242787 css: default to lower font-family specificity than the styles defined inside the ebook, closes #2070 (#2081) 2025-09-19 13:10:37 +02:00
StepanSad 523a03d212 Update translation.json (#2078) 2025-09-19 12:04:22 +02:00
Huang Xin d49f3442e8 fix(layout): scroll in the dropdown menu in landscape mode, closes #2072 (#2076) 2025-09-19 11:50:03 +02:00
Huang Xin 48dc570809 fix: handle invalid bookkey for viewState, closes #2073 (#2074) 2025-09-19 11:09:32 +02:00
Huang Xin 509320813f tts: support media session in desktop apps (#2071) 2025-09-19 10:22:39 +02:00
Huang Xin 54d7e73acb fix: disable swipe up to toggle action bar for PDF zoomed in (#2068) 2025-09-18 19:46:47 +02:00
Huang Xin 1bc0eb3a4b release: version 0.9.80 (#2067) 2025-09-18 18:45:12 +02:00
Huang Xin c0bf2d40dd fix: jump to current item in virtualized TOC list, closes #2055 (#2066) 2025-09-18 18:38:09 +02:00
Huang Xin e30a39f9cb fix(config): prevent occasional errors when saving progress and configs (#2065) 2025-09-18 16:56:44 +02:00
Huang Xin 9727e4e9b8 fix(metadata): handle invalid language code and title when importing, closes #2056 and closes #2058 (#2064) 2025-09-18 16:47:02 +02:00
Huang Xin 3380912cdb feat(kosync): use metadata hash to aggregate different versions of the same book (#2063) 2025-09-18 14:51:26 +02:00
Huang Xin 91a5454b46 feat(sync): aggregate books with metadata hash for progress and annotation sync (#2062) 2025-09-18 14:05:16 +02:00
Huang Xin e0c5acf4c8 chore: check file status before deleting cloud files (#2053) 2025-09-17 04:54:32 +02:00
Huang Xin 7e062be0d0 doc: update accessibility support in README (#2051) 2025-09-16 18:06:10 +02:00
Huang Xin b23876c2b9 a11y: support NVDA screen reader on Windows and Orca screen reader on Linux (#2050) 2025-09-16 17:38:21 +02:00
Huang Xin e9a152683e fix(layout): show focus ring only for keyboard navigation (#2046) 2025-09-15 21:13:52 +02:00
Huang Xin 34f1af727f a11y: accessibility for mobile platforms (#2044) 2025-09-15 18:45:45 +02:00
Huang Xin dfc24f3803 fix(layout): fix break word for titles in book detail, closes #2039 (#2043) 2025-09-15 09:21:35 +02:00
Huang Xin ec233f7b37 fix(sync): resolve issue where invalid tokens were occasionally used with the storage API (#2041) 2025-09-14 22:08:22 +02:00
Huang Xin bb34b1ba59 a11y: add screen reader support (#2040)
Tested with VoiceOver on iOS and macOS
2025-09-14 22:00:42 +02:00
Daniil Leontev b1fb477359 fb2: respect elements nested in stanza (#2038)
Updates foliate-js submodule to include necessary changes. Fixes #2027
2025-09-14 17:03:53 +02:00
StepanSad 3c877a19fc Update translation.json (#2032) 2025-09-14 08:49:02 +08:00
Huang Xin b47baa6b74 fix: add delete confirmation for book deletion from contextmenu, closes #2013 (#2029) 2025-09-12 18:27:07 +02:00
Huang Xin 14c85cbadf feat(metadata): add an option to remove cover image, closes #2020 (#2028) 2025-09-12 17:10:25 +02:00
Huang Xin a88b9139f8 fix: occasionally failed double click detection on the header bar on Windows, closes #2015 (#2026) 2025-09-12 13:36:56 +02:00
Huang Xin 251a4b52c0 fix: support dragging window on touch screen, closes #2010 (#2025) 2025-09-12 11:43:49 +02:00
Huang Xin 1aaeaf5ebe feat: add support links in the about window (#2024) 2025-09-12 08:03:35 +02:00
Huang Xin 9fd152d727 refactor(a11y): add basic lint for accessibility (#2021) 2025-09-11 18:57:04 +02:00
Huang Xin 2571ceede4 fix: also apply zoom shortcuts to PDFs (#2016) 2025-09-10 17:32:23 +02:00
Huang Xin 932cfa3b9f feat: show groups in list mode in the library page, closes #978 (#2009) 2025-09-09 18:18:54 +02:00
Huang Xin 01fde8573c fonts: support variable fonts when importing ttf/otf font files, closes #2003 (#2007) 2025-09-09 11:25:27 +02:00
Huang Xin d95656b37b fix(tts): compensate audio fade-in for resumed play on iOS and macOS, closes #2005 (#2006) 2025-09-09 08:29:39 +02:00
Huang Xin 210eda0340 fix: pagination with volume keys now has right direction for RTL books, closes #1996 (#2004) 2025-09-09 05:36:45 +02:00
Huang Xin 0d8b877d73 fix: don't show rounded window in maximized or fullscreen mode on (#2001)
Linux, closes #1998
2025-09-08 19:32:15 +02:00
dependabot[bot] 79d39b2295 chore(deps): bump the github-actions group with 2 updates (#1999) 2025-09-08 12:44:45 +08:00
Huang Xin e03b0af58a feat: swipe to paginate on fixed layout books, closes #1028 (#1995) 2025-09-07 16:14:34 +02:00
Huang Xin 71c2143cba fix: avoid pagination until book view is inited, closes #1983 (#1994) 2025-09-07 14:47:35 +02:00
Huang Xin 70a7f5be19 fix: use transparent background for rounded window on Linux, closes #1982 (#1991) 2025-09-07 10:38:27 +02:00
Huang Xin d2242423f5 fix: correctly parse filenames for woff/woff2 font files on Android, closes #1981 (#1990) 2025-09-07 07:04:48 +02:00
Huang Xin 782e9c8b20 fix: restore scale factor (zoom level) for PDFs, closes #1985 (#1989) 2025-09-07 06:12:57 +02:00
Huang Xin 55b2c678a4 fix(kosync): handle non-standard XPath syntax from Koreader, closes #1968 (#1988) 2025-09-07 06:05:27 +02:00
Huang Xin 48d754c184 fix(layout): prevent layout shift on Android browsers, closes #1979 (#1980) 2025-09-06 18:13:02 +02:00
Huang Xin ff0443ace7 compat: fixed zoom level handling on iOS and macOS, closes #1784 (#1978) 2025-09-06 16:07:06 +02:00
Huang Xin cc3cc58de0 release: version 0.9.78 (#1975) 2025-09-06 10:50:18 +02:00
Huang Xin 80d60266c2 settings: default to override background color in fixed layout documents (#1974) 2025-09-06 10:40:18 +02:00
54wedge f06cdb5899 txt: nest chapters inside volumes when converting to epub (#1972)
* txt: nest chapters inside volumes

* txt: readability

* txt: format
2025-09-06 10:28:26 +02:00
Huang Xin dfe68988aa fix(kosync): use 1-based indices in xpointer, closes #1968 (#1973) 2025-09-06 07:40:32 +02:00
Huang Xin d0264de3eb compat: more accessible iframes for PDFs, closes #1136 (#1971) 2025-09-05 18:56:15 +02:00
Huang Xin 77c4303938 feat(pdf): support setting zoom level, zoom mode and spread mode for PDF (#1969)
This should close #1717, close #1276, close #669.
2025-09-05 18:20:15 +02:00
Huang Xin 5724d2eeec feat(tts): support going previous/next by sentence in bottom TTS bar, closes #1816 (#1967) 2025-09-05 10:34:30 +02:00
54wedge 64007b1188 txt: improve zh chapter extraction (#1966)
* txt: improve zh chapter extraction

* txt: fix regexp

* txt: one more keyword
2025-09-05 10:31:26 +02:00
Huang Xin 50ee7bd8be doc: change badge colors for last commit and commit activity 2025-09-04 11:44:11 +02:00
Huang Xin 2bcbde4bf1 doc: fixed badge-language-coverage URL encoding 2025-09-04 11:39:27 +02:00
Huang Xin 99f75dd3b6 i18n: add Bengali(BN), Sinhala (SI) and Tamil (TA) translations (#1965) 2025-09-04 11:37:29 +02:00
Huang Xin 2b9132b4a8 feat(translator): preprocess some common mistranslated publishing terms (#1964) 2025-09-04 09:38:17 +02:00
Huang Xin 82782be16e fix: prevent layout shift when virtual keyboard pops up on Android, closes #1959 (#1963) 2025-09-04 08:04:51 +02:00
Huang Xin 7067129887 fix(tts): fixed headings cannot be highlighted in TTS (#1955) 2025-09-03 16:42:59 +02:00
Huang Xin 6e706ac8f7 feat: add inline editor for bookmarks in the sidebar, closes #1909 (#1954) 2025-09-03 11:06:35 +02:00
Huang Xin 39b902c927 epub: parse correct book front cover for some EPUBs, closes #1913 (#1953) 2025-09-03 07:50:26 +02:00
Huang Xin 94a9cd7c9d doc: update README and add link of readest subreddit (#1952) 2025-09-03 07:24:54 +02:00
Huang Xin 0dae7db140 fix: improved sensitivity of text selection with stylus, closes #706 (#1950) 2025-09-02 18:51:45 +02:00
Huang Xin 32954e025c fix(tts): media control in the lock screen and with airpods for iOS, closes #1407 (#1949) 2025-09-02 17:24:16 +02:00
Huang Xin 61dda9a517 feat: group custom fonts into families, closes #1934 (#1945) 2025-09-01 17:49:46 +02:00
Huang Xin 99a4cb50ac feat: add an option to show persistent TTS bar at the bottom, closes #1818 (#1944) 2025-09-01 14:23:28 +02:00
Huang Xin f4b00d6dec fix: get rid of invalid grid insets (#1943) 2025-09-01 07:05:48 +02:00
Huang Xin f69422a61c fix: conditional Back key interception on Android (#1942) 2025-09-01 06:16:31 +02:00
Huang Xin 7f4c157a90 chore: build with install package permission by default for Android (#1939) 2025-08-31 16:53:29 +02:00
Huang Xin 7d9724ff8b fix: close books with the system back key on Android, closes #1933 (#1938) 2025-08-31 10:15:28 +02:00
Huang Xin 06db7f36f8 refactor: rename ConfirmSyncDialog to KOSyncResolver (#1937) 2025-08-31 05:46:43 +02:00
Huang Xin f0c249bce2 release: version 0.9.76 (#1932) 2025-08-30 06:19:01 +02:00
Huang Xin e8a96fba95 compat: more compat with window insets on Android (#1931) 2025-08-30 06:18:06 +02:00
Huang Xin 92b01c40a0 layout: fix grouping modal not shown on Linux (#1930) 2025-08-30 05:28:02 +02:00
Huang Xin 6003eeadba compat: support more kosync server implementations, closes #1862 (#1929) 2025-08-30 04:54:31 +02:00
Huang Xin 856ed4d3b3 ux: add splash screen for iOS and Android and reduce flash when opening books (#1926) 2025-08-29 19:10:09 +02:00
Huang Xin 267fe58a8c fix: reduce screen flash with different background colors when app starts, closes #1915 (#1922) 2025-08-28 20:02:53 +02:00
Huang Xin 1b00d8c41c fix: support importing azw3 files on Android, closes #1920 (#1921) 2025-08-28 16:11:30 +02:00
Huang Xin cb126613b1 perf(sync): incremental sync of library books (#1917) 2025-08-27 19:04:57 +02:00
Huang Xin 515bfee2a1 fix(kosync): normalize xpointer to improve KOReader sync tolerance, closes #1857 (#1914) 2025-08-27 09:18:32 +02:00
Huang Xin 584f3511f8 refactor(kosync): move kosync into reader sidebar menu and add manual pull/push trigger (#1912) 2025-08-27 13:32:00 +08:00
Huang Xin 3ea54e6725 fix: display hidden footnotes, closes #1847 (#1907) 2025-08-26 12:45:12 +02:00
Huang Xin 48212d892f fonts: purge custom fonts when reseting fonts config (#1906) 2025-08-26 11:23:34 +02:00
Huang Xin 977c61b914 refactor: use useFileSelector hook for all file selection (#1905) 2025-08-26 10:04:10 +02:00
Huang Xin 66fceb2313 chore: add 10MB of grace bytes for storage quota (#1904) 2025-08-26 07:28:15 +02:00
Huang Xin 76feefff00 layout: fix overflow of some font names in custom fonts, closes #1901 (#1903) 2025-08-26 06:35:43 +02:00
Huang Xin 1be43ff704 fix: close reader window when trying to go to library in separate reader window, closes #1899 (#1900) 2025-08-25 18:01:06 +02:00
Huang Xin b6d4a547e5 chore: fix typo (#1898) 2025-08-25 09:41:36 +02:00
Huang Xin 5ee860f1e8 refactor: use safe insets from native for Android, closes #1793 and closes #1886 (#1897) 2025-08-25 09:38:49 +02:00
dependabot[bot] 0b01132d88 chore(deps): bump actions/setup-java in the github-actions group (#1896)
Bumps the github-actions group with 1 update: [actions/setup-java](https://github.com/actions/setup-java).


Updates `actions/setup-java` from 4 to 5
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-25 09:13:58 +02:00
Huang Xin b98c487919 theme: fix themed background color for PDFs on macOS, closes #1887 (#1894) 2025-08-24 08:29:45 +02:00
Huang Xin b43b08d423 fix: init traffic light properly in library page on macOS, closes #1872 (#1891) 2025-08-24 06:33:52 +02:00
Huang Xin 3ddb6e6422 fix: handle some cases where footnote is empty, closes #1880 (#1883) 2025-08-23 10:28:51 +02:00
Huang Xin ea9146be5b fonts: parse font family as well as font style when importing fonts, closes #1876 (#1881) 2025-08-23 10:27:58 +02:00
Huang Xin 78cb1f45e4 chore: fix workflow dependency for release (#1874) 2025-08-22 17:51:01 +02:00
Huang Xin e472d1cf1e release: version 0.9.75 (#1873) 2025-08-22 17:47:24 +02:00
shaqtiktok 75dc04d598 fix(tts): reuse audio object in the same paragraph for better performance, closes #1777 (#1853)
* Remove stopInternal which causes audio delaying

* Change audio obj to not be recreated every sentence

* refactor abort signal handler

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-22 17:22:34 +02:00
Huang Xin fc4c033cf3 layout: layout tweaks on the custom fonts panel (#1871) 2025-08-22 12:35:20 +02:00
Huang Xin dbee054838 feat: apply custom fonts directly in custom font panel (#1870) 2025-08-22 12:17:57 +02:00
Huang Xin 45b805dcd4 layout: keep book title align center of the blank space when there are window buttons, closes #1867 (#1869) 2025-08-22 10:42:34 +02:00
Huang Xin a8ac54b51c layout: fix position of close button miss-match between reader and library page on Windows and Linux, closes #1866 (#1868) 2025-08-22 10:05:00 +02:00
German Shein 86f705eae9 feat: support author sorting (last name first), closes #1799 (#1865)
* [Issue #1799](https://github.com/readest/readest/issues/1799): Changing the name order when sorting the book by Author by placing the last name first in Arabic, Tibetan, German, English, Spanish, French, Hindi, Italian, Dutch, Polish, Portuguese, Russian, Thai, Turkish and Ukrainian languages

* refactor author sort

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-22 07:59:59 +02:00
Huang Xin d390209dab feat: support importing TTF/ODF fonts, closes #979 and closes #1708 (#1864) 2025-08-21 21:49:08 +02:00
Huang Xin 149f94be4c css: ensure workarounds don’t interfere with body background color (#1859) 2025-08-20 18:30:20 +02:00
Huang Xin ef1a1eab98 layout: consistent label font weight for settings panels (#1856) 2025-08-20 12:14:45 +02:00
lijiahao66666 dc7c6e14bc fix(translator): refresh translation when provider is changed in translator popup (#1855)
– Add the translate function to the dependency array in the TranslatorPopup component
– This change ensures that the translation content is re-fetched whenever the translate function is updated

Co-authored-by: 李家豪 <7904127+li_jiahaojj@user.noreply.gitee.com>
2025-08-20 11:54:20 +02:00
Huang Xin 8c31dc85f9 chore: bump next.js, opennext, wrangler and supabase to latest versions (#1851) 2025-08-20 05:57:28 +02:00
Huang Xin 7b8d1ddc35 chore: bump tauri to latest dev branch (#1849) 2025-08-20 05:15:44 +02:00
Huang Xin 8c12399732 layout: fix footer bar layout in landscape mode on Android, closes #1803 (#1843) 2025-08-19 15:49:38 +02:00
Huang Xin 93a4b1a448 css: fix unexpected align center for some EPUBs (#1842) 2025-08-19 15:05:26 +02:00
Huang Xin 04b0752b22 layout: fix drop down in TTS panel cannot open on older iOS, closes #1810 (#1841) 2025-08-19 15:02:02 +02:00
Huang Xin 5137c08204 chore: more aggressive cache for CI (#1839) 2025-08-19 10:21:20 +02:00
Huang Xin ec0b44fb9f reader: fix collapse of nested TOC for some EPUBs, closes #1820 and closes #1813 (#1837) 2025-08-19 09:55:25 +02:00
Huang Xin 0bf83dfe67 css: use monospace font for code blocks when overriding custom book fonts, closes #1805 (#1836) 2025-08-19 07:56:10 +02:00
Huang Xin b151b70cb9 fix: don't proxy network connection from Tailscale IP addresses, closes #1834 (#1835) 2025-08-19 05:02:29 +02:00
Huang Xin f2640359a0 release: version 0.9.72 (#1831) 2025-08-18 17:43:09 +02:00
Huang Xin 079aeaedb0 fix(sync): support custom koreader sync server hosted in LAN, closes #1800 (#1830) 2025-08-18 17:35:49 +02:00
Huang Xin bfacadb964 layout: fix options list align right on Windows (#1829) 2025-08-18 16:10:06 +02:00
Huang Xin 2e98ed44ee tts: fix edge tts, closes #1821 (#1828) 2025-08-18 14:44:38 +02:00
dependabot[bot] 952d2a3553 chore(deps): bump actions/checkout in the github-actions group (#1827)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-18 13:36:01 +02:00
StepanSad 225aa0ca59 Update translation.json (#1826) 2025-08-17 20:15:14 +08:00
Huang Xin 37f718b84b fix: adjust snap threshold to avoid pagination changes when toggling toolbars, closes #1807 (#1811) 2025-08-14 17:00:52 +02:00
Huang Xin db8f53a6e1 css: background color for dialogs and images, closes #1797 (#1798) 2025-08-13 15:37:52 +02:00
Huang Xin ce345ad67f fix(koplugin): add expected 301 status code (#1791) 2025-08-12 21:33:51 +02:00
Huang Xin 8e53e625a4 fix(koplugin): fix typo of saveSetting, closes #1788 (#1789) 2025-08-12 20:20:43 +02:00
Huang Xin 3350bdbdd3 release: version 0.9.71 (#1787) 2025-08-12 18:59:04 +02:00
Huang Xin 634d789654 css: fix initial text align (#1786) 2025-08-12 18:37:00 +02:00
Huang Xin 0dfa8e96f5 feat(sync): add readest koplugin for progress sync, also implements #1729 (#1785) 2025-08-12 18:24:47 +02:00
Huang Xin 3698e6ca28 sync: refactor koreader sync settings (#1783)
* sync: refactor koreader sync settings

* i18n: added Tibetan translations, closes #1780
2025-08-11 19:41:39 +02:00
Zeedif 595608bd62 feat: Implement KOReader Progress Synchronization (#1770)
* feat(sync): implement KOReader progress synchronization

This commit introduces a comprehensive feature to synchronize reading progress with a KOReader sync server. It includes a compatibility layer to handle discrepancies between Readest's CFI-based progress and KOReader's XPointer/page-based progress, primarily using the `percentage` field as a common ground.

Key additions include:
- A new settings panel under "KOReader Sync" for server configuration, authentication, and sync strategy management (e.g., prompt on conflict, always use latest).
- A conflict resolution dialog that appears when remote progress differs significantly from local progress, allowing the user to choose which version to keep.
- A client-side `useKOSync` hook to manage the entire synchronization lifecycle, including API calls, state management, and conflict resolution logic.
- A new API endpoint `/api/kosync` that acts as a secure proxy to the user-configured KOReader sync server, handling authentication and forwarding requests.
- Logic to differentiate between paginated (PDF/CBZ) and reflowable (EPUB) formats, using page numbers for paginated files where possible and falling back to percentage for reliability.
- Spanish translations for all UI elements related to the KOReader sync feature.
- Addition of `uuid` package to generate a unique `device_id` for sync purposes.

Refactor:
- The `debounce` utility has been improved to include `flush` and `cancel` methods, allowing for more precise control over debounced function execution, which is now used in the sync hook.

* fix(kosync): add support for converting between XPointer and CFI in progress synchronization

* fix(kosync): update navigation method to use select instead of goTo for paginated formats

* fix(kosync): refactor synchronization settings and improve conflict resolution handling

* fix(kosync): add event dispatcher for flushing KOReader synchronization

* fix(sync): handle xpointer in a different section, fix styling

* i18n: update translations

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-11 17:53:28 +02:00
Huang Xin b8bb1ee71d fix(sync): correct xpointer spine index and omit certain tag index (#1781) 2025-08-11 15:25:16 +02:00
arutonee1 4298213ce4 feat: added option to delete only the local copy of a book (#1773)
* feat: added button to delete the local copy of a book

* refactor: merged deletion api and messages

* fix: included translations for deletion messages

* updated translations

* refactor the deletion dropdown menu

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-11 10:38:34 +02:00
Huang Xin 5671460618 sync: add xcfi to convert between readest epubcfi and koreader xpointer (#1774) 2025-08-09 19:01:54 +02:00
Huang Xin 452dc9f3e6 chore: cache nextjs build for CI (#1771) 2025-08-09 07:28:38 +02:00
Andres Puello 09e65211b4 feat: add option to config reading progress format (#1761)
* feat: added percentage option and set by default

* feat: added decimal percentaje

* fix: fixed issue that the function of format didn't look for typeof

* fix: Added revision fixes

* fix: Added revision fixes and changes

* fix: solved about styling code

* fix: solved about styling code on book.ts and translation.json

* fix: changed lines useEffect line that is escencial to Layoutpanel.tsx, and ProgressInfo.tsx has correct render like last version (Loc.) nd its separator

* fix: correct SUPPORTED_LANGS key and disable style reapplication in saveViewSettings

* i18n for the progress style config

---------

Co-authored-by: Huang Xin <chrox.huang@gmail.com>
2025-08-09 06:23:18 +02:00
Huang Xin f46be89036 fix: apply system color scheme for iOS in auto theme mode, closes #1762 (#1767) 2025-08-08 13:45:04 +02:00
Huang Xin f2f744a2fe css: mix with background color for horizontal rule, closes #1649 (#1759) 2025-08-07 11:18:56 +02:00
Huang Xin ceb1963b88 auth: avoid infinite loop of redirect and auth after token is expired (#1758) 2025-08-07 05:40:13 +02:00
Huang Xin 23d74b567a css: apply monospace font settings for EPUBs, closes #1754 (#1757) 2025-08-07 03:54:44 +02:00
StepanSad 5014932a8f i18n: Update translation.json (#1756) 2025-08-07 07:20:14 +08:00
Huang Xin 6a5694d2b2 feat: readest koplugin for progress sync (#1753) 2025-08-06 17:12:00 +02:00
Huang Xin df074082a8 fix: thread-safe NativeFile read for PDF on Android, closes #1748 (#1751) 2025-08-06 16:24:56 +02:00
Huang Xin 847d514441 fix: NativeFile access for file URI when importing files on iOS, closes #1746 (#1747) 2025-08-05 18:48:42 +02:00
Huang Xin b2a71da2b0 fix: chunk of cache item now has enough space for read of MAX_CACHE_CHUNK_SIZE, closes #1392 (#1744) 2025-08-04 16:35:27 +02:00
Huang Xin 3e5e4d2946 compat: detect book language from text for invalid language code in metadata (#1743) 2025-08-04 14:55:18 +02:00
Huang Xin e3c05e7648 feat: add reset password button in user page (#1742) 2025-08-04 14:12:19 +02:00
Huang Xin df9ca2d2fa api: migrate usage stats from KV to db (#1739) 2025-08-03 05:36:33 +02:00
Huang Xin 1631ece2bd docs: doc submodule update as dependencies 2025-08-03 03:39:24 +02:00
Huang Xin 2ae25f3bbe translator: add more translator languages (#1738) 2025-08-02 17:09:09 +02:00
Huang Xin 511093a6ef tts: tts now works when language codes are mistakenly set with language names (#1737) 2025-08-02 16:52:32 +02:00
Huang Xin 1ef126f820 release: version 0.9.69 (#1733) 2025-08-02 03:36:10 +02:00
Huang Xin 1788b86ced chore: bump to upstream foliate-js (#1732) 2025-08-02 03:24:58 +02:00
Huang Xin 3111e93054 layout: fix unintended reader view scroll when highlighting, closes #1716 (#1731) 2025-08-02 02:58:58 +02:00
Huang Xin 288abc678c refactor: unified path resolver on all platforms (#1730) 2025-08-01 16:51:17 +02:00
Huang Xin 7e559c5b90 metadata: fix formatting for subjects, closes #1724 (#1727) 2025-08-01 00:19:58 +08:00
Huang Xin eaaeb844f3 txt: fix language detection when parsing TXT files, closes #1720 (#1723) 2025-07-31 08:52:25 +02:00
Huang Xin 097fde203d css: overriding book color also applies to the font elements (#1721) 2025-07-31 04:40:08 +02:00
Huang Xin efd85e67d9 api: production for apple iap verifier (#1719) 2025-07-31 01:41:55 +02:00
Huang Xin bd4bf459e3 epub: fix some images are not displayed in EPUBs, closes #1709 (#1718) 2025-07-30 14:11:55 +02:00
Huang Xin 8fb1745dbb config: add an option to disable double click, closes #1713 (#1715) 2025-07-30 11:35:07 +02:00
Huang Xin 47c6d6d058 layout: fix cover image width in fit mode in list view (#1714) 2025-07-30 11:29:31 +02:00
Huang Xin 564693d456 security: add updater permission for standalone reader window, closes #1710 (#1712) 2025-07-30 05:00:46 +02:00
Huang Xin aa3cd2a0c5 txt: trim punctuations in parsed author names (#1707)
* txt: trim punctuations in parsed author names

* release: version 0.9.68
2025-07-29 20:50:21 +02:00
Huang Xin c581f8ab47 txt: more robust TXT parsing (#1706) 2025-07-29 20:06:55 +02:00
Huang Xin 63ed763499 api: handle downloading books of sanitized titles (#1705) 2025-07-29 18:23:19 +08:00
Huang Xin 213fc0d21e api: handle more string sanitization (#1702) 2025-07-28 19:12:42 +02:00
Huang Xin 9bc1494582 api: sanitize string before saving into db (#1701) 2025-07-28 18:44:26 +02:00
Huang Xin 33b2ba1609 chore: add legal links in about window and user page (#1700) 2025-07-28 18:19:45 +02:00
Huang Xin b9add62ba7 translation: skip translating pre, code and math tags, closes #1693 (#1698) 2025-07-28 09:18:19 +02:00
Huang Xin 23fa2dc8b2 css: fix hard-coded font color, closes #1695 (#1697) 2025-07-28 08:36:10 +02:00
Huang Xin 67ac09a290 premium: increased cloud sync storage for premium users (#1696) 2025-07-28 08:25:22 +02:00
Huang Xin f10c14ae96 api: batch updating daily usage key in KV (#1694) 2025-07-28 06:25:47 +02:00
Huang Xin 438d3ae0c2 config: default to open file with new window (#1691) 2025-07-27 19:12:19 +02:00
Huang Xin 842062fe44 perf: multi-part download with range access (#1690) 2025-07-27 18:56:41 +02:00
Huang Xin d01897d62a fix: load backup library data if main library data is unavailable, closes #1672 (#1689) 2025-07-27 16:48:58 +02:00
Huang Xin 6eeb8e7580 layout: fix insets for double borders and add book spine decorator (#1688) 2025-07-27 14:36:55 +02:00
Huang Xin 9cebadba57 font: fix broken links for online CJK fonts (#1687) 2025-07-27 08:25:25 +02:00
Huang Xin e5ec9abca3 chore: suppress warnings from old objc crate (#1686) 2025-07-27 08:03:45 +02:00
Huang Xin 5e7f09d5c8 perf: eliminate redundant re-renders of book cover components (#1685) 2025-07-27 07:02:05 +02:00
Huang Xin ffa193d116 fix: importing books from url and set downloaded timestamp for reimported books (#1684) 2025-07-26 14:34:51 +02:00
Huang Xin 686f8988bc api: use node api endpoint for iap verifying (#1683) 2025-07-26 13:34:36 +02:00
Huang Xin 8d5c98004b api: ensure proper string decoded on edge runtimes (#1680) 2025-07-25 21:13:01 +02:00
Huang Xin 214411919f iap: use sandbox environment for testflight (#1679) 2025-07-25 19:49:53 +02:00
Huang Xin 42c2825e90 feat: enable IAP for upgrading to Readest Premium on iOS (#1678) 2025-07-25 19:08:53 +02:00
Huang Xin 8974a87168 feat: add IAP server api (#1676) 2025-07-25 14:44:56 +02:00
Huang Xin f3e9983742 feat: add IAP in native bridge plugin for iOS (#1673) 2025-07-24 14:54:17 +02:00
初五 beaf034035 docs: fix outdated repository links and spelling error (#1669)
* docs: update outdated repository links in CONTRIBUTING.md

- Update issue tracker link from chrox/readest to readest/readest    
- Update new issue link from chrox/readest to readest/readest

* fix: correct spelling of "quota" in release notes

- Fix typo "quata" -> "quota" in 0.9.30 release notes
2025-07-23 20:00:55 +02:00
Huang Xin a5f18ab024 release: version 0.9.67 (#1664) 2025-07-22 16:11:00 +02:00
Huang Xin d1f5934947 library: various fixes on metadata editor and bookshelf (#1663)
* i18n: update translations

* library: various fixes on metadata editor and bookshelf

* chore: enable parallel web builds checking
2025-07-22 16:04:44 +02:00
Huang Xin f03d6f6861 feat: search in book format, group names and descriptions (#1662) 2025-07-22 11:40:14 +02:00
Huang Xin 20523b7eaf PDF: Support custom background theming for PDF files, closes #1649 (#1661) 2025-07-22 11:34:38 +02:00
Huang Xin f31c9b2a98 layout: fix hardcoded image layout in fixed layout documents (#1660) 2025-07-22 10:40:21 +02:00
Huang Xin 93071e727f linux: fixed multiple instances created in OAuth, closes #1654 (#1659) 2025-07-22 09:00:19 +02:00
Huang Xin 2d12210606 css: multiply img color in light mode when overriding book color (#1656)
This fixed image color issue for EPUBs in #1649.
2025-07-21 18:17:16 +02:00
Huang Xin 88ccc528d4 layout: hover header to show traffic light window control on macOS, closes #1645 (#1653) 2025-07-21 17:16:58 +02:00
Yurii Kostyukov 9d3fc07865 feat: Added Yandex Translator (#1652) 2025-07-21 17:13:13 +02:00
Huang Xin 6d465a40fb fix: don't use comma as separator when parsing filenames, closes #1622 (#1650) 2025-07-21 15:32:10 +02:00
Huang Xin 01c7e4c2e2 chore: fixed failed AppImage builds for Linux 2025-07-21 20:39:57 +08:00
Huang Xin 4005858242 release: version 0.9.66 (#1643) 2025-07-20 23:12:40 +02:00
Huang Xin c24f951375 fix: set xdg-mime with mime type other than scheme, closes #1621 (#1641) 2025-07-21 05:02:22 +08:00
Huang Xin dbe1efd1e9 tts: convert iso6392 language code to iso6391 to filter tts voices, closes #1627 (#1639) 2025-07-20 22:10:14 +02:00
Huang Xin 0299cce343 library: fix book not redownloaded for the redownload button in detail modal, closes #1628 (#1638) 2025-07-20 21:00:29 +02:00
Huang Xin ca9f504329 layout: fix layout for auth and user page (#1637) 2025-07-20 20:05:55 +02:00
Huang Xin df3527ceca fix: chaining file open with OS opening the highest precedence, closes #1622 (#1636) 2025-07-20 17:09:19 +02:00
Huang Xin 73d0838291 doc: run a preview build to identify compatibility issues between OpenNext, Next.js, and Supabase (#1635) 2025-07-20 11:32:00 +02:00
Huang Xin 8e3574509e chore: downgrade next.js to 15.3 (#1634)
Until https://github.com/opennextjs/opennextjs-cloudflare/issues/667 is fixed.
2025-07-20 11:12:06 +02:00
Huang Xin 9bdf375304 api: fix cors for api with new nextjs version (#1633) 2025-07-20 10:24:28 +02:00
Huang Xin effa922597 chore: bump tauri, next and zustand to latest versions (#1631) 2025-07-20 09:42:32 +02:00
Huang Xin fe484257a0 doc: add deepwiki badge and link (#1630) 2025-07-20 07:02:14 +02:00
Huang Xin cc0cd9a114 layout: fix nested toc items not expanded in very long toc list, closes #1625 (#1629) 2025-07-20 06:42:31 +02:00
Huang Xin 916d63806e chore: fix loading chunk error of optional chaining for Android WebView below 80 (#1626) 2025-07-19 20:24:30 +02:00
Huang Xin 59d0118067 chore: handle ChunkLoadError by refreshing page (#1619) 2025-07-19 06:37:13 +02:00
Huang Xin 957c6b9f32 fix: update book cover image from metadata in sidebar (#1615) 2025-07-18 19:11:02 +02:00
Huang Xin 9c5a1147fa release: version 0.9.65 (#1614) 2025-07-18 18:31:38 +02:00
Huang Xin 60ad6706ff ios: skip context menu when long-press on book cover, closes #1612 (#1613) 2025-07-18 18:22:59 +02:00
Huang Xin d9199d1182 sync: also sync book metadata (#1611) 2025-07-18 17:59:05 +02:00
Huang Xin 219edb9faf translation: fix translation not working for table of contents (#1610) 2025-07-18 11:54:26 +02:00
Huang Xin 30a0227ef3 layout: unset text indent inside of list elements (#1609) 2025-07-18 11:26:28 +02:00
Huang Xin 29b5a7cb88 tts: skip text to speech for rubys and footnote anchors, closes #1334 (#1608) 2025-07-18 11:10:12 +02:00
Huang Xin abb727870e tts: handle invalid language code and show no voices hints, closes #1579 (#1607) 2025-07-18 09:27:51 +02:00
Dave Vasilevsky af84a8b110 chore: match dev-web command to docs (#1606) 2025-07-18 06:50:41 +02:00
Huang Xin 0dea5d3094 layout: maintain the layout of anchor elements while increasing their tap target size, closes #1603 (#1605) 2025-07-18 06:26:54 +02:00
Huang Xin 527b95521e fix: replace fallback book cover with new cover image (#1604) 2025-07-18 06:03:22 +02:00
Huang Xin 396b23ddb4 css: named container classes for easier css customization, closes #1598 (#1600) 2025-07-17 20:17:51 +02:00
Huang Xin ee0d1549bd layout: add window borders on Linux, closes #1570 (#1599) 2025-07-18 01:40:12 +08:00
Huang Xin 6da6b85530 feat: support multiple reader windows on desktop, closes #259 (#1596) 2025-07-17 10:21:37 +02:00
StepanSad cde055dcb7 Update translation.json (#1595) 2025-07-17 05:03:39 +02:00
Huang Xin 08957ce3d7 release: version 0.9.64 (#1589) 2025-07-15 19:40:03 +02:00
Huang Xin 33515f1f09 fix: save custom cover images in apps (#1588) 2025-07-15 19:33:13 +02:00
Huang Xin e3c411f043 feat: support to edit book metadata, closes #753 (#1583) 2025-07-14 19:46:57 +02:00
StepanSad eda71725da Update translation.json (#1581) 2025-07-14 20:09:46 +08:00
StepanSad f19f45c00e Fixed bad Ukrainian translation. (#1576) 2025-07-12 18:01:41 +02:00
Huang Xin 6fb7897092 chore: flatpak with custom oauth (#1574) 2025-07-11 11:28:43 +08:00
Huang Xin 2de941d7fb doc: add appdata metainfo for Flathub (#1569) 2025-07-10 14:10:40 +02:00
Huang Xin 3e4b7ebb13 css: remove unintended indent for images, closes #1567 (#1568)
* chore: target Android SDK to version 36

* css: remove unintended indent for images, closes #1567
2025-07-10 12:05:07 +02:00
Huang Xin 2b52f82b57 fix: bookmark ribbon is now placed correctly when sidebar is pinned (#1565) 2025-07-09 18:23:55 +02:00
Huang Xin 9fb7e80757 fix: pull down to refresh is now more smoother and responsive (#1564) 2025-07-09 18:01:33 +02:00
Huang Xin 38869ce438 search: more responsive full-text search, closes #1558 (#1562) 2025-07-09 12:36:22 +02:00
Huang Xin 1dbf93c1cf css: lighten the highlight color in dark mode, closes #1489 (#1561) 2025-07-09 10:41:40 +02:00
Huang Xin aafcab8232 layout: fix dimension of inline images, closes #1555 (#1560) 2025-07-09 09:52:51 +02:00
Huang Xin bcfe1b8830 fix: invalid href handling in EPUB (#1557) 2025-07-09 04:57:21 +02:00
Huang Xin 5db1847f7e layout: add window border on Windows 10, closes #1551 (#1556) 2025-07-08 17:24:43 +02:00
Huang Xin 165ca4a6e9 chore: add fastlane action to release to Google Play (#1554) 2025-07-08 16:18:53 +02:00
Huang Xin 9556e1305f updater: show update notes for new releases (#1552) 2025-07-08 12:13:47 +02:00
Huang Xin f5b686abd9 release: version 0.9.63 (#1550) 2025-07-07 15:07:35 +02:00
Huang Xin 74e1b733a9 layout: dismiss system context menu popup when selecting text on iPad, closes #1485 (#1549) 2025-07-07 14:57:04 +02:00
Huang Xin a9e0090275 i18n: add Thai (th-TH) translations (#1548) 2025-07-07 11:21:41 +02:00
Huang Xin 68d57da9ca perf: speed up opening for large MOBI books, closes #1544 (#1547) 2025-07-07 10:15:54 +02:00
Bhscer 3cef529ebd feat: support deleting only the cloud backup of a book (#1546)
* feat: support delete the cloud storage of a book

* feat: merge to delete function

* fix: change a logic
2025-07-07 09:37:11 +02:00
Huang Xin 85ac399e2c tts: fix some direct text being skipped in tts, closes #1515 (#1543) 2025-07-06 12:13:40 +02:00
Huang Xin e8d09773a2 fix: handling toc links for some mobi books, closes #1525 (#1542) 2025-07-06 08:49:47 +02:00
Huang Xin 23905407ca doc: layout for the download badges (#1541) 2025-07-06 05:42:29 +02:00
Huang Xin 2ab1304b6d doc: update Readme for mobile apps downloads (#1540) 2025-07-06 05:20:30 +02:00
Huang Xin d94236e127 layout: fix grid cover image height in crop mode (#1537)
Support setting arbitrary columns with custom UI css:
```
.bookshelf-items {
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
}
```
2025-07-05 17:29:33 +02:00
Huang Xin 630025c123 chore: update Android target sdk to 35 (#1534) 2025-07-05 08:49:41 +02:00
Huang Xin 2b73c5a85a perf: lazy loading fonts in fonts list (#1533) 2025-07-05 07:32:22 +02:00
Huang Xin a03f2c585f layout: fix overlay scrollbar with virtualized list, closes #1524 (#1530) 2025-07-04 19:36:13 +02:00
Huang Xin 8a0216dcf8 fix: links in mobi are now properly followed, closes #1503 (#1528) 2025-07-04 07:47:27 +02:00
Huang Xin 59013be586 settings: remove unsupported screen orientation lock on iPad, closes #1520 (#1522) 2025-07-03 13:41:42 +02:00
Huang Xin c6a638dbfd compat: footnote img size, closes #1518 (#1519) 2025-07-02 18:55:09 +02:00
Huang Xin e54440ad80 chore: retry workflow for r2 uploading (#1517) 2025-07-02 17:37:05 +02:00
Huang Xin 446b014f7d chore: upload to r2 after release with retry (#1516) 2025-07-02 17:23:12 +02:00
Huang Xin 9190433ea1 subscription: fix action button in plan card (#1514) 2025-07-02 16:48:32 +02:00
Huang Xin d78d135600 release: version 0.9.62 (#1513) 2025-07-02 15:02:20 +02:00
Huang Xin 04b6c89e58 layout: refine user profile page (#1512) 2025-07-02 13:50:22 +02:00
Huang Xin 8eefba2bee feat: add an option to show/hide original text in translation, also closes #1492 (#1511) 2025-07-02 07:27:05 +02:00
Huang Xin a254139200 layout: extend drag area outward so the scrollbar remains clickable (#1509) 2025-07-01 09:55:09 +02:00
Huang Xin 0514bed5b7 css: fix for legacy epub image dimension, closes #1507 (#1508) 2025-07-01 09:46:27 +02:00
Huang Xin 49978c5e3f layout: add overlay scrollbar for TOC on Android (#1506) 2025-07-01 09:17:18 +02:00
Huang Xin b959bf070c feat: add upgrade to premium option in settings menu (#1505) 2025-07-01 09:15:25 +02:00
Huang Xin 49e6877810 feat: add option to toggle Parallel Reading on/off when viewing multiple books, closes #1496 (#1504) 2025-06-30 19:06:59 +02:00
Huang Xin bb759597e9 shortcuts: add ESC to dismiss search bar, closes #1495 (#1502) 2025-06-30 16:13:43 +02:00
Huang Xin bf0bb80009 api: fix for supabase RLS (#1501) 2025-06-30 15:08:53 +02:00
Huang Xin 5d61a9808f feat: add subscription management for tauri platforms (#1499) 2025-06-30 10:04:57 +02:00
Huang Xin bc36da6b25 feat: add subscription management for web (#1494) 2025-06-29 18:33:36 +02:00
Huang Xin 280cb75ca4 api: cors in middleware (#1493) 2025-06-29 17:00:52 +02:00
Huang Xin d5d0a9abd3 api: add subscription API (#1491) 2025-06-29 14:38:04 +02:00
Huang Xin 9f343992b2 settings: add three new CJK fonts (#1484) 2025-06-27 12:31:01 +02:00
Huang Xin 7ab7058dba refactor: fix fallback cover image for fitted mode (#1483) 2025-06-27 10:28:38 +02:00
Huang Xin c3113ade13 chore: fix uninlined format args warning (#1482) 2025-06-27 10:27:21 +02:00
Huang Xin 7737c05c8f layout: apply custom css also to the library page, tweaks on fitted cover images (#1481) 2025-06-27 10:01:17 +02:00
Huang Xin 4fde9955b2 release: version 0.9.61 (#1479) 2025-06-26 11:27:02 +02:00
Huang Xin 3875c8f239 feat: add an option to show remaining pages in chapter (#1478) 2025-06-26 11:19:30 +02:00
Huang Xin 7bd1b03652 fix: get correct filename when importing txt files on iOS (#1477) 2025-06-26 10:21:55 +02:00
Huang Xin 73878c14e3 fix: EPUB covers occasionally fail to display in library view, closes #1029 (#1476) 2025-06-26 10:03:35 +02:00
Huang Xin bc2c3a2c9a feat: add options to reset settings in the config dialog, closes #1111 (#1475) 2025-06-26 09:59:34 +02:00
Huang Xin 460d37c656 chore: update assetlinks and deeplink for Android (#1474) 2025-06-25 14:47:50 +02:00
Huang Xin fff9e3221e chore: use default production supabase and posthog if not customized, closes #1454 (#1473) 2025-06-25 13:46:48 +02:00
Huang Xin f77fa17cbc bump: update tauri to latest dev branch, closes #1468 (#1471) 2025-06-25 05:36:45 +02:00
Huang Xin d4565f7d60 feat: add an option in the view menu to crop or fit book covers of the library page (#1469) 2025-06-25 03:48:08 +02:00
Huang Xin e8b1976f4f doc: adjust the priority of the readest koplugin for Koreader (#1467) 2025-06-24 08:34:31 +02:00
Huang Xin 6b4bb64885 feat: custom css for the reader UI (#1466) 2025-06-24 08:33:49 +02:00
Huang Xin fc0c42f7db android: fix transparent icon background on Android, closes #1462 (#1464) 2025-06-24 06:54:49 +02:00
Huang Xin 41e5f27d4d layout: view menu now has a maximum width, closes #1459 (#1463)
* refactor: auth callback page

* layout: view menu now has a maximum width, closes #1459
2025-06-24 05:44:03 +02:00
Huang Xin a042b4c739 fix: longer timeout to save changes before reloading page (#1461) 2025-06-24 05:01:46 +02:00
Huang Xin a1c1a9f983 chore: bump opennextjs and wrangler to latest versions (#1460) 2025-06-24 03:22:12 +02:00
Huang Xin 423ae77160 doc: add the Support section in README (#1458) 2025-06-23 12:46:08 +02:00
Huang Xin 05c61a4b1a doc: add donation badge and link with crypto payment (#1457) 2025-06-23 11:37:13 +02:00
Huang Xin ce8f06b7fb chore: add sponsor readest link 2025-06-23 05:03:21 +02:00
Huang Xin f815844577 release: version 0.9.60 (#1452) 2025-06-22 11:24:47 +02:00
Huang Xin 7fe4d38c51 fix: allow inline scripts on Tauri platforms (#1451) 2025-06-22 11:22:39 +02:00
Huang Xin 43edcb4e9e android: dismiss the splash screen and change icon background color in task list (#1450)
* layout: various layout fix on iPad

* android: dismiss the splash screen and change icon background color in task list, closes #1424
2025-06-22 09:10:03 +02:00
Huang Xin e259b29afa fix: disable translation when the primary language is not defined, closes #1443 (#1448) 2025-06-22 07:10:22 +02:00
Huang Xin cd5f57a004 layout: ignore system top/bottom safe insets when there is no header or footer, closes #1442 (#1447) 2025-06-22 06:32:23 +02:00
Huang Xin 8c0cdb0e22 layout: tweaks on layout for iPad (#1446) 2025-06-22 05:55:40 +02:00
初五 c0df9f1cb6 doc: correct spelling 'Themeing' to 'Theming' in README (#1444) 2025-06-22 08:19:21 +08:00
Huang Xin cb1ddaeedb fix: smoother orientation change in iOS, closes #1434 (#1441) 2025-06-21 17:11:45 +02:00
Huang Xin 4447dd7b9a css: fix unintended font size adjust in scrolled mode on iOS, closes #1438 (#1440) 2025-06-21 13:25:46 +02:00
Huang Xin dcb7602837 fix: importing books on iOS at first launch is more reliable now (#1439) 2025-06-21 13:17:24 +02:00
Huang Xin 60e48742fe chore: allow download url for installers (#1437) 2025-06-21 11:13:59 +02:00
Huang Xin dd43569778 chore: download releases using cloudflare cdn (#1436) 2025-06-21 10:36:51 +02:00
Huang Xin 8433dd6950 chore: upload release artifacts to a R2 release bucket (#1435) 2025-06-21 16:24:49 +08:00
Huang Xin 379802f580 release: ready for Google Play release, closes #1001 (#1433) 2025-06-20 16:48:53 +02:00
Huang Xin 2dcb5d7c1e sync: add network timeout of sync to 5 sec (#1432)
* sync: add network timeout of sync to 5 sec

* css: minor tweaks on headings and list
2025-06-20 16:45:10 +02:00
Huang Xin 98870d9f3e release: version 0.9.59 (#1429) 2025-06-19 17:10:14 +02:00
Huang Xin 31aeb2289c settings: more reasonable constraints for top and bottom margins (#1428) 2025-06-19 17:01:20 +02:00
Huang Xin 0f66ecbba1 layout: remove unnecessary inset for header on landscape screen in iOS (#1425) 2025-06-19 12:01:42 +02:00
Huang Xin f0d3c9dd4e fix: transform vw/vh to pixels correctly, closes #1420 (#1421)
* i18n: update translations

* fix: transform vw/vh to pixels correctly, closes #1420
2025-06-19 10:53:58 +02:00
Huang Xin 92aae340d9 settings: unset deprecated margin settings (#1419) 2025-06-18 18:56:41 +02:00
Huang Xin 3cf40039ee release: version 0.9.58 (#1418) 2025-06-18 18:23:09 +02:00
Huang Xin 7ed4937d9d settings: add an option to always show status bar (#1417) 2025-06-18 18:15:55 +02:00
Huang Xin b3593c0365 feat: support split-screen mode on iPad, closes #971 (#1416) 2025-06-18 15:54:46 +02:00
Huang Xin 6235202066 layout: fix issue where sidebars couldn’t be dragged to resize on iPad (#1415) 2025-06-18 15:43:51 +02:00
Huang Xin b3d1085ddf fix: now it's possible to have multiple columns in portrait mode, closes #1369 (#1413) 2025-06-18 14:25:32 +02:00
Huang Xin b2a4ddae60 feat: show annotation create time and various other UI fixes (#1412) 2025-06-18 13:17:18 +02:00
Huang Xin 42f50af27b tts: each book view now has its own tts controller (#1411) 2025-06-18 11:37:02 +02:00
Huang Xin 07f74d8858 settings: individually adjust top, bottom, left, and right margins on the reader page (#1410) 2025-06-18 10:55:24 +02:00
Huang Xin 9477789db1 layout: apply responsive safe area insets to the reader page, closes #1067 and closes #1212 (#1408)
This also fixes layout glitches when showing status bar on devices without display cutouts
2025-06-17 03:36:23 +02:00
Huang Xin 8a7d0e1b0d fix: annotation tools now works when TTS is enabled, closes #1403 (#1406) 2025-06-16 08:53:55 +02:00
Huang Xin 261ce95ed1 feat: add keyboard shortcut t to toggle TTS (#1405) 2025-06-16 08:50:10 +02:00
Huang Xin c5a3b44cbf chore: add NEXT_PUBLIC_API_BASE_URL in example env file (#1404) 2025-06-16 06:02:50 +02:00
Huang Xin ed68d25b15 fix: restore full view settings when reopening book (#1400) 2025-06-15 16:07:51 +02:00
Huang Xin f7f8872a13 tts: translation also when TTS is played in the background, closes #1383 (#1399) 2025-06-15 09:07:22 +02:00
Huang Xin be9802a936 release: version 0.9.57 (#1395) 2025-06-14 15:42:08 +02:00
Huang Xin 84328dcfb2 hotfix: resolve compatibility issues with Android Text-to-Speech API (#1394) 2025-06-14 15:39:12 +02:00
Huang Xin 89d48c72b0 release: version 0.9.56 (#1391) 2025-06-13 17:35:38 +02:00
Huang Xin 467e80cd4b fix: various fixes on text selector in TTS mode and normal image height (#1390) 2025-06-13 17:28:21 +02:00
Huang Xin 20955ec294 chore: handle client uncaught exceptions and show error message (#1389) 2025-06-13 15:24:24 +02:00
James Sizeland 3cae183f51 feat: add syntax highlighting (#1386)
* add highlightjs

* add github light and dark theme

* inject style classes

* fix typo

* remove conflicting style overrides

* add highlightjs function and language list

* add language select setting

* add language select input

* add watcher to rerender page on changes to language select

* lockfile update

* rename highlightCode variables

* reorder code languages alphabetically

* copy translation style for highlight selector

* move code highlighting below theme color section

* move highlighting toggle logic into settings component

* add conditional statement to docLoadHandler manageSyntaxHighlighting

* reorder imports

* switch to media query for dark mode

* combine useEffect callbacks for code highlighting

* add feature line about code highlighting

* fix table tabbing
2025-06-13 14:40:18 +02:00
Huang Xin 99319ab4d1 fix: TTS now properly loads the next chapter in the background, closes #1382 (#1388) 2025-06-13 13:54:17 +02:00
Huang Xin 658969e97a feat: support native tts engine on Android (#1387) 2025-06-12 17:56:14 +02:00
James Sizeland 28c6c1ec58 css: add more aggressive overrides for background and text colour (#1384)
* add more aggressive overrides for background and text colour

* reduce styles changes
2025-06-12 14:24:50 +02:00
James Sizeland d8cc220c18 chore: add vscode settings to include inlayhints (#1385) 2025-06-12 14:22:49 +02:00
Huang Xin 97d7eb659c bump: sync with upstream foliate-js (#1381) 2025-06-11 05:11:06 +02:00
Ahmed 9766ec0125 i18n: improve some Arabic strings in translation.json (#1380) 2025-06-11 03:05:49 +02:00
Huang Xin e46d01448b css: handle inline image height (#1379) 2025-06-10 08:43:00 +02:00
Huang Xin bb8bf99b40 Also transform CSS for azw3 files (#1378) 2025-06-10 06:39:15 +02:00
Huang Xin f8ac30adf1 tts: add native tts plugin for Android (#1376) 2025-06-09 16:54:12 +02:00
Huang Xin 69d418aa61 perf: optimize toc list view for very large toc list, closes #386 (#1370) 2025-06-08 15:01:34 +02:00
Huang Xin 95af3bd3d1 release: version 0.9.55 (#1366)
* release: version 0.9.55

* layout: fix drag handler for multiple dialog instances
2025-06-07 07:55:36 +02:00
Huang Xin 5bdc29fe84 perf: optimize by removing unnecessary animations on Android, closes #1360 (#1365) 2025-06-07 06:53:53 +02:00
Huang Xin 56f4b275f4 translator: toast for Daily Quota Exceeded error (#1363) 2025-06-06 16:24:14 +02:00
Huang Xin 00b46dd8bb bump: upgrade next.js to latest version (#1362) 2025-06-06 15:40:33 +02:00
Huang Xin 8ce89ca8f6 bump: upgrade supabase.js to latest version (#1361) 2025-06-06 14:37:29 +02:00
Huang Xin 648d9ec260 css: unset justify text align when overriding layout, closes #1338 (#1358) 2025-06-06 12:08:53 +02:00
Huang Xin ebe1c10c84 sync: also update deleted notes in synchronization, closes #1356 (#1357) 2025-06-06 11:47:26 +02:00
Huang Xin f2309ef496 release: version 0.9.53 (#1351) 2025-06-05 17:42:22 +02:00
Huang Xin 8d71593f39 fix: exit select mode when all books are deleted, closes #1347 (#1350) 2025-06-05 17:26:55 +02:00
Huang Xin 09e3c30a2a feat: add daily translation quota of DeepL API for more sustainable service (#1349) 2025-06-05 16:58:07 +02:00
Huang Xin eee4cbeaca fix: avoid additional click after books is downloaded before opening (#1346) 2025-06-05 11:43:46 +02:00
Huang Xin 00deca5029 bump: upgrade opennext and wrangler to latest versions (#1345) 2025-06-05 10:46:52 +02:00
Huang Xin c7c20fec55 epub: last resort to get the first image in manifest as book cover (#1343) 2025-06-05 08:40:48 +02:00
Huang Xin 57e22dc7b6 layout: use system select widget for language selection, closes #1339 (#1342) 2025-06-05 08:19:24 +02:00
Huang Xin debd60fb70 css: inline image only as direct children of p and span, closes #1340 (#1341) 2025-06-05 07:47:56 +02:00
Huang Xin dfa17c1c0b css: auto height for single images in anchor (#1337) 2025-06-04 20:08:16 +02:00
Huang Xin 2a26f8b04a fix: trigger library update for the books counter after importing books (#1336) 2025-06-04 19:02:49 +02:00
Huang Xin db955d2c8c feat: add an option to override book fg/bg color, closes #1328 (#1335) 2025-06-04 18:58:53 +02:00
Huang Xin 51602a77fb tts: disable media session controls to keep alive tts (#1333) 2025-06-04 08:17:33 +02:00
Huang Xin 48da5ab546 fix: update current bookshelf after importing and deleting books (#1331) 2025-06-03 19:33:23 +02:00
Huang Xin 363b98dd3b release: version 0.9.52 (#1327) 2025-06-03 15:49:04 +02:00
Huang Xin 43532a0ee8 feat: add an option to show remaining in minutes of the current chapter, closes #406 (#1326) 2025-06-03 15:37:55 +02:00
Huang Xin 03b2af1f97 chore: bump pdf.js to latest release of version 4 (#1325) 2025-06-03 13:22:52 +02:00
Huang Xin 3f8d03ae28 sync: add sync status menu item in view menu, closes #844 (#1324) 2025-06-03 11:43:58 +02:00
Huang Xin 146a71fdef font: load CJK fonts only in CJK env or the book has CJK languages, closes #1298 (#1323) 2025-06-03 08:55:40 +02:00
Huang Xin 734e22d92c layout: notebook layout tweaks (#1319) 2025-06-02 17:27:02 +02:00
Huang Xin a77986c0e6 feat: add search functionality to the notebook (#1318) 2025-06-02 16:03:55 +02:00
Huang Xin 4e7f9c49b0 layout: render book description as html in book details, closes #1316 (#1317) 2025-06-02 14:25:51 +02:00
Huang Xin c51c95b883 feat: support note taking with markdown, closes #1097 (#1315) 2025-06-02 11:35:09 +02:00
Huang Xin 62081bebfd fix: update current bookshelf items when library is updated (#1314) 2025-06-02 09:57:11 +02:00
Huang Xin 15394d17d8 feat: show current books count in search bar, closes #1310 (#1312) 2025-06-02 09:31:35 +02:00
Huang Xin 5d1deadc1e fix: defers rendering all component until after hydration for Tauri platforms, closes #1301 (#1311) 2025-06-02 08:25:08 +02:00
Huang Xin b497ddacac css: add margin for div only when overriding layout, closes #1286 (#1309) 2025-06-02 07:59:58 +02:00
Huang Xin a9965fd52c config: add an option to sort TOC by page number, closes #1304 (#1308) 2025-06-02 07:23:59 +02:00
Huang Xin 87d610bb62 revert: some browsers may report wrong pixel density, closes #1305 (#1306)
This reverts commit 6e0cc38aa7ca726e251c16e453922fcd637cee49.
2025-06-02 06:19:13 +02:00
Huang Xin dbc4651141 chore: parse webkit version on linux (#1299) 2025-06-01 04:57:53 +02:00
Huang Xin dad2dfd078 i18n: update translations (#1297) 2025-05-31 18:58:43 +02:00
Huang Xin 1fe188814d release: version 0.9.51 (#1296) 2025-05-31 18:33:21 +02:00
Huang Xin 6ec3a5b332 feat: add an option to enable JavaScript in EPUB, closes #1278 (#1295) 2025-05-31 18:25:25 +02:00
Huang Xin 1acba2e08c fix: notebook now won't prevent pagination, closes #1285 (#1294) 2025-05-31 15:02:36 +02:00
Huang Xin 32f9346f84 tts: more robust way to save last speaking location for tts (#1293) 2025-05-31 13:34:35 +02:00
Huang Xin 69c503026a bump: catch up foliate-js upstream (#1292) 2025-05-31 13:31:27 +02:00
Huang Xin 84fc4cec5e tts: read from last read sentence in tts (#1291) 2025-05-31 10:53:36 +02:00
Huang Xin 5c9c11fafd translator: disable translator if the system language is the same with the book language (#1290)
Closes #1288
2025-05-31 09:34:52 +02:00
Huang Xin 77fa57438e tts: show speaking sentence and chapter info in tts media session (#1289) 2025-05-31 09:21:17 +02:00
Huang Xin e4d217f3aa config: add theme mode options in the library page (#1283) 2025-05-30 09:10:48 +02:00
Huang Xin c7a583c53a translator: lazy load translation observer (#1282) 2025-05-30 09:08:45 +02:00
Huang Xin 6c86917098 translator: add daily translation quota for deepl (#1275) 2025-05-29 19:24:09 +02:00
Huang Xin 7c21f48d68 layout: hide the header bar only on mobile platforms when translation is enabled (#1274) 2025-05-29 10:31:02 +02:00
Huang Xin 5820191c26 translator: also translate table of contents (#1273) 2025-05-29 10:08:01 +02:00
Huang Xin b37a804192 css: enhanced compatibility with the text-indent CSS property, closes #1267 (#1271) 2025-05-29 08:53:58 +02:00
Huang Xin ebdfc39ef6 release: version 0.9.50 (#1264) 2025-05-28 17:05:02 +02:00
Huang Xin 9f0d8b5c12 tts: select voice in bilingual TTS mode (#1263) 2025-05-28 16:59:45 +02:00
Huang Xin a267671671 layout: more responsive layout for the header in settings dialog (#1257) 2025-05-28 09:28:51 +02:00
Huang Xin aa62ebd3f0 css: unchange color for anchor children, unchange only image dimension in span, closes #1251 and #1252 (#1256) 2025-05-28 08:04:09 +02:00
Huang Xin cc85e5c304 release: version 0.9.49 (#1247) 2025-05-27 14:47:09 +02:00
Huang Xin b0cc0b6f1a layout: better organized panels in settings dialog (#1246) 2025-05-27 14:37:32 +02:00
Huang Xin 6b033f7509 translator: post-process translated text for punctuation spacing (#1245) 2025-05-27 14:32:25 +02:00
Huang Xin 5c0670031a fix: use updated library when dropfile to import on web browser (#1244) 2025-05-26 16:33:18 +02:00
Huang Xin 35735cd931 fix: skip speech when the text is empty at the end of some chapters, closes #1231 (#1243) 2025-05-26 16:03:43 +02:00
Huang Xin 699a01c78f feat: support full book translation to bilingual books, closes #398 (#1240) 2025-05-26 12:23:33 +02:00
Huang Xin 033e161455 layout: overlay the footer of the translator popup and less sensitive page flip with mouse wheel (#1238)
* layout: overlay the footer of the translator popup

* fix: less sensitive page flip with mouse wheel, addresses #1165
2025-05-25 09:32:00 +02:00
Huang Xin e6428656c2 fix: select filtered books when activating select all (#1237) 2025-05-25 08:02:34 +02:00
Huang Xin 68c3c45da3 feat: add an option to opt-out telemetry (#1236) 2025-05-25 07:48:57 +02:00
Huang Xin 36e87d4eb6 layout: display full publisher field in book details, closes #1224 (#1234) 2025-05-24 07:14:22 +02:00
Huang Xin 7f5059a8ea tts: always infer language code from script in bilingual TTS (#1233) 2025-05-24 07:01:41 +02:00
Huang Xin 8dc04f4095 feat: add bilingual TTS support for bilingual books, closes #1226 (#1230) 2025-05-23 18:18:24 +02:00
Huang Xin 6a48f3feb4 fix: use external browsers to open links in epub file, closes #1211 (#1228) 2025-05-23 08:15:57 +02:00
Huang Xin facf5fba75 fix: reorder icons in the ico file for Windows, closes #1175 (#1227) 2025-05-22 16:47:21 +02:00
Huang Xin 9700942e24 fix: transient navigation bar should work now for Android 8-15, closes #1199 (#1225) 2025-05-22 16:12:59 +02:00
Huang Xin e238b49fbf feat: add an option to invert image color in dark mode (#1223) 2025-05-22 10:38:09 +02:00
Huang Xin 32dfadf182 doc: update screenshots (#1222) 2025-05-22 09:34:16 +02:00
Huang Xin 66744108e2 css: unset link color in footnotes in dark mode for safari (#1221) 2025-05-22 09:09:02 +02:00
Huang Xin 401f28847c fix: add location only when toc item is at the same level as the section (#1220) 2025-05-22 08:08:52 +02:00
Huang Xin 9674b45cf0 layout: align text end for select labels on safari (#1219) 2025-05-22 07:03:10 +02:00
Huang Xin a4bc88d305 feat: add azure and google translator providers, closes #1135 and closes #1215 (#1218) 2025-05-21 17:25:00 +02:00
Huang Xin b6b8b17760 fix: normalize input text by removing newlines to prevent translation glitches (#1210) 2025-05-21 05:26:20 +02:00
Huang Xin 14810d6737 feat: add select all button in select mode, closes #1202 (#1209) 2025-05-20 16:58:04 +02:00
Huang Xin ed6c92a65a css: don't override body background, closes #1206 (#1208)
Revisit #1151.
2025-05-20 15:11:36 +02:00
Huang Xin 20d7a1185e layout: grouping modal now should stay on top of the header bar (#1207) 2025-05-20 14:12:17 +02:00
Huang Xin 32bcfda9ad fix: reimporting books will update the import time, being consistent with update time, closes #1200 (#1203) 2025-05-20 04:40:41 +02:00
Huang Xin def157cacb release: version 0.9.43 (#1196) 2025-05-19 16:51:34 +02:00
Huang Xin 7c464b9a8d feat: add prev/next section buttons in footer bar, closes #1125 (#1195) 2025-05-19 16:40:11 +02:00
Huang Xin 90f3512ae2 feat: add scrolling overlap in pixels option to paginate in scrolled mode (#1194) 2025-05-19 15:21:06 +02:00
Huang Xin 12d93ba749 fix: adjust scroll offset to account for header/footer bars when paginating in scroll mode, closes #1181 (#1193) 2025-05-19 10:34:05 +02:00
Huang Xin 554d786105 fix: update library in store when deleting book in bookshelf, closes #1178 (#1191) 2025-05-19 08:31:25 +02:00
Huang Xin eb71c59524 chore: config wrangler before deployment in Cloudflare (#1189) 2025-05-19 07:55:11 +02:00
Huang Xin 93e81ee05b release: remove deprecated MSI installer, closes #1174 (#1187) 2025-05-19 03:29:44 +02:00
Huang Xin b83972e366 refactor: use translator hook to support more translator providers (#1186) 2025-05-18 18:35:37 +02:00
Huang Xin 967536aa23 refactor: update wrangler config (#1185) 2025-05-18 17:52:50 +02:00
Huang Xin 4523437e34 refactor: add kv cache for translation backend (#1184) 2025-05-18 17:16:44 +02:00
Huang Xin e49dd4accb feat: show cloud backup status for each book on mobile and desktop, closes #1167 (#1173) 2025-05-17 17:02:42 +02:00
Huang Xin b7c1f5703c fix: less sensitive magic trackpad and mouse when flipping page, closes #1165 (#1172) 2025-05-17 08:56:22 +02:00
Huang Xin 5373b67aae css: get rid of most overriding for background color, closes #1162 (#1164) 2025-05-16 08:23:34 +02:00
Huang Xin ddc9673260 css: compat for background colors in chapter headers of Feedbooks eBooks (#1163) 2025-05-16 03:29:07 +02:00
Huang Xin 16b0821eab i18n: add translations of Nederlands (#1161) 2025-05-15 18:32:38 +02:00
Huang Xin 93876a73cc layout: responsive translator popup, also closes #1157 (#1160) 2025-05-15 18:19:32 +02:00
Huang Xin 048867e595 settings: disable spell check in color input (#1159) 2025-05-15 05:33:35 +02:00
Huang Xin 602be376a1 css: include additional weight variants for built-in fonts (#1158) 2025-05-15 05:04:16 +02:00
Huang Xin c52ae43cd9 settings: avoid horizontal margin default to zero, fixes #1151 (#1155) 2025-05-14 15:25:32 +02:00
Huang Xin 348d743e82 css: styling adjustments for better compatibility with Gutenberg eBooks (#1154)
This also closes #1153, #1148, #1147 and the css problem in #1151.
2025-05-14 14:55:07 +02:00
Huang Xin c7e495c379 settings: keep screen awake is disabled by default, closes #1145 (#1149)
* layout: hint info now takes up full width in scrolled mode

* settings: keep screen awake is disabled by default, closes #1145
2025-05-14 11:11:32 +02:00
Huang Xin e368076cac chore: update bundleVersion for macOS AppStore build (#1146) 2025-05-14 03:45:00 +02:00
Huang Xin 382e0ca3c6 release: version 0.9.41 (#1143) 2025-05-13 14:17:01 +02:00
Huang Xin d96fb32bcf fix: apply layout styles to div tag, closes #1130 (#1142) 2025-05-13 13:48:21 +02:00
Huang Xin 4779e2638f fix: disable updater for non-appimage app on linux, closes #1129 (#1141) 2025-05-13 12:50:40 +02:00
Huang Xin d094ba9916 chore: cache avatar image for offline usage (#1140) 2025-05-13 10:42:00 +02:00
Huang Xin e4ba0c302e fix: segment txt with paragraph count if no chapter titles are parsed, closes #1134 (#1139) 2025-05-13 10:21:47 +02:00
Huang Xin 0150177a9d chore: bump tauri to version 2.5.1 (#1138) 2025-05-13 09:30:34 +02:00
Huang Xin 74e32d47b0 release: add linux arch armhf (#1133) 2025-05-13 14:57:27 +08:00
Huang Xin 2dd7cf54e6 refactor: use text selector hook in annotator, closes #1020 (#1131) 2025-05-12 18:00:45 +02:00
Huang Xin a5cbfe97db fix: replace background when attribute changed, closes #1124 (#1126) 2025-05-11 17:07:17 +02:00
Huang Xin da49526f4c fix: now auto orientation follows system settings, closes #1093 and closes #1098 (#1122) 2025-05-11 14:57:05 +02:00
Huang Xin 2e46bc1627 css: get rid of hardcoded font color only when overriding font, closes #1095 (#1120) 2025-05-11 14:34:51 +02:00
Huang Xin 7e542e8694 fix: reorganize TTS control view hierarchy, closes #1080 (#1119) 2025-05-11 12:06:15 +02:00
Huang Xin 2a624ef57a css: only override background color in dark mode, closes #1066 (#1117) 2025-05-11 09:33:03 +02:00
Huang Xin 60ae0c9ac3 feat: show book file size in book details (#1114) 2025-05-10 18:28:08 +02:00
Huang Xin 110b4b37cd ui: various UI fixes and enhancements on mobile platforms (#1113) 2025-05-10 18:18:15 +02:00
Huang Xin f0edbf14a9 fix: padding top of the drag handler with the cutouts, closes #1101 (#1110) 2025-05-10 08:27:40 +02:00
Huang Xin 20669a9cb3 fix: show updating status in the about window, closes #1079 (#1109) 2025-05-10 08:00:25 +02:00
Huang Xin 3756bec66c css: special case for align center and zero text indent (#1105) 2025-05-09 14:40:55 +02:00
Huang Xin 3e1d03dc17 css: override text align and fix hardcoded text intent (#1104) 2025-05-09 14:20:38 +02:00
Huang Xin 4aebbf679f fix: various on styles of UI and footnote visibility (#1099)
* fix: footnote now should be hidden in srcdoc iframes

* fix: eliminate white flash on startup when using dark mode

* feat: rescale UI size with pixel density
2025-05-08 18:21:31 +02:00
Huang Xin 0a562d90ac fix: maintain library state across reader/library navigation, closes #1072 (#1096) 2025-05-08 09:26:58 +02:00
Huang Xin e91a39e8b6 release: version 0.9.40 (#1092) 2025-05-07 20:03:58 +02:00
Huang Xin 2006baac4b revert: unbreak style overriding, closes #1058 and closes #1075 (#1091) 2025-05-07 19:56:14 +02:00
Huang Xin 0d4f61f08a compat: fix insane img dimension might cause layout miscalculation, closes #1073 (#1090) 2025-05-07 18:33:13 +02:00
Huang Xin 0b5f0ede3b fix: close action bar when the footbar is dismissed, closes #1064 (#1086) 2025-05-07 14:44:17 +02:00
Huang Xin 99a146525f fix: transient navigation bar in Android 9, closes #1059 (#1085) 2025-05-07 14:11:46 +02:00
Huang Xin 6080f9e0f3 fix: revert to unlock screen orientation for library page, closes #1063 (#1084) 2025-05-07 13:50:40 +02:00
Huang Xin 6b3d41a4b8 fix: styling links and also the content of links, closes #1057 (#1083) 2025-05-07 12:46:28 +02:00
Huang Xin e403c009c2 fix: avoid glitches when expanding toc sub items, closes #1033 (#1081) 2025-05-07 12:22:42 +02:00
Huang Xin 14bf2bcf8f fix: segment separator now should be more than 8 dash, closes #1056 (#1077) 2025-05-07 09:58:30 +02:00
Huang Xin 7f66af71c8 fix: dialog drag handler layout, closes #1036 (#1076) 2025-05-07 09:27:20 +02:00
Huang Xin cbab201138 doc: format issue templates (#1071) 2025-05-07 06:04:20 +02:00
Huang Xin 0f47d6d393 release: version 0.9.39 (#1055) 2025-05-06 18:58:43 +02:00
Huang Xin 38101cb76d fix: temporarily disable font name with style for windows (#1054) 2025-05-06 18:29:27 +02:00
Huang Xin 8f0982ae33 fix: replace bg color only for non-default theme color, closes #1049 (#1053) 2025-05-06 18:16:00 +02:00
Huang Xin 1c015f5973 feat: add option to open last book on start, closes #328 (#1052) 2025-05-06 17:00:57 +02:00
Huang Xin 4c1af671bb fix: use separate compact margin and gap when header and footer are dismissed, closes #734 (#1047) 2025-05-06 12:38:04 +02:00
Huang Xin b4cda00a91 fix: don't scroll when selection is in current page in TTS, closes #863 (#1046) 2025-05-06 11:09:50 +02:00
Huang Xin c326ad402d fix: calculate vw/vh size from iframe viewport, closes #1027 (#1045) 2025-05-06 09:32:01 +02:00
Huang Xin e03636e0a1 fix: expanding clickable region of TOC expander icons, closes #1033 (#1044) 2025-05-06 06:45:51 +02:00
Huang Xin 9669f1272d fix: more robust txt parser, closes #1042 (#1043)
* refactor: reuse BookCover component in BookDetail dialog

* fix: more robust txt parser, closes #1042
2025-05-06 06:28:25 +02:00
Huang Xin 4094fd86ff fix: padding drag handler on Android, closes #1036 (#1039)
* refactor: load default icon when avatar image fails to load

* fix: padding drag handler on Android, closes #1036
2025-05-05 17:42:57 +02:00
Huang Xin 27df89e261 fix: override text-indent when overriding book layout, closes #1026 (#1038) 2025-05-05 11:58:49 +02:00
Huang Xin ddcee6b3df fix: TTS timeout options should popup now in iOS (#1037) 2025-05-05 11:43:10 +02:00
Huang Xin 5e04f6ae03 feat: support locking screen orientation, closes #860 (#1034) 2025-05-05 10:55:50 +02:00
Huang Xin 6299ea09b7 fix: escape special characters when converting from txt to epub, closes #1018 (#1024) 2025-05-04 13:46:08 +02:00
Huang Xin 4f6f45fe8a fix: don't preview font style for system fonts on Linux, closes #1015 (#1023) 2025-05-04 12:23:01 +02:00
Huang Xin 6b290f09f5 refactor: unify hooks for pagination and continuous scroll (#1021) 2025-05-04 10:58:44 +02:00
Huang Xin c9e08f2c01 fix: more robust continuous scroll, closes #993 (#1017) 2025-05-03 12:07:32 +02:00
Huang Xin 8167f27156 feat: add location info for each entry in table of contents (#1016) 2025-05-02 15:08:20 +02:00
Huang Xin 77c7176526 fix: volume retained when app is in background for iOS (#1014) 2025-05-02 11:15:42 +02:00
Huang Xin a8d657414d fix: more robust txt parser (#1005) 2025-04-30 18:01:47 +02:00
Huang Xin ed368e1205 fix: skip rt elements in annotation and search for Japanese books, closes #904 (#1004) 2025-04-30 11:36:52 +02:00
Huang Xin 7de1bdf824 fix: more robust method to detect language for TTS (#1003) 2025-04-30 09:36:51 +02:00
Huang Xin 141c502c76 fix: handle full CJK Unified Ideographs in search terms, closes #989 (#1002) 2025-04-30 08:52:19 +02:00
Huang Xin b1ea729a54 fix: also overriding anchor color when overriding font in dark mode, closes #995 (#1000) 2025-04-30 06:19:32 +02:00
Huang Xin d3eff19ab9 fix: filter out some non-free fonts on Android and Linux, closes #994 (#999) 2025-04-29 20:02:54 +02:00
Huang Xin 59f0cede39 fix: import system fonts list on Android (#998)
Note that the system fonts might not be available in Android webview
2025-04-29 19:14:03 +02:00
Huang Xin 946d17882b fix: reactivating volume keys interception when resumed from background on iOS (#997) 2025-04-29 18:39:36 +02:00
Huang Xin f9adf789d7 release: version 0.9.38 (#986) 2025-04-28 18:24:29 +02:00
Huang Xin efb8f70d01 fix: inherit book fonts for popup footnotes (#985) 2025-04-28 18:16:56 +02:00
Huang Xin e235cb98d2 fix: intercept the Back key to dismiss popup dialogs on Android, closes #636 (#983) 2025-04-28 16:57:15 +02:00
Huang Xin 4275508ccd feat: add volume keys for page turning, closes #471 (#982) 2025-04-28 12:36:24 +02:00
Huang Xin a424ae8b15 feat: retrieve system fonts on iOS and Android and show font weight variants, closes #949 and closes #557 (#976) 2025-04-26 17:37:04 +02:00
Huang Xin 9303ec8c5f feat: add primary color input in theme editor, closes #963 (#970) 2025-04-26 09:58:20 +02:00
Huang Xin 98faed320a feat: add more English voices for all en locales, closes #966 (#969) 2025-04-26 09:00:09 +02:00
Huang Xin 5bbdc70420 fix: preserve selection anchor when spanning paginated content, closes #873 (#968) 2025-04-26 07:45:51 +02:00
Huang Xin de21182240 fix: don't panic on plugin log initialization error, closes #953 (#961) 2025-04-25 14:42:57 +02:00
Huang Xin 4ae35992a1 compat: auto hide navigation bar also for Android version below 11 (#960) 2025-04-25 10:56:36 +02:00
Huang Xin b91d7b7a61 release: version 0.9.37 (#957) 2025-04-24 21:14:57 +02:00
Huang Xin 7c0d9bf375 fix: popup footnotes for anchors without epub namespace, closes #948 (#956) 2025-04-24 21:03:05 +02:00
Huang Xin ea8ab91ed3 feat: add list view for the bookshelf, closes #542 (#955) 2025-04-24 19:53:09 +02:00
Huang Xin efd415ece2 fix: dismiss status bar when resumed from background on Android (#952) 2025-04-24 11:12:10 +02:00
Huang Xin ec86d68b80 fix: check xdg-mime before registering deeplink in Linux, closes #475 (#951) 2025-04-24 16:19:14 +08:00
Huang Xin 172ab382bc fix: query status bar height on Android, closes #943 (#947) 2025-04-23 14:01:37 +02:00
Huang Xin ad551a04a1 feat: add description from metadata in book details, closes #913 (#946) 2025-04-22 19:01:43 +02:00
Huang Xin 4dc943d23e fix: tts now fallbacks to metadata language of the book, closes #916 (#945) 2025-04-22 18:12:14 +02:00
Huang Xin ee93885c04 fix: sync custom themes from global settings to localstorage, closes #917 (#944) 2025-04-22 14:52:46 +02:00
Huang Xin 14c032aaff feat: add global keyboard shortcut to toggle fullscreen with F11, closes #933 (#942) 2025-04-22 14:13:52 +02:00
Huang Xin 2f619ca745 fix: open book even the library is empty, closes #937 (#941) 2025-04-22 13:56:07 +02:00
Huang Xin 8979827cf5 fix: compat language code for api versions (#940) 2025-04-22 12:38:07 +02:00
Huang Xin ae16eeffa7 fix: prevent scrollToAnchor on focus outside content container, closes #906 (#928) 2025-04-21 14:57:01 +02:00
Huang Xin 8e96ef3e5d fix: hide navigation bar on reader page on Android 11, closes #921 (#927) 2025-04-21 14:49:16 +02:00
Huang Xin 38e24da968 fix: support v1 and v2 of translate API (#919) 2025-04-20 12:30:25 +08:00
Huang Xin 7371efe1d1 release: version 0.9.36 (#915) 2025-04-19 18:07:45 +02:00
Huang Xin 3cb37bca5d feat: add GuanKiapTsingKhai-T in the CJK fonts list, closes #884 (#914) 2025-04-19 18:00:35 +02:00
Huang Xin b9fa204eda fix: show system navigation bar by swiping up from bottom on Android, closes #592 (#912) 2025-04-19 17:02:17 +02:00
Huang Xin cf66665096 feat: immersive UI in reader page on iOS and Android, closes #886 and closes #864 (#911) 2025-04-19 16:43:39 +02:00
Huang Xin 759779a98d fix: links in about window is now clickable in apps (#902) 2025-04-17 11:17:07 +02:00
Huang Xin 68409db8a0 compat: more accessible iframes to support LingKuma and Immersive Translate extension, closes #889 (#901) 2025-04-17 10:36:04 +02:00
Huang Xin 46f0e8e7f6 feat: open files with readest from file manager on iOS (#898) 2025-04-16 17:36:11 +02:00
davimed 6260168caa Fix filename with quotes (#897)
* Escape double quotes in filenames

* Format code
2025-04-16 17:34:15 +02:00
Huang Xin a8430a7be1 feat: open files with readest from file manager on Android, closes #865 (#895) 2025-04-16 07:04:30 +02:00
Huang Xin 7bfaa38746 feat: add download/upload buttons in book detail dialogs (#891) 2025-04-15 06:19:29 +02:00
Huang Xin c5b2feda48 release: version 0.9.35 (#888) 2025-04-14 17:50:39 +02:00
Huang Xin 59f875f590 feat: add books sorting by title and author in the library page, closes #540 (#887) 2025-04-14 17:38:41 +02:00
Huang Xin 8efad90932 feat: in-app updater for Android (#885) 2025-04-14 14:21:58 +02:00
Huang Xin 5c23642ac0 chore: generate Android package signature and download url in update file 2025-04-13 23:59:56 +08:00
Huang Xin ee6500cecb fix: books without cover images now can be synced across devices (#878) 2025-04-13 10:47:50 +02:00
Huang Xin f004d7b3ea release: version 0.9.33 (#875) 2025-04-13 02:31:13 +08:00
Huang Xin f7d53508a6 refactor: new updater dialog (#874) 2025-04-13 02:06:14 +08:00
Huang Xin 53e7f2526c fix: window maximized and fullscreen now don't conflict with each other (#872) 2025-04-12 13:42:36 +02:00
Huang Xin 8f0e2d1642 chore: show webview version info in the about window (#869) 2025-04-12 16:35:54 +08:00
Huang Xin c807f0ccc8 refactor: OAuth flow is now handled via ASWebAuthenticationSession on macOS (#866) 2025-04-12 12:49:41 +08:00
Huang Xin 6ba15319a8 fix: reverse punctuation transform when highlighting (#862) 2025-04-11 06:30:08 +02:00
Huang Xin 3ea3a50baa fix: handle file basename from content provider uri (#861) 2025-04-11 05:18:12 +02:00
Huang Xin ccd467ebcf feat: support separate header/footer visibility for paginated and scrolled modes (#859) 2025-04-11 03:40:26 +02:00
Huang Xin fe25c3e995 feat: support native sign-in-with-apple on macOS (#856) 2025-04-10 14:49:11 +02:00
Huang Xin d4af2d3ef5 feat: also add sans-serif CJK fonts (#853) 2025-04-09 16:03:24 +02:00
Huang Xin 6ff1edb67c fix: no padding top for bookmark ribbon (#851) 2025-04-09 13:03:23 +02:00
Huang Xin 3d2847e427 fix: RTL layout for the bottom configuration panel, closes #849 (#850) 2025-04-09 10:47:15 +02:00
Huang Xin ff4d8af054 release: version 0.9.32 (#848) 2025-04-09 06:01:55 +02:00
Huang Xin 612302f42f feat: add LXGW WenKai TC in CJK fonts list (#847) 2025-04-09 05:57:06 +02:00
Huang Xin a26607a154 fix: don't inline the font menu if more options are available (#846) 2025-04-09 05:32:24 +02:00
Huang Xin 939c7049ed fix: position the underline and squiggly highlight decoration at the middle between text lines (#845) 2025-04-09 05:26:22 +02:00
Huang Xin 0ead07e704 fix: don't reset all font-size and font color when override book style (#842) 2025-04-09 01:55:36 +02:00
Huang Xin f4908c4536 release: version 0.9.31 (#839) 2025-04-08 15:54:34 +02:00
Huang Xin 37951c2ebf feat: add book metadata search in the bookshelf, closes #100 (#838) 2025-04-08 15:37:18 +02:00
Huang Xin 915f222e08 doc: add a troubleshooting section in README (#837) 2025-04-08 18:05:22 +08:00
Huang Xin 9446df1b9c feat: add an option to configure default CJK fonts in CJK environments, closes #780 (#836) 2025-04-08 11:27:59 +02:00
Huang Xin bfcf89b093 fix: toggle the menus on the whole screen when Click-To-Flip is disabled, closes #831 (#834) 2025-04-08 08:09:46 +02:00
Huang Xin b0cf087d78 fix: support opening content URIs by copying to cache when direct access fails, closes #829 (#833) 2025-04-08 07:45:33 +02:00
Huang Xin 5863586766 feat: vertical layout for highlighting tools, closes #787 (#830) 2025-04-07 17:24:14 +02:00
Huang Xin 99182a1f9e feat: add configurable timeout to stop TTS playback automatically, closes #747 (#826) 2025-04-07 12:40:14 +02:00
Huang Xin 2599f40dfb feat: add option to keep window on top, closes #820 (#825) 2025-04-07 08:23:46 +02:00
Huang Xin aa66ba54c5 fix: set proper default theme mode, closes #823 (#824) 2025-04-07 07:16:31 +02:00
Huang Xin 4f0ef01a17 fix: tts now works in background in iOS, closes #547 (#822)
To enable background playback in Android, go to Settings > Apps & Notifications > Readest > Battery > Battery Optimization, and disable battery optimization for Readest.
2025-04-06 18:42:58 +02:00
Huang Xin 267e1656db release: version 0.9.30 (#817) 2025-04-05 11:39:37 +02:00
Huang Xin dcecda2984 fix: cache NativeFile with LRU, closes #797 (#816) 2025-04-05 11:27:07 +02:00
Huang Xin ce88f0229a fix: rounded reader widget only when sidebar is invisible , closes #809 (#815) 2025-04-05 10:08:39 +02:00
Huang Xin 316c82ea52 fix: override link color only when overriding font, closes #812 (#814) 2025-04-05 09:34:29 +02:00
Huang Xin 25be60ce2a chore: bump tauri to 2.4.1 (#813) 2025-04-05 07:40:33 +02:00
Huang Xin 4703eec045 fix: set correct filename in openFile API, closes #803 (#810) 2025-04-05 12:41:26 +08:00
Huang Xin ab22a83332 fix: open default file chooser on Android, closes #798 and closes #799 (#807) 2025-04-04 05:01:12 +02:00
Huang Xin a3d6ebe44d feat: add fixed storage quota for self-hosted instance, closes #805 (#806) 2025-04-04 03:48:43 +02:00
Huang Xin f8804b5d3c fix: revert font family, size and color when overrideFont is set, closes #792 (#804) 2025-04-03 17:15:19 +02:00
Huang Xin ee3785ad51 fix: hack to decode incorrectly encoded url in href (#796) 2025-04-02 07:35:00 +02:00
Huang Xin 5ba40cd2d6 release: version 0.9.29 (#790) 2025-04-01 18:27:15 +02:00
Huang Xin 6131180a31 fix: initiate OAuth using Custom Tabs on Android, closes #361 (#788) 2025-04-01 17:04:17 +02:00
Huang Xin 7ccf3d0632 refactor: read local file with fs plugin for Tauri App, closes #553 and closes #489 (#785) 2025-04-01 14:26:26 +02:00
Huang Xin 39fb7f759e fix: handle preflight on tauri apps (#776) 2025-03-31 03:38:47 +02:00
Huang Xin d89834a53d chore: balance translator load between auth and guest users (#771) 2025-03-30 16:35:13 +02:00
Huang Xin 3c4dd2a143 fix: fix translation api with retry (#770) 2025-03-30 15:59:57 +02:00
Huang Xin 847efb7082 fix: head bar may hide login buttons in auth page (#769) 2025-03-30 11:32:57 +02:00
Huang Xin 01db8f90fa fix: store the filepath for transient import, closes #760 (#768) 2025-03-30 10:27:45 +02:00
Huang Xin e70c46ff02 fix: only replace quote punctuations for vertical layout, closes #761 (#767) 2025-03-30 09:49:22 +02:00
Huang Xin 7cb1998ebb fix: soft keyboard in custom css textarea, closes #762 and closes #763 (#766) 2025-03-30 09:33:27 +02:00
Huang Xin f5fd37f40d release: version 0.9.28 (hotfix) (#759) 2025-03-29 19:46:09 +01:00
Huang Xin 2daee62b80 fix: dismiss highlighting tools for annotation taking (#758) 2025-03-29 19:40:37 +01:00
Huang Xin ffd179bb06 fix: TXT file correctly read and imported on desktop apps, closes #756 (#757) 2025-03-29 19:11:41 +01:00
Huang Xin 4acc0d8e12 feat: replace punctuation marks in vertical reading mode (#754) 2025-03-29 12:42:23 +01:00
Huang Xin a66786444f release: version 0.9.27 (#751) 2025-03-29 08:03:38 +01:00
Huang Xin 01ad18ca57 feat: global fulltext search shortcut with ctrl/cmd+f (#750) 2025-03-29 07:45:41 +01:00
Huang Xin a72c8f2391 refactor: rename plugin safari-auth to native-bridge (#749)
* fix: layout tweaks for mobile

* refactor: rename plugin safari-auth to native-bridge
2025-03-29 07:27:18 +01:00
Huang Xin f90177713a tts: avoid pauses after abbreviations, faster warming up, preferred voice ids (#742) 2025-03-28 13:25:08 +01:00
Huang Xin 041eb1b921 feat: support more languages in deepl translator (#740) 2025-03-28 07:31:34 +01:00
Huang Xin 105302d241 fix: don't crash with some malformed toc data for PDF, closes #518 (#739) 2025-03-28 06:54:32 +01:00
Huang Xin bd9c7b459c fix: avoid stale values in relocate handler, closes #736 (#738) 2025-03-28 06:33:51 +01:00
Huang Xin 1c122752bb fix: update downloadedAt field properly (#737) 2025-03-28 06:03:18 +01:00
Huang Xin 203ef8b986 feat: add reset password page, closes #700 (#731) 2025-03-28 03:52:04 +08:00
Huang Xin ceda806f77 fix: override book writing-mode (#730) 2025-03-27 15:53:35 +01:00
Huang Xin baac9dfa25 fix: unhardcode background color in non-default themes (#729) 2025-03-27 13:05:03 +01:00
Huang Xin 10aace0026 fix: avoid view glitch when initialize view settings, closes #713 (#728) 2025-03-27 12:40:41 +01:00
Huang Xin 3e38896078 feat: add option to swap click-to-flip area, closes #719 (#727) 2025-03-27 11:35:57 +01:00
Huang Xin 0fa53c5f15 fix: should listen single-instance event on Windows to open-with files, closes #724 (#726) 2025-03-27 09:11:03 +01:00
Xi Gou 2b6320b880 refactor: support both r2 and s3 storage (#718) 2025-03-27 07:00:27 +01:00
Huang Xin 7ba938ea25 fix: header layout for auth and user pages (#725) 2025-03-27 05:14:38 +01:00
Huang Xin 6d4ad34e32 release: version 0.9.26 (#723) 2025-03-26 17:44:26 +01:00
Huang Xin 3239ff0a80 feat: add LXGW WenKai to serif fonts list (#722) 2025-03-26 17:28:14 +01:00
Huang Xin 8b7e3c6f79 fix: textarea hidden behind keyboard in fullscreen Android app. closes #653 (#720) 2025-03-26 15:57:35 +01:00
Huang Xin ace4b78420 fix: uncrash scroll prev at start and scroll next at end (#712) 2025-03-26 12:42:39 +01:00
Huang Xin 39ac89fead fix: don't override bg color for img, closes #656 (#711) 2025-03-26 12:23:46 +01:00
Huang Xin 490fe7e70c ui: fix library header padding (#710) 2025-03-26 09:07:38 +01:00
Huang Xin 06a7a8ea82 feat: add option for transient import from file managers, closes #456 (#709) 2025-03-26 07:11:47 +01:00
Huang Xin b7f4a3503f doc: add TXT to supported formats list (#708)
* fix: set max width for annotation toolbar

* doc: add TXT to supported formats list
2025-03-26 11:45:56 +08:00
Huang Xin 835ed443a5 fix: style tweaks for Windows (#707) 2025-03-25 18:58:23 +01:00
Huang Xin 3f3c6983ba fix: also export newly created annotations (#704) 2025-03-25 06:26:11 +01:00
Huang Xin f22ce1c225 fix: access runtime variables in function invoke (#703) 2025-03-25 05:29:22 +01:00
Huang Xin 9990de112f chore: bump updates of next.js and tauri (#702) 2025-03-25 04:46:07 +01:00
Huang Xin ede37757db chore: support deploy in cloudflare with opennext and fix android build 2025-03-25 03:52:04 +08:00
Huang Xin 0c65d44bc9 chore: add android release in github actions (#695) 2025-03-25 01:57:05 +08:00
Huang Xin 07c9813ec1 fix: no background color for section and page info in vertical scroll mode (#694) 2025-03-24 07:13:38 +01:00
Huang Xin 03f0ee1f7d release: version 0.9.25 (#690) 2025-03-23 19:39:39 +01:00
Huang Xin ebc23c4ce3 feat: export annotations in markdown format, closes #152 (#689) 2025-03-23 19:33:05 +01:00
Huang Xin d098487cd1 fix: add metadata parsed from cbz zip comment, closes #639 (#688) 2025-03-23 18:18:08 +01:00
Huang Xin 14f2db730a fix: add option to override justify style in books, closes #671 (#687) 2025-03-23 16:59:45 +01:00
Huang Xin a79f301165 feat: add language preference options in settings (#686) 2025-03-23 16:39:58 +01:00
Huang Xin 05d086352f chore: add f-droid metadata (#682) 2025-03-23 08:59:34 +01:00
Huang Xin ef864a32c2 feat: action bar at the bottom on mobile devices (#681) 2025-03-23 08:56:57 +01:00
Huang Xin 0d93a13407 fix: format footer in popup window (#665) 2025-03-21 05:52:56 +01:00
Huang Xin ab0208b7ab fix: now img in paragraph won't get indented and default to override book layout (#657) 2025-03-20 17:10:26 +01:00
Huang Xin c52f3c0430 feat: support importing TXT files, closes #308 (#655) 2025-03-20 15:00:30 +01:00
Huang Xin accf6fb5c7 feat: snap dialog to preview effects of book settings adjustments (#646) 2025-03-19 14:23:52 +01:00
Huang Xin 18c2ce1274 fix: various fixes on layout settings and styles, closes #637 (#644) 2025-03-19 10:14:02 +01:00
Huang Xin 9a104b918c fix: dismiss popup footnotes on window resize, also closes #634 and closes #635 (#638) 2025-03-19 04:22:15 +01:00
805 changed files with 124353 additions and 17206 deletions
+33
View File
@@ -0,0 +1,33 @@
# Dependencies
node_modules
**/node_modules
# Rust build artifacts
target
**/target
# Git
.git
.gitignore
# Build outputs
dist
**/dist
.next
**/.next
out
**/out
# IDE
.idea
.vscode
*.swp
# OS files
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
+15
View File
@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: ['readest']
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+8 -4
View File
@@ -7,13 +7,17 @@ assignees: ''
---
**Does your feature request involve difficulty for you to complete a task? Please describe.**
A clear and concise description of what the problem is. Ex. I think it takes too many steps to [...]
> A clear and concise description of what the problem is. Ex. I think it takes too many steps to [...]
**Describe the solution you'd like**
A clear and concise description of what you'd like to happen.
> A clear and concise description of what you'd like to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
> A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any additional context or screenshots about the feature request here.
> Add any additional context or screenshots about the feature request here.
+10 -19
View File
@@ -11,22 +11,16 @@ assignees: ''
A clear and concise description of what the current behavior is.
Please also add **screenshots** of the existing application.
**Example:**
```
In DarkMode, when scrollbar are displayed (for example on Companies page, with enough companies in the list), we see a blank square in the bottom right corner
[screenshot]
```
> **Example:**
> In DarkMode, when scrollbar are displayed (for example on Companies page, with enough companies in the list), we see a blank square in the bottom right corner
> [screenshot]
## Expected behavior
A clear and concise description of what the expected behavior is.
**Example:**
```
The blank square should be transparent (invisible)
```
> **Example:**
> The blank square should be transparent (invisible)
## Technical inputs
@@ -34,11 +28,8 @@ Operating System:
Readest Version:
**Example:**
```
Operating System: macOS 14.3.1
Readest Version: 0.9.0
We are displaying custom scrollbars that disappear when the user is not scrolling. See ScrollWrapper.
Probably fixable with CSS
```
> **Example:**
> Operating System: Android 14 (WebView 135.0)
> Readest Version: 0.9.0
> We are displaying custom scrollbars that disappear when the user is not scrolling. See ScrollWrapper.
> Probably fixable with CSS
+1 -1
View File
@@ -8,6 +8,6 @@ updates:
groups:
github-actions:
patterns:
- "*" # Group all Actions updates into a single larger pull request
- '*' # Group all Actions updates into a single larger pull request
schedule:
interval: weekly
+43 -9
View File
@@ -11,7 +11,7 @@ jobs:
env:
RUSTFLAGS: '-C target-cpu=skylake'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: Install minimal stable with clippy and rustfmt
@@ -19,6 +19,7 @@ jobs:
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Install system dependencies
run: |
sudo apt-get update
@@ -28,32 +29,65 @@ jobs:
run: cargo fmt --check
- name: Clippy Check
working-directory: apps/readest-app/src-tauri
run: cargo clippy -- -D warnings
run: cargo clippy -p Readest --no-deps -- -D warnings
build_web_app:
runs-on: ubuntu-latest
strategy:
matrix:
config:
- platform: 'web'
- platform: 'tauri'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: 'true'
- name: setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.15.1
- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
- name: cache Next.js build
uses: actions/cache@v5
with:
path: apps/readest-app/.next/cache
key: nextjs-${{ matrix.config.platform }}-${{ github.sha }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
nextjs-${{ matrix.config.platform }}-${{ github.sha }}-
nextjs-${{ matrix.config.platform }}-
- name: install Dependencies
working-directory: apps/readest-app
run: |
pnpm install && pnpm setup-pdfjs
pnpm install && pnpm setup-vendors
- name: build the web App
- name: run format check
run: |
pnpm format:check || (pnpm format && git diff && exit 1)
- name: run tests
working-directory: apps/readest-app
run: |
pnpm build-web
pnpm test -- --watch=false
- name: run lint
working-directory: apps/readest-app
run: |
pnpm lint
- name: build the web App
if: matrix.config.platform == 'web'
working-directory: apps/readest-app
run: |
pnpm build-web && pnpm check:all
- name: build the Tauri App
if: matrix.config.platform == 'tauri'
working-directory: apps/readest-app
run: |
pnpm build && pnpm check:all
+249 -54
View File
@@ -17,14 +17,14 @@ jobs:
release_version: ${{ steps.get-release-notes.outputs.release_version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
- name: get version
run: echo "PACKAGE_VERSION=$(node -p "require('./apps/readest-app/package.json').version")" >> $GITHUB_ENV
- name: get release
id: get-release
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const { data } = await github.rest.repos.getLatestRelease({
@@ -35,7 +35,7 @@ jobs:
core.setOutput('release_tag', data.tag_name);
- name: get release notes
id: get-release-notes
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
@@ -48,6 +48,63 @@ jobs:
core.setOutput('release_version', version);
core.setOutput('release_note', releaseNote);
update-release:
permissions:
contents: write
runs-on: ubuntu-latest
needs: get-release
steps:
- name: update release
id: update-release
uses: actions/github-script@v8
env:
release_id: ${{ needs.get-release.outputs.release_id }}
release_tag: ${{ needs.get-release.outputs.release_tag }}
release_note: ${{ needs.get-release.outputs.release_note }}
with:
script: |
const { data } = await github.rest.repos.generateReleaseNotes({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: process.env.release_tag,
})
const notes = process.env.release_note.split(/\d+\.\s/).filter(Boolean);
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.release_id,
body: body,
draft: false,
prerelease: false
})
build-koreader-plugin:
needs: get-release
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: create KOReader plugin zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=${{ needs.get-release.outputs.release_version }}
plugin_zip="Readest-${version}-1.koplugin.zip"
meta_file="apps/readest.koplugin/_meta.lua"
perl -i -pe "s/^}/ version = \"${version}\",\n}/" "${meta_file}"
cd apps
zip -r ../${plugin_zip} readest.koplugin
cd ..
echo "Uploading ${plugin_zip} to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} ${plugin_zip} --clobber
build-tauri:
needs: get-release
permissions:
@@ -56,49 +113,75 @@ jobs:
fail-fast: false
matrix:
config:
- os: ubuntu-latest
release: android
rust_target: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
- os: ubuntu-22.04
release: linux
arch: x86_64
rust_target: x86_64-unknown-linux-gnu
- os: ubuntu-22.04-arm
release: linux
arch: aarch64
rust_target: aarch64-unknown-linux-gnu
- os: ubuntu-22.04-arm
release: linux
arch: armhf
rust_target: arm-unknown-linux-gnueabihf
args: '--target arm-unknown-linux-gnueabihf --bundles deb'
- os: macos-latest
release: macos
arch: aarch64
rust_target: x86_64-apple-darwin,aarch64-apple-darwin
args: '--target universal-apple-darwin'
- os: windows-latest
release: windows
arch: x86_64
rust_target: x86_64-pc-windows-msvc
args: '--target x86_64-pc-windows-msvc'
args: '--target x86_64-pc-windows-msvc --bundles nsis'
- os: windows-latest
release: windows
arch: aarch64
rust_target: aarch64-pc-windows-msvc
args: '--target aarch64-pc-windows-msvc --bundles nsis'
runs-on: ${{ matrix.config.os }}
timeout-minutes: 30
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: initialize git submodules
run: git submodule update --init --recursive
- name: setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9.14.4
- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
- name: setup Java (for Android build only)
if: matrix.config.release == 'android'
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '17'
- name: setup Android SDK (for Android build only)
if: matrix.config.release == 'android'
uses: android-actions/setup-android@v3
- name: install NDK (for Android build only)
if: matrix.config.release == 'android'
run: sdkmanager "ndk;28.2.13676358"
- name: install dependencies
run: pnpm install
- name: copy pdfjs-dist to public directory
run: pnpm --filter @readest/readest-app setup-pdfjs
- name: copy pdfjs-dist and simplecc-dist to public directory
run: pnpm --filter @readest/readest-app setup-vendors
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
@@ -107,7 +190,24 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.config.os }}-cargo-${{ hashFiles('Cargo.lock') }}
key: ${{ matrix.config.os }}-${{ matrix.config.release }}-${{ matrix.config.arch }}-cargo
- name: install dependencies (ubuntu only)
if: contains(matrix.config.os, 'ubuntu') && matrix.config.release != 'android' && matrix.config.arch != 'armhf'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
- name: install dependencies (ubuntu only - armhf specific)
if: contains(matrix.config.os, 'ubuntu') && matrix.config.arch == 'armhf'
run: |
sudo dpkg --add-architecture armhf
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev:armhf libgtk-3-dev:armhf libwebkit2gtk-4.1-dev:armhf libappindicator3-dev:armhf librsvg2-dev:armhf gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
echo 'PKG_CONFIG_ALLOW_CROSS=1' >> $GITHUB_ENV
echo 'PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig' >> $GITHUB_ENV
echo 'PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf' >> $GITHUB_ENV
echo 'CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc' >> $GITHUB_ENV
- name: create .env.local file for Next.js
run: |
@@ -116,19 +216,83 @@ jobs:
echo "NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}" >> .env.local
echo "NEXT_PUBLIC_SUPABASE_ANON_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}" >> .env.local
echo "NEXT_PUBLIC_APP_PLATFORM=tauri" >> .env.local
cp .env.local apps/readest-app/.env.local
- name: copy .env.local to apps/readest-app
run: cp .env.local apps/readest-app/.env.local
- name: install dependencies (ubuntu only)
if: contains(matrix.config.os, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
- uses: tauri-apps/tauri-action@v0
- name: build and upload Android apks
if: matrix.config.release == 'android'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/28.2.13676358
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
cd apps/readest-app/
rm -rf src-tauri/gen/android
pnpm tauri android init
pnpm tauri icon ../../data/icons/readest-book.png
git checkout .
pushd src-tauri/gen/android
echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" > keystore.properties
echo "password=${{ secrets.ANDROID_KEY_PASSWORD }}" >> keystore.properties
base64 -d <<< "${{ secrets.ANDROID_KEY_BASE64 }}" > $RUNNER_TEMP/keystore.jks
echo "storeFile=$RUNNER_TEMP/keystore.jks" >> keystore.properties
popd
version=${{ needs.get-release.outputs.release_version }}
apk_path=src-tauri/gen/android/app/build/outputs/apk/universal/release
universial_apk=Readest_${version}_universal.apk
arm64_apk=Readest_${version}_arm64.apk
pnpm tauri android build
cp ${apk_path}/app-universal-release.apk $universial_apk
pnpm tauri android build -t aarch64
cp ${apk_path}/app-universal-release.apk $arm64_apk
echo "Uploading $universial_apk to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk --clobber
echo "Uploading $arm64_apk to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk --clobber
echo "Uploading signatures to GitHub release"
pnpm tauri signer sign $universial_apk
pnpm tauri signer sign $arm64_apk
gh release upload ${{ needs.get-release.outputs.release_tag }} $universial_apk.sig --clobber
gh release upload ${{ needs.get-release.outputs.release_tag }} $arm64_apk.sig --clobber
- name: download and update latest.json for Android release
if: matrix.config.release == 'android'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd apps/readest-app/
curl -sL https://github.com/readest/readest/releases/latest/download/latest.json -o latest.json
version=${{ needs.get-release.outputs.release_version }}
universial_apk_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/Readest_${version}_universal.apk"
arm64_apk_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/Readest_${version}_arm64.apk"
universial_sig=$(cat Readest_${version}_universal.apk.sig)
arm64_sig=$(cat Readest_${version}_arm64.apk.sig)
jq --arg url "$universial_apk_url" \
--arg sig "$universial_sig" \
'.platforms["android-universal"] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
jq --arg url "$arm64_apk_url" \
--arg sig "$arm64_sig" \
'.platforms["android-arm64"] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
echo "Uploading updated latest.json to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
- name: Override tauri-cli with custom AppImage format (Linux)
if: matrix.config.release == 'linux'
run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch feat/truly-portable-appimage --force
- uses: tauri-apps/tauri-action@v0
if: matrix.config.release != 'android'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: 'true'
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -144,12 +308,29 @@ jobs:
releaseBody: ${{ needs.get-release.outputs.release_note }}
args: ${{ matrix.config.args || '' }}
- name: upload portable binaries (Windows only)
- name: upload release notes to GitHub release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Uploading release notes to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} apps/readest-app/release-notes.json --clobber
- name: build and upload portable binaries (Windows only)
if: matrix.config.os == 'windows-latest'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
shell: bash
run: |
echo "Building Portable Binaries"
pushd apps/readest-app/
echo "NEXT_PUBLIC_PORTABLE_APP=true" >> .env.local
pnpm tauri build ${{ matrix.config.args }}
popd
echo "Uploading Portable Binaries"
arch=${{ matrix.config.arch }}
version=${{ needs.get-release.outputs.release_version }}
@@ -172,35 +353,49 @@ jobs:
echo "Uploading $bin_file to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file --clobber
update-release:
permissions:
contents: write
runs-on: ubuntu-latest
needs: [get-release, build-tauri]
echo "Signing portable binary"
pushd apps/readest-app/
pnpm tauri signer sign "../../$bin_file"
popd
echo "Uploading signature to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} $bin_file.sig --clobber
steps:
- name: update release
id: update-release
uses: actions/github-script@v7
- name: download and update latest.json for Windows portable release
if: matrix.config.os == 'windows-latest'
env:
release_id: ${{ needs.get-release.outputs.release_id }}
release_tag: ${{ needs.get-release.outputs.release_tag }}
release_note: ${{ needs.get-release.outputs.release_note }}
with:
script: |
const { data } = await github.rest.repos.generateReleaseNotes({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: process.env.release_tag,
})
const notes = process.env.release_note.split(/(?:\d\.\s)/).filter(Boolean);
const formattedNotes = notes.map(note => `* ${note.trim()}`).join("\n");
const body = `## Release Highlight\n${formattedNotes}\n\n${data.body}`;
github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.release_id,
body: body,
draft: false,
prerelease: false
})
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
curl -sL https://github.com/readest/readest/releases/latest/download/latest.json -o latest.json
version=${{ needs.get-release.outputs.release_version }}
arch=${{ matrix.config.arch }}
if [ "$arch" = "x86_64" ]; then
bin_file="Readest_${version}_x64-portable.exe"
platform_key="windows-x86_64-portable"
elif [ "$arch" = "aarch64" ]; then
bin_file="Readest_${version}_arm64-portable.exe"
platform_key="windows-aarch64-portable"
else
echo "Unknown architecture: $arch"
exit 1
fi
portable_url="https://github.com/readest/readest/releases/download/${{ needs.get-release.outputs.release_tag }}/$bin_file"
portable_sig=$(cat $bin_file.sig)
jq --arg url "$portable_url" \
--arg sig "$portable_sig" \
--arg key "$platform_key" \
'.platforms[$key] = {signature: $sig, url: $url}' latest.json > tmp.$$.json && mv tmp.$$.json latest.json
echo "Uploading updated latest.json to GitHub release"
gh release upload ${{ needs.get-release.outputs.release_tag }} latest.json --clobber
upload-to-r2:
needs: [get-release, build-tauri]
uses: ./.github/workflows/upload-to-r2.yml
with:
tag: ${{ needs.get-release.outputs.release_tag }}
secrets: inherit
+18
View File
@@ -0,0 +1,18 @@
name: Retry workflow
on:
workflow_dispatch:
inputs:
run_id:
required: true
jobs:
rerun:
runs-on: ubuntu-latest
steps:
- name: rerun ${{ inputs.run_id }}
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh run watch ${{ inputs.run_id }} > /dev/null 2>&1
gh run rerun ${{ inputs.run_id }} --failed
+77
View File
@@ -0,0 +1,77 @@
name: Upload Release Assets to R2
on:
workflow_call:
inputs:
tag:
required: true
type: string
workflow_dispatch:
inputs:
tag:
description: 'Release tag name (e.g., v1.2.3)'
required: true
type: string
jobs:
upload-to-r2:
runs-on: ubuntu-latest
timeout-minutes: 3
strategy:
fail-fast: false
env:
RELEASE_R2_BUCKET: readest-releases
RELEASE_R2_ACCOUNT_ID: ${{ secrets.RELEASE_R2_ACCOUNT_ID }}
RELEASE_R2_ACCESS_KEY_ID: ${{ secrets.RELEASE_R2_ACCESS_KEY_ID }}
RELEASE_R2_SECRET_ACCESS_KEY: ${{ secrets.RELEASE_R2_SECRET_ACCESS_KEY }}
steps:
- name: Download release assets
run: |
gh release download "${{ inputs.tag }}" --repo readest/readest --dir ./release-assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rclone
run: curl https://rclone.org/install.sh | sudo bash
- name: Configure rclone
run: |
mkdir -p ~/.config/rclone
cat > ~/.config/rclone/rclone.conf <<EOF
[r2]
type = s3
provider = Cloudflare
access_key_id = $RELEASE_R2_ACCESS_KEY_ID
secret_access_key = $RELEASE_R2_SECRET_ACCESS_KEY
endpoint = https://${RELEASE_R2_ACCOUNT_ID}.r2.cloudflarestorage.com
EOF
- name: Modify latest.json download URLs
run: |
GITHUB_BASE_URL="https://github.com/readest/readest/releases/download"
READEST_BASE_URL="https://download.readest.com/releases"
sed -i "s#${GITHUB_BASE_URL}#${READEST_BASE_URL}#g" ./release-assets/latest.json
- name: Upload to R2
run: |
mkdir releases
mv ./release-assets/latest.json releases
mv ./release-assets/release-notes.json releases
rclone copy ./release-assets r2:${RELEASE_R2_BUCKET}/releases/${{ inputs.tag }}/
rclone copy ./releases r2:${RELEASE_R2_BUCKET}/releases/
- name: Upload successful
if: success()
run: echo "Upload completed successfully"
retry-on-failure:
if: failure() && fromJSON(github.run_attempt) < 3
needs: [upload-to-r2]
runs-on: ubuntu-latest
steps:
- env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: gh workflow run retry-workflow.yml -F run_id=${{ github.run_id }}
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: 'true'
- uses: amondnet/vercel-action@v25
+8
View File
@@ -38,3 +38,11 @@ next-env.d.ts
# Rust build
target
fastlane/report.xml
*.koplugin.zip
# nix
result*
.playwright-mcp/
+7 -1
View File
@@ -3,4 +3,10 @@
url = https://github.com/readest/foliate-js.git
[submodule "packages/tauri"]
path = packages/tauri
url = https://github.com/tauri-apps/tauri.git
url = https://github.com/readest/tauri.git
[submodule "packages/tauri-plugins"]
path = packages/tauri-plugins
url = https://github.com/readest/tauri-plugins-workspace.git
[submodule "packages/simplecc-wasm"]
path = packages/simplecc-wasm
url = https://github.com/readest/simplecc-wasm.git
+34 -1
View File
@@ -1 +1,34 @@
packages/foliate-js/
# Dependencies
node_modules
pnpm-lock.yaml
# Build Artifacts (Web & Rust)
.next
.open-next
.build
.tauri
out
build
dist
target
fastlane
.wrangler
# Autogenerated Tauri files
gen
**/autogenerated
**/schemas
# Submodules (External Repos)
packages
# Vendored Assets (Generated/External Code)
apps/readest-app/public/*.js
apps/readest-app/public/vendor
# Environment & Editor
.env
.env.*
.vscode
.idea
*.log
+1
View File
@@ -5,5 +5,6 @@
"tabWidth": 2,
"singleQuote": true,
"jsxSingleQuote": true,
"endOfLine": "lf",
"plugins": ["prettier-plugin-tailwindcss"]
}
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"ms-vscode.vscode-typescript-next",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"rust-lang.rust-analyzer"
]
}
+25 -1
View File
@@ -4,4 +4,28 @@
"packages/tauri/Cargo.toml",
"apps/readest-app/src-tauri/Cargo.toml"
],
}
// "editor.formatOnSave": true, // uncomment to add format on save
"typescript.inlayHints.parameterNames.enabled": "all",
"typescript.inlayHints.variableTypes.enabled": true,
"typescript.inlayHints.propertyDeclarationTypes.enabled": true,
"typescript.inlayHints.functionLikeReturnTypes.enabled": true,
"typescript.inlayHints.enumMemberValues.enabled": true,
"javascript.validate.enable": false,
"javascript.format.enable": false,
"typescript.format.enable": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.requireConfig": true,
"prettier.documentSelectors": ["**/*.{js,jsx,ts,tsx,css,json,md,html,yml}"]
}
+4 -4
View File
@@ -3,14 +3,14 @@
When contributing to `Readest`, whether on GitHub or in other community spaces:
- Be respectful, civil, and open-minded.
- Before opening a new pull request, try searching through the [issue tracker](https://github.com/chrox/readest/issues) for known issues or fixes.
- Before opening a new pull request, try searching through the [issue tracker](https://github.com/readest/readest/issues) for known issues or fixes.
- If you want to make code changes based on your personal opinion(s), make sure you open an issue first describing the changes you want to make, and open a pull request only when your suggestions get approved by maintainers.
## How to Contribute
### Prerequisites
In order to not waste your time implementing a change that has already been declined, or is generally not needed, start by [opening an issue](https://github.com/chrox/readest/issues/new/choose) describing the problem you would like to solve.
In order to not waste your time implementing a change that has already been declined, or is generally not needed, start by [opening an issue](https://github.com/readest/readest/issues/new/choose) describing the problem you would like to solve.
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.
@@ -43,8 +43,8 @@ git submodule update --init --recursive
```bash
# might need to rerun this when code is updated
pnpm install
# copy pdfjs-dist to Next.js public directory
pnpm --filter @readest/readest-app setup-pdfjs
# copy vendors dist libs to public directory
pnpm --filter @readest/readest-app setup-vendors
```
#### 3. Verify Dependencies Installation
Generated
+2335 -2044
View File
File diff suppressed because it is too large Load Diff
+40 -1
View File
@@ -1,3 +1,42 @@
[workspace]
members = ["apps/readest-app/src-tauri"]
members = [
"apps/readest-app/src-tauri",
"packages/tauri/crates/tauri",
"packages/tauri/crates/tauri-utils",
"packages/tauri/crates/tauri-build",
"packages/tauri-plugins/plugins/deep-link",
"packages/tauri-plugins/plugins/single-instance"
]
resolver = "2"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
log = "0.4"
tauri = { version = "2", default-features = false }
tauri-build = "2"
tauri-plugin = "2"
tauri-utils = "2"
schemars = "0.8"
serde_json = "1"
thiserror = "2"
glob = "0.3"
zbus = "5.9"
dunce = "1"
url = "2"
[workspace.package]
authors = ["Bilingify LLC"]
homepage = "https://readest.com"
license = "AGPL-3.0"
repository = "https://github.com/readest/readest"
categories = []
edition = "2021"
rust-version = "1.77.2"
[patch.crates-io]
tauri = { path = "packages/tauri/crates/tauri" }
tauri-utils = { path = "packages/tauri/crates/tauri-utils" }
tauri-build = { path = "packages/tauri/crates/tauri-build" }
tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" }
tauri-plugin-single-instance = { path = "packages/tauri-plugins/plugins/single-instance" }
+1 -19
View File
@@ -5,31 +5,13 @@ ENV PATH="${PATH}:${PNPM_HOME}"
RUN npm install --global pnpm
# Install necessary packages
RUN apt update -y && apt install -y --no-install-recommends \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
ca-certificates \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Rust and Cargo
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
COPY . /app
WORKDIR /app
RUN pnpm install
RUN pnpm --filter @readest/readest-app setup-pdfjs
RUN pnpm --filter @readest/readest-app setup-vendors
WORKDIR /app/apps/readest-app
+117 -42
View File
@@ -5,18 +5,22 @@
<h1>Readest</h1>
<br>
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 15](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
[Readest][link-website] is an open-source ebook reader designed for immersive and deep reading experiences. Built as a modern rewrite of [Foliate](https://github.com/johnfactotum/foliate), it leverages [Next.js 16](https://github.com/vercel/next.js) and [Tauri v2](https://github.com/tauri-apps/tauri) to deliver a smooth, cross-platform experience across macOS, Windows, Linux, Android, iOS, and the Web.
[![Website][badge-website]][link-website]
[![Web App][badge-web-app]][link-web-readest]
[![OS][badge-platforms]][link-website]
<br>
[![][badge-hellogithub]][link-hellogithub]
[![][badge-discord]][link-discord]
[![Discord][badge-discord]][link-discord]
[![Reddit][badge-reddit]][link-reddit]
[![AGPL Licence][badge-license]](LICENSE)
[![Language Coverage][badge-language-coverage]][link-locales]
[![Donate][badge-donate]][link-donate]
[![Latest release][badge-release]][link-gh-releases]
[![Last commit][badge-last-commit]][link-gh-commits]
[![Commits][badge-commit-activity]][link-gh-pulse]
[![][badge-hellogithub]][link-hellogithub]
[![Ask DeepWiki][badge-deepwiki]][link-deepwiki]
</div>
@@ -26,7 +30,8 @@
<a href="#screenshots">Screenshots</a> •
<a href="#downloads">Downloads</a> •
<a href="#getting-started">Getting Started</a> •
<a href="#contributors">Contributors</a> •
<a href="#troubleshooting">Troubleshooting</a> •
<a href="#support">Support</a> •
<a href="#license">License</a>
</p>
@@ -40,37 +45,38 @@
<div align="left">✅ Implemented</div>
| **Feature** | **Description** | **Status** |
| --------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------- |
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, PDF (experimental) | ✅ |
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience. | ✅ |
| **Excerpt Text for Note-Taking** | Easily excerpt text from books for detailed notes and analysis. | ✅ |
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
| **Translate with DeepL** | Translate selected text instantly using DeepL for accurate translations. | ✅ |
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
| **Text-to-Speech (TTS) Support** | Enable text-to-speech functionality for a more accessible reading experience. | ✅ |
| **Feature** | **Description** | **Status** |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------- |
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF (experimental) | ✅ |
| **Scroll/Page View Modes** | Switch between scrolling or paginated reading modes. | ✅ |
| **Full-Text Search** | Search across the entire book to find relevant sections. | ✅ |
| **Annotations and Highlighting** | Add highlights, bookmarks, and notes to enhance your reading experience. | ✅ |
| **Excerpt Text for Note-Taking** | Easily excerpt text from books for detailed notes and analysis. | ✅ |
| **Dictionary/Wikipedia Lookup** | Instantly look up words and terms when reading. | ✅ |
| **[Parallel Read][link-parallel-read]** | Read two books or documents simultaneously in a split-screen view. | ✅ |
| **Customize Font and Layout** | Adjust font, layout, theme mode, and theme colors for a personalized experience. | ✅ |
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
| **Accessibility** | Provides full keyboard navigation and supports for screen readers such as VoiceOver, TalkBack, NVDA, and Orca. | ✅ |
| **Translate with DeepL and Yandex** | From a single sentence to the entire book—translate instantly. | ✅ |
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
## Planned Features
<div align="left">🛠 Building</div>
<div align="left">🔄 Planned</div>
| **Feature** | **Description** | **Priority** |
| ------------------------------- | ------------------------------------------------------------------------------------------ | ------------ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | 🛠 |
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
| **Sync with Koreader** | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🔄 |
| **Keyboard Navigation** | Implement vimium-style keybindings for book navigation. | 🔄 |
| **Support OPDS/Calibre** | Integrate OPDS/Calibre to access online libraries and catalogs. | 🔄 |
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 |
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
| **Feature** | **Description** | **Priority** |
| ------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------ |
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | 🛠 |
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🔄 |
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
Stay tuned for continuous improvements and updates! Contributions and suggestions are always welcome—let's build the ultimate reading experience together. 😊
@@ -78,25 +84,35 @@ Stay tuned for continuous improvements and updates! Contributions and suggestion
![Annotations](./data/screenshots/annotations.png)
![DeepL](./data/screenshots/deepl.png)
![TTS](./data/screenshots/tts_speak_aloud.png)
![DeepL](./data/screenshots/deepl.png)
![Footnote](./data/screenshots/footnote_popover.png)
![Wikipedia](./data/screenshots/wikipedia_vertical.png)
![Themeing Dark Mode](./data/screenshots/theming_dark_mode.png)
![Theming Dark Mode](./data/screenshots/theming_dark_mode.png)
---
## Downloads
The Readest app is available for download! 🥳 🚀
### Mobile Apps
- macOS / iOS / iPadOS : Search for "Readest" on the [App Store][link-appstore], also available on TestFlight for beta test (send your Apple ID to readestapp@gmail.com to request access).
- Windows / Linux / Android: Visit [https://readest.com][link-website] or the [Releases on GitHub][link-gh-releases].
- Web: Visit [https://web.readest.com][link-web-readest].
<div align="center">
<a href="https://apps.apple.com/app/id6738622779">
<img alt="Download on the App Store" src="https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg" style="height: 50px;" /></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://play.google.com/store/apps/details?id=com.bilingify.readest">
<img alt="Get it on Google Play" src="https://upload.wikimedia.org/wikipedia/commons/7/78/Google_Play_Store_badge_EN.svg" style="height: 50px;" /></a>
</div>
### Platform-Specific Downloads
- macOS / iOS / iPadOS : Search and install **Readest** on the [App Store][link-appstore], _also_ available on TestFlight for beta test (send your Apple ID to <readestapp@gmail.com> to request access).
- Windows / Linux / Android: Visit and download **Readest** at [https://readest.com][link-website] or the [Releases on GitHub][link-gh-releases].
- Linux users can also install [Readest on Flathub][link-flathub].
- Web: Visit and use **Readest for Web** at [https://web.readest.com][link-web-readest].
## Requirements
@@ -121,16 +137,16 @@ To get started with Readest, follow these steps to clone and build the project.
```bash
git clone https://github.com/readest/readest.git
cd readest
git submodule update --init --recursive
```
### 2. Install Dependencies
```bash
# might need to rerun this when code is updated
git submodule update --init --recursive
pnpm install
# copy pdfjs-dist to Next.js public directory
pnpm --filter @readest/readest-app setup-pdfjs
# copy vendors dist libs to public directory
pnpm --filter @readest/readest-app setup-vendors
```
### 3. Verify Dependencies Installation
@@ -152,6 +168,8 @@ For Windows targets, “Build Tools for Visual Studio 2022” (or a higher editi
pnpm tauri dev
# or start development for the Web app
pnpm dev-web
# preview with OpenNext build for the Web app
pnpm preview
```
For Android:
@@ -199,6 +217,39 @@ nix develop ./ops#android # enter a dev shell for the android app
Please check the [wiki][link-gh-wiki] of this project for more information on development.
## Troubleshooting
### 1. Readest Wont Launch on Windows (Missing Edge WebView2 Runtime)
**Symptom**
- When you double-click readest.exe, nothing happens. No window appears, and Task Manager does not show the process.
- This can affect both the standard installer and the portable version.
**Cause**
- Microsoft Edge WebView2 Runtime is either missing, outdated, or improperly installed on your system. Readest depends on WebView2 to render the interface on Windows.
**How to Fix**
1. Check if WebView2 is installed
- Open “Add or Remove Programs” (a.k.a. Apps & features) on Windows. Look for “Microsoft Edge WebView2 Runtime.”
2. Install or Update WebView2
- Download the WebView2 Runtime directly from Microsoft: [link](https://developer.microsoft.com/en-us/microsoft-edge/webview2?form=MA13LH).
- If you prefer an offline installer, download the offline package and run it as an Administrator.
3. Re-run Readest
- After installing/updating WebView2, launch readest.exe again.
- If you still encounter problems, reboot your PC and try again.
**Additional Tips**
- If reinstalling once doesnt work, uninstall Edge WebView2 completely, then reinstall it with Administrator privileges.
- Verify your Windows installation has the latest updates from Microsoft.
**Still Stuck?**
- See Issue [readest/readest#358](https://github.com/readest/readest/issues/358) for further details, or head over to our [Discord][link-discord] server and open a support discussion with detailed logs of your environment and the steps youve taken.
## Contributors
Readest is open-source, and contributions are welcome! Feel free to open issues, suggest features, or submit pull requests. Please **review our [contributing guidelines](CONTRIBUTING.md) before you start**. We also welcome you to join our [Discord][link-discord] community for either support or contributing guidance.
@@ -209,6 +260,18 @@ Readest is open-source, and contributions are welcome! Feel free to open issues,
</p>
</a>
## Support
If Readest has been useful to you, consider supporting its development. You can [become a sponsor on GitHub](https://github.com/sponsors/readest), [donate via Stripe](https://donate.stripe.com/4gMcN5aZdcE52kW3TFgjC01), or [contribute with crypto](https://donate.readest.com). Your contribution helps us squash bugs faster, improve performance, and keep building great features.
### Sponsors
<p align="center">
<a title="Browser testing via TestMu AI" href="https://www.testmu.ai?utm_source=readest&utm_medium=sponsor" target="_blank">
<img src="https://raw.githubusercontent.com/readest/readest/refs/heads/main/data/sponsors/testmu-ai-logo.png" style="vertical-align: middle;" width="250" />
</a>
</p>
## License
Readest is free software: you can redistribute it and/or modify it under the terms of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See the [LICENSE](LICENSE) file for details.
@@ -219,9 +282,11 @@ The following libraries and frameworks are used in this software:
- [zip.js](https://github.com/gildas-lormeau/zip.js), which is licensed under the BSD-3-Clause license.
- [fflate](https://github.com/101arrowz/fflate), which is MIT licensed.
- [PDF.js](https://github.com/mozilla/pdf.js), which is licensed under Apache License 2.0.
- [daisyUI](https://github.com/saadeghi/daisyui), which is MIT licensed.
- [marked](https://github.com/markedjs/marked), which is MIT licensed.
- [next.js](https://github.com/vercel/next.js), which is MIT licensed.
- [react](https://github.com/facebook/react), which is MIT licensed.
- [react-icons](https://github.com/react-icons/react-icons), which has various open-source licenses.
- [react](https://github.com/facebook/react), which is MIT licensed.
- [tauri](https://github.com/tauri-apps/tauri), which is MIT licensed.
The following fonts are utilized in this software, either bundled within the application or provided through web fonts:
@@ -237,12 +302,18 @@ The following fonts are utilized in this software, either bundled within the app
[badge-license]: https://img.shields.io/github/license/readest/readest?color=teal
[badge-release]: https://img.shields.io/github/release/readest/readest?color=green
[badge-platforms]: https://img.shields.io/badge/platforms-macOS%2C%20Windows%2C%20Linux%2C%20Android%2C%20iOS%2C%20Web%2C%20PWA-green
[badge-last-commit]: https://img.shields.io/github/last-commit/readest/readest?color=green
[badge-commit-activity]: https://img.shields.io/github/commit-activity/m/readest/readest
[badge-last-commit]: https://img.shields.io/github/last-commit/readest/readest?color=blue
[badge-commit-activity]: https://img.shields.io/github/commit-activity/m/readest/readest?color=blue
[badge-discord]: https://img.shields.io/discord/1314226120886976544?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square
[badge-hellogithub]: https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=8a5b6ade2aee461a8bd94e59200682a7&claim_uid=eRLUbPOy2qZtDgw&theme=small
[badge-donate]: https://donate.readest.com/badge.svg
[badge-deepwiki]: https://deepwiki.com/badge.svg
[badge-reddit]: https://img.shields.io/reddit/subreddit-subscribers/readest?style=flat&logo=reddit&color=F37E41
[badge-language-coverage]: https://img.shields.io/badge/coverage-53%25%20population%20🌍-green
[link-donate]: https://donate.readest.com/?tickers=btc%2Ceth%2Csol%2Cusdc
[link-appstore]: https://apps.apple.com/app/apple-store/id6738622779?pt=127463130&ct=github&mt=8
[link-website]: https://readest.com?utm_source=github&utm_medium=referral&utm_campaign=readme
[link-flathub]: https://flathub.org/en/apps/com.bilingify.readest
[link-web-readest]: https://web.readest.com
[link-gh-releases]: https://github.com/readest/readest/releases
[link-gh-commits]: https://github.com/readest/readest/commits/main
@@ -252,3 +323,7 @@ The following fonts are utilized in this software, either bundled within the app
[link-parallel-read]: https://readest.com/#parallel-read
[link-koreader]: https://github.com/koreader/koreader
[link-hellogithub]: https://hellogithub.com/repository/8a5b6ade2aee461a8bd94e59200682a7
[link-deepwiki]: https://deepwiki.com/readest/readest
[link-locales]: https://github.com/readest/readest/tree/main/apps/readest-app/public/locales
[link-kosync-wiki]: https://github.com/readest/readest/wiki/Sync-with-Koreader-devices
[link-reddit]: https://reddit.com/r/readest/
+8 -2
View File
@@ -2,5 +2,11 @@ PDFJS_BUILD_PATH=../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build
PDFJS_FONTS_PATH=../../packages/foliate-js/node_modules/pdfjs-dist
PDFJS_STYLE_PATH=../../packages/foliate-js/vendor/pdfjs
NEXT_PUBLIC_DEV_SUPABASE_URL=https://gxkhxxxeapexynpouyjz.supabase.co
NEXT_PUBLIC_DEV_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd4a2h4eHhlYXBleHlucG91eWp6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ0MzAwNTksImV4cCI6MjA1MDAwNjA1OX0.jhinkQsimQoidsg_U59YD5ROw4PmMJQNKuyXbr4TiQA
NEXT_PUBLIC_DEFAULT_POSTHOG_URL_BASE64="aHR0cHM6Ly91cy5pLnBvc3Rob2cuY29t"
NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX2ViNXowbVRxWm8yZm5YYnZGNmE3bFh5TThpTmRSNTNsR1A3VFM3VGh4S08="
NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64="aHR0cHM6Ly9yZWFkZXN0LnN1cGFiYXNlLmNv"
NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64="ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKemRYQmhZbUZ6WlNJc0luSmxaaUk2SW5aaWMzbDRablZ6YW1weFpIaHJhbkZzZVhOaklpd2ljbTlzWlNJNkltRnViMjRpTENKcFlYUWlPakUzTXpReE1qTTJOekVzSW1WNGNDSTZNakEwT1RZNU9UWTNNWDAuM1U1VXFhb3VfMVNnclZlMWVvOXJBcGMwdUtqcWhwUWRVWGh2d1VIbVVmZw=="
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_DEV_BASE64="cGtfdGVzdF81MVJmQmdLRTdSWW5pTWsxc0tDV2RUd2hMZzcySzk4eDRWcjlIdDdsRFBONngzcnpZYmhydGtNQnpDdzZKbHFaRVVITVp5eVNjVXhCZXVkcGppWTk0WXNHcDAweFlRRnRRaUU="
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_BASE64="cGtfbGl2ZV81MVFYN3dRRU5ndjJFOUxQRHpZUlE5TlJJeTNjd09EZ1AzSkNFRHRPWlFtdFJWc3Brd053ZE1NNUpIVnVPTmJWcjZ3VGFCMUNZR1pJMmRPVWppTkY0bHJvVjAwalE4TkpkdWk="
+15
View File
@@ -4,14 +4,29 @@ NEXT_PUBLIC_POSTHOG_HOST=YOUR_POSTHOG_HOST
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824
NEXT_PUBLIC_API_BASE_URL=https://your-api-base-url.com
SUPABASE_ADMIN_KEY=YOUR_SUPABASE_ADMIN_KEY
DEEPL_PRO_API_KEYS=YOUR_DEEPL_PRO_API_KEYS
DEEPL_FREE_API_KEYS=YOUR_DEEPL_FREE_API_KEYS
# r2, s3
NEXT_PUBLIC_OBJECT_STORAGE_TYPE=r2
R2_TOKEN_VALUE=YOUR_R2_TOKEN_VALUE
R2_ACCESS_KEY_ID=YOUR_R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY=YOUR_R2_SECRET_ACCESS_KEY
R2_BUCKET_NAME=YOUR_R2_BUCKET_NAME
R2_ACCOUNT_ID=YOUR_R2_ACCOUNT_ID
R2_REGION=YOUR_R2_REGION
S3_ENDPOINT=PLACE_HOLDER
S3_ACCESS_KEY_ID=PLACE_HOLDER
S3_SECRET_ACCESS_KEY=PLACE_HOLDER
S3_BUCKET_NAME=PLACE_HOLDER
S3_REGION=PLACE_HOLDER
TEMP_STORAGE_PUBLIC_BUCKET_NAME=PLACE_HOLDER
+3 -1
View File
@@ -1 +1,3 @@
NEXT_PUBLIC_APP_PLATFORM=tauri
NEXT_PUBLIC_APP_PLATFORM=tauri
DBUS_ID=com.bilingify.readest
+2
View File
@@ -0,0 +1,2 @@
NEXT_PUBLIC_APP_PLATFORM=tauri
AI_GATEWAY_API_KEY=your_key_here
+3
View File
@@ -0,0 +1,3 @@
NEXT_PUBLIC_APP_PLATFORM=web
AI_GATEWAY_API_KEY=your_key_here
NEXT_PUBLIC_AI_GATEWAY_API_KEY=your_key_here
+1 -1
View File
@@ -32,7 +32,7 @@ yarn-error.log*
/private_keys
# plists
*.plist
Entitlements*.plist
# local confs
tauri.*.conf.json
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/styles/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/utils",
"ui": "@/components/ui",
"lib": "@/libs",
"hooks": "@/hooks"
},
"registries": {}
}
+34 -12
View File
@@ -1,13 +1,35 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import { defineConfig, globalIgnores } from 'eslint/config';
import next from 'eslint-config-next';
import nextVitals from 'eslint-config-next/core-web-vitals';
import tseslint from 'eslint-config-next/typescript';
import jsxA11y from 'eslint-plugin-jsx-a11y';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [...compat.extends("next/core-web-vitals", "next/typescript")];
const eslintConfig = defineConfig([
...tseslint,
...next,
...nextVitals,
{
rules: {
...jsxA11y.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
},
globalIgnores([
'node_modules/**',
'.next/**',
'.open-next/**',
'out/**',
'build/**',
'public/**',
'next-env.d.ts',
]),
]);
export default eslintConfig;
+588
View File
@@ -0,0 +1,588 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
dependencies = [
"crc32fast",
"libz-rs-sys",
"miniz_oxide",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gif"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f954a9e9159ec994f73a30a12b96a702dde78f5547bcb561174597924f7d4162"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "image"
version = "0.25.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"gif",
"image-webp",
"moxcms",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libredox"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "libz-rs-sys"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "840db8cf39d9ec4dd794376f38acc40d0fc65eec2a8f484f7fd375b84602becd"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "md5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "moxcms"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "png"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxfm"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3502d6155304a4173a5f2c34b52b7ed0dd085890326cb50fd625fdf39e86b3b"
dependencies = [
"num-traits",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]]
name = "windows_thumbnail"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"directories-next",
"image",
"md5",
"once_cell",
"windows",
"windows-core",
"zip",
]
[[package]]
name = "zip"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b"
dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap",
"memchr",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
[[package]]
name = "zopfli"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]
name = "zune-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "111f7d9820f05fd715df3144e254d6fc02ee4088b0644c0ffd0efc9e6d9d2773"
[[package]]
name = "zune-jpeg"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6fb7703e32e9a07fb3f757360338b3a567a5054f21b5f52a666752e333d58e"
dependencies = [
"zune-core",
]
@@ -0,0 +1,31 @@
[package]
name = "windows_thumbnail"
version = "0.1.0"
edition = "2021"
publish = false
[workspace]
[lib]
name = "windows_thumbnail"
path = "src/mod.rs"
crate-type = ["cdylib"]
[dependencies]
anyhow = "1"
base64 = "0.22"
directories-next = "2.0"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "webp"] }
md5 = "0.8"
once_cell = "1.19"
zip = { version = "6.0", default-features = false, features = ["deflate"] }
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_Security",
"Win32_System_Com",
"Win32_System_LibraryLoader",
"Win32_System_Registry",
"Win32_UI_Shell",
] }
windows-core = "0.62"
@@ -0,0 +1,134 @@
# Windows Thumbnail Provider for Readest
This crate provides Windows Explorer thumbnail support for eBook files when Readest is set as the default application.
## Features
- **Automatic Cover Extraction**: Extracts cover images from EPUB, MOBI, AZW, AZW3, FB2, CBZ, CBR files
- **Readest Branding**: Adds a small Readest icon overlay at the bottom-right corner
- **Smart Caching**: Caches generated thumbnails for faster subsequent loads
- **File Association Aware**: Only shows thumbnails when Readest is the default app for the file type
- **COM Integration**: Full Windows Shell extension implementation via `IThumbnailProvider`
## Supported Formats
| Format | Extension | Cover Source |
| ---------- | ----------------------- | ---------------------------- |
| EPUB | `.epub` | OPF manifest cover reference |
| MOBI/AZW | `.mobi`, `.azw`, `.prc` | EXTH cover offset |
| AZW3/KF8 | `.azw3`, `.kf8` | KF8 format cover |
| FB2 | `.fb2` | `<binary>` coverpage element |
| Comic Book | `.cbz`, `.cbr` | First image in archive |
| Plain Text | `.txt` | Generated placeholder |
## Building
### Library Only
```bash
cargo build --release
```
### COM DLL (for Windows Explorer integration)
```bash
cargo build --release --features com
```
### CLI Tool
```bash
cargo build --release --features cli
```
## Installation
The thumbnail provider DLL is automatically registered when Readest is installed via the NSIS installer.
### Manual Registration (for development)
```powershell
# Register the DLL
regsvr32 /s target\release\windows_thumbnail.dll
# Unregister the DLL
regsvr32 /s /u target\release\windows_thumbnail.dll
# Refresh Explorer to see changes
ie4uinit.exe -show
```
After registration, you may need to restart Windows Explorer or log out/in for changes to take effect.
## Usage (Development / Manual testing)
For local development and testing, build the Windows DLL (or the library) from the Readest Tauri app folder and register it manually. The legacy CLI test harness used to live in the separate `packages/tauri` workspace, but the thumbnail handler implementation now lives inside Readest's Tauri app.
Build the DLL (for Windows explorer integration):
```powershell
cd apps/readest-app/src-tauri
cargo build --release --manifest-path Cargo.toml --features com
```
The standalone CLI test harness is no longer distributed with the app. To test the thumbnail provider locally, build and register the DLL as shown above and use a small test harness that imports `readestlib`'s thumbnail code or use Explorer after registering the handler.
Manual registration for development (register the generated DLL):
```powershell
# Register the DLL
regsvr32 /s target\release\windows_thumbnail.dll
# Unregister the DLL
regsvr32 /s /u target\release\windows_thumbnail.dll
# Refresh Explorer to see changes
ie4uinit.exe -show
```
This generates a thumbnail with the Readest overlay at the specified size.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Windows Explorer │
├─────────────────────────────────────────────────────────────────┤
│ │ │
│ IThumbnailProvider ───┼──► ThumbnailProvider │
│ │ │ │
│ │ ▼ │
│ │ Check File Association │
│ │ (is Readest the default?) │
│ │ │ │
│ │ ▼ (if yes) │
│ │ Extract Cover Image │
│ │ │ │
│ │ ▼ │
│ │ Add Readest Overlay │
│ │ │ │
│ │ ▼ │
│ │ Return HBITMAP │
│ │ │
└─────────────────────────────────────────────────────────────────┘
```
## COM Details
- **CLSID**: `{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}`
- **Shell Thumbnail Handler GUID**: `{e357fccd-a995-4576-b01f-234630154e96}`
- **Threading Model**: Apartment
## How It Works
1. When Windows Explorer needs a thumbnail, it queries the registered shell extension
2. The COM DLL implements `IInitializeWithItem` to receive the file path
3. It checks if Readest.exe is the default application for that file type using `AssocQueryStringW`
4. If Readest is the default, it extracts the cover and generates the thumbnail
5. If Readest is NOT the default, it returns `S_FALSE` to let Windows use other handlers
This ensures thumbnails only appear for files the user has associated with Readest.
## License
MIT License - See LICENSE file for details.
@@ -0,0 +1,506 @@
/// Windows COM Thumbnail Provider for Readest
///
/// Implements IThumbnailProvider and IInitializeWithItem for Windows Shell integration.
/// This allows Windows Explorer to show book covers as thumbnails for eBook files.
///
/// **Important**: Thumbnails are only shown when Readest.exe is the default application
/// for the file type.
///
/// ## CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::path::PathBuf;
use std::sync::atomic::{AtomicIsize, AtomicU32, Ordering};
use windows::core::{IUnknown, Interface, GUID, HRESULT, PCWSTR, PWSTR};
use windows::Win32::Foundation::{
CLASS_E_NOAGGREGATION, E_FAIL, E_INVALIDARG, E_NOINTERFACE, HMODULE, S_FALSE, S_OK,
};
use windows::Win32::Graphics::Gdi::{
CreateDIBSection, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, HBITMAP,
};
use windows::Win32::System::Com::{CoTaskMemFree, IClassFactory, IClassFactory_Impl};
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
use windows::Win32::System::Registry::{
RegCloseKey, RegCreateKeyExW, RegDeleteTreeW, RegSetValueExW, HKEY, HKEY_CLASSES_ROOT,
KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ,
};
use windows::Win32::UI::Shell::{
AssocQueryStringW, IInitializeWithItem, IInitializeWithItem_Impl, IShellItem,
IThumbnailProvider, IThumbnailProvider_Impl, ASSOCF_NONE, ASSOCSTR_EXECUTABLE,
SIGDN_FILESYSPATH, WTSAT_ARGB, WTS_ALPHATYPE,
};
use windows_core::BOOL;
use windows_core::{implement, Ref};
use super::cached_thumbnail_for_path;
// ─────────────────────────────────────────────────────────────────────────────
// CLSID for Readest Thumbnail Provider
// ─────────────────────────────────────────────────────────────────────────────
/// CLSID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
pub const CLSID_READEST_THUMBNAIL: GUID = GUID::from_u128(0xA1B2C3D4_E5F6_7890_ABCD_EF1234567890);
/// Supported file extensions
pub const SUPPORTED_EXTENSIONS: &[&str] = &[
".epub", ".mobi", ".azw", ".azw3", ".kf8", ".prc", ".fb2", ".cbz", ".cbr", ".txt",
];
// DLL reference counting
static DLL_REF_COUNT: AtomicU32 = AtomicU32::new(0);
static DLL_MODULE_PTR: AtomicIsize = AtomicIsize::new(0);
fn dll_add_ref() {
DLL_REF_COUNT.fetch_add(1, Ordering::SeqCst);
}
fn dll_release() {
DLL_REF_COUNT.fetch_sub(1, Ordering::SeqCst);
}
fn set_dll_module(h: HMODULE) {
DLL_MODULE_PTR.store(h.0 as isize, Ordering::SeqCst);
}
fn get_dll_module() -> Option<HMODULE> {
let ptr = DLL_MODULE_PTR.load(Ordering::SeqCst);
if ptr == 0 {
None
} else {
Some(HMODULE(ptr as *mut c_void))
}
}
// ─────────────────────────────────────────────────────────────────────────────
// File Association Check
// ─────────────────────────────────────────────────────────────────────────────
/// Check if Readest.exe is the default application for a given file extension.
fn is_readest_default_for_extension(ext: &str) -> bool {
let ext_wide: Vec<u16> = ext.encode_utf16().chain(std::iter::once(0)).collect();
let mut buffer = [0u16; 260];
let mut buffer_size = buffer.len() as u32;
unsafe {
let result = AssocQueryStringW(
ASSOCF_NONE,
ASSOCSTR_EXECUTABLE,
PCWSTR(ext_wide.as_ptr()),
None,
Some(PWSTR(buffer.as_mut_ptr())),
&mut buffer_size,
);
if result.is_ok() {
let len = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
let path = String::from_utf16_lossy(&buffer[..len]).to_lowercase();
return path.contains("readest");
}
}
false
}
/// Check if Readest is the default app for a specific file path.
fn is_readest_default_for_file(path: &PathBuf) -> bool {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let ext_with_dot = format!(".{}", ext.to_lowercase());
return is_readest_default_for_extension(&ext_with_dot);
}
false
}
// ─────────────────────────────────────────────────────────────────────────────
// ThumbnailProvider
// ─────────────────────────────────────────────────────────────────────────────
/// Interior mutability wrapper for COM single-threaded apartment
struct ComCell<T>(UnsafeCell<T>);
impl<T> ComCell<T> {
fn new(value: T) -> Self {
Self(UnsafeCell::new(value))
}
fn get(&self) -> &T {
unsafe { &*self.0.get() }
}
fn set(&self, value: T) {
unsafe {
*self.0.get() = value;
}
}
}
// SAFETY: COM thumbnail providers run in single-threaded apartment (STA)
unsafe impl<T> Sync for ComCell<T> {}
unsafe impl<T> Send for ComCell<T> {}
#[implement(IThumbnailProvider, IInitializeWithItem)]
pub struct ThumbnailProvider {
file_path: ComCell<Option<PathBuf>>,
file_ext: ComCell<Option<String>>,
should_provide: ComCell<bool>,
}
impl ThumbnailProvider {
pub fn new() -> Self {
dll_add_ref();
Self {
file_path: ComCell::new(None),
file_ext: ComCell::new(None),
should_provide: ComCell::new(false),
}
}
}
impl Default for ThumbnailProvider {
fn default() -> Self {
Self::new()
}
}
impl Drop for ThumbnailProvider {
fn drop(&mut self) {
dll_release();
}
}
impl IInitializeWithItem_Impl for ThumbnailProvider_Impl {
fn Initialize(&self, psi: Ref<'_, IShellItem>, _grfmode: u32) -> windows::core::Result<()> {
let item = psi.ok()?;
unsafe {
let path_pwstr = item.GetDisplayName(SIGDN_FILESYSPATH)?;
let mut len = 0usize;
let mut ptr = path_pwstr.0;
while *ptr != 0 {
len += 1;
ptr = ptr.add(1);
}
let slice = std::slice::from_raw_parts(path_pwstr.0, len);
let path_str = String::from_utf16_lossy(slice);
let path = PathBuf::from(&path_str);
CoTaskMemFree(Some(path_pwstr.0 as *const c_void));
let is_default = is_readest_default_for_file(&path);
self.should_provide.set(is_default);
if !is_default {
return Ok(());
}
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_lowercase())
.unwrap_or_default();
self.file_path.set(Some(path));
self.file_ext.set(Some(ext));
}
Ok(())
}
}
impl IThumbnailProvider_Impl for ThumbnailProvider_Impl {
fn GetThumbnail(
&self,
cx: u32,
phbmp: *mut HBITMAP,
pdwalpha: *mut WTS_ALPHATYPE,
) -> windows::core::Result<()> {
if !*self.should_provide.get() {
return Err(E_FAIL.into());
}
let path = self.file_path.get().as_ref().ok_or(E_FAIL)?;
let ext = self.file_ext.get().as_ref().ok_or(E_FAIL)?;
let png_bytes = cached_thumbnail_for_path(path, ext, cx).map_err(|_| E_FAIL)?;
let img = image::load_from_memory(&png_bytes).map_err(|_| E_FAIL)?;
let rgba = img.to_rgba8();
let (width, height) = (rgba.width(), rgba.height());
let bmi = BITMAPINFO {
bmiHeader: BITMAPINFOHEADER {
biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
biWidth: width as i32,
biHeight: -(height as i32),
biPlanes: 1,
biBitCount: 32,
biCompression: BI_RGB.0,
..Default::default()
},
..Default::default()
};
let mut bits: *mut c_void = std::ptr::null_mut();
unsafe {
let hbmp = CreateDIBSection(None, &bmi, DIB_RGB_COLORS, &mut bits, None, 0)
.map_err(|_| E_FAIL)?;
if bits.is_null() {
return Err(E_FAIL.into());
}
// RGBA -> BGRA
let dst =
std::slice::from_raw_parts_mut(bits as *mut u8, (width * height * 4) as usize);
let src = rgba.as_raw();
for i in 0..(width * height) as usize {
let si = i * 4;
dst[si] = src[si + 2]; // B
dst[si + 1] = src[si + 1]; // G
dst[si + 2] = src[si]; // R
dst[si + 3] = src[si + 3]; // A
}
*phbmp = hbmp;
*pdwalpha = WTSAT_ARGB;
}
Ok(())
}
}
// ─────────────────────────────────────────────────────────────────────────────
// ClassFactory
// ─────────────────────────────────────────────────────────────────────────────
#[implement(IClassFactory)]
pub struct ThumbnailProviderFactory;
impl ThumbnailProviderFactory {
pub fn new() -> Self {
dll_add_ref();
Self
}
}
impl Default for ThumbnailProviderFactory {
fn default() -> Self {
Self::new()
}
}
impl Drop for ThumbnailProviderFactory {
fn drop(&mut self) {
dll_release();
}
}
impl IClassFactory_Impl for ThumbnailProviderFactory_Impl {
fn CreateInstance(
&self,
punkouter: Ref<'_, IUnknown>,
riid: *const GUID,
ppvobject: *mut *mut c_void,
) -> windows::core::Result<()> {
unsafe {
if ppvobject.is_null() {
return Err(E_INVALIDARG.into());
}
*ppvobject = std::ptr::null_mut();
if !punkouter.is_null() {
return Err(CLASS_E_NOAGGREGATION.into());
}
let provider: IThumbnailProvider = ThumbnailProvider::new().into();
provider.query(&*riid, ppvobject).ok()
}
}
fn LockServer(&self, flock: BOOL) -> windows::core::Result<()> {
if flock.as_bool() {
dll_add_ref();
} else {
dll_release();
}
Ok(())
}
}
// ─────────────────────────────────────────────────────────────────────────────
// DLL Exports
// ─────────────────────────────────────────────────────────────────────────────
#[no_mangle]
pub extern "system" fn DllMain(hinstance: HMODULE, reason: u32, _reserved: *mut c_void) -> BOOL {
const DLL_PROCESS_ATTACH: u32 = 1;
if reason == DLL_PROCESS_ATTACH {
set_dll_module(hinstance);
}
BOOL::from(true)
}
#[no_mangle]
pub extern "system" fn DllCanUnloadNow() -> HRESULT {
if DLL_REF_COUNT.load(Ordering::SeqCst) == 0 {
S_OK
} else {
S_FALSE
}
}
#[no_mangle]
pub unsafe extern "system" fn DllGetClassObject(
rclsid: *const GUID,
riid: *const GUID,
ppv: *mut *mut c_void,
) -> HRESULT {
if ppv.is_null() || rclsid.is_null() || riid.is_null() {
return E_INVALIDARG;
}
*ppv = std::ptr::null_mut();
if *rclsid != CLSID_READEST_THUMBNAIL {
return E_NOINTERFACE;
}
if *riid != IClassFactory::IID && *riid != IUnknown::IID {
return E_NOINTERFACE;
}
let factory: IClassFactory = ThumbnailProviderFactory::new().into();
factory.query(&*riid, ppv)
}
#[no_mangle]
pub unsafe extern "system" fn DllRegisterServer() -> HRESULT {
match register_server_impl() {
Ok(()) => S_OK,
Err(e) => e,
}
}
#[no_mangle]
pub unsafe extern "system" fn DllUnregisterServer() -> HRESULT {
let _ = unregister_server_impl();
S_OK
}
// ─────────────────────────────────────────────────────────────────────────────
// Registry helpers
// ─────────────────────────────────────────────────────────────────────────────
fn get_dll_path() -> Option<String> {
let module = get_dll_module()?;
let mut buffer = [0u16; 260];
unsafe {
let len = GetModuleFileNameW(Some(module), &mut buffer);
if len == 0 {
None
} else {
Some(String::from_utf16_lossy(&buffer[..len as usize]))
}
}
}
fn clsid_string() -> String {
format!(
"{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
CLSID_READEST_THUMBNAIL.data1,
CLSID_READEST_THUMBNAIL.data2,
CLSID_READEST_THUMBNAIL.data3,
CLSID_READEST_THUMBNAIL.data4[0],
CLSID_READEST_THUMBNAIL.data4[1],
CLSID_READEST_THUMBNAIL.data4[2],
CLSID_READEST_THUMBNAIL.data4[3],
CLSID_READEST_THUMBNAIL.data4[4],
CLSID_READEST_THUMBNAIL.data4[5],
CLSID_READEST_THUMBNAIL.data4[6],
CLSID_READEST_THUMBNAIL.data4[7]
)
}
fn to_wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
unsafe fn set_reg_value(key: HKEY, name: &str, value: &str) -> Result<(), HRESULT> {
let name_w = to_wide(name);
let value_w = to_wide(value);
let bytes: &[u8] = std::slice::from_raw_parts(value_w.as_ptr() as *const u8, value_w.len() * 2);
if RegSetValueExW(key, PCWSTR(name_w.as_ptr()), Some(0), REG_SZ, Some(bytes)).is_err() {
Err(E_FAIL)
} else {
Ok(())
}
}
unsafe fn create_reg_key(parent: HKEY, subkey: &str) -> Result<HKEY, HRESULT> {
let subkey_w = to_wide(subkey);
let mut hkey = HKEY::default();
let result = RegCreateKeyExW(
parent,
PCWSTR(subkey_w.as_ptr()),
Some(0),
None,
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut hkey,
None,
);
if result.is_err() {
Err(E_FAIL)
} else {
Ok(hkey)
}
}
unsafe fn register_server_impl() -> Result<(), HRESULT> {
let dll_path = get_dll_path().ok_or(E_FAIL)?;
let clsid = clsid_string();
// CLSID key
let clsid_key = create_reg_key(HKEY_CLASSES_ROOT, &format!("CLSID\\{}", clsid))?;
set_reg_value(clsid_key, "", "Readest Thumbnail Provider")?;
// CRITICAL: DisableProcessIsolation = 1
let disable_isolation_name = to_wide("DisableProcessIsolation");
let value: u32 = 1;
let _ = windows::Win32::System::Registry::RegSetValueExW(
clsid_key,
PCWSTR(disable_isolation_name.as_ptr()),
Some(0),
windows::Win32::System::Registry::REG_DWORD,
Some(std::slice::from_raw_parts(
&value as *const u32 as *const u8,
4,
)),
);
let inproc_key = create_reg_key(clsid_key, "InprocServer32")?;
set_reg_value(inproc_key, "", &dll_path)?;
set_reg_value(inproc_key, "ThreadingModel", "Apartment")?;
let _ = RegCloseKey(inproc_key);
let _ = RegCloseKey(clsid_key);
// Register ShellEx thumbnail handler for each extension
for ext in SUPPORTED_EXTENSIONS {
let ext_shellex_path =
format!("{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}", ext);
if let Ok(ext_shellex_key) = create_reg_key(HKEY_CLASSES_ROOT, &ext_shellex_path) {
let _ = set_reg_value(ext_shellex_key, "", &clsid);
let _ = RegCloseKey(ext_shellex_key);
}
}
Ok(())
}
unsafe fn unregister_server_impl() -> Result<(), HRESULT> {
let clsid = clsid_string();
let clsid_path = to_wide(&format!("CLSID\\{}", clsid));
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(clsid_path.as_ptr()));
for ext in SUPPORTED_EXTENSIONS {
let ext_path = to_wide(&format!(
"{}\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}",
ext
));
let _ = RegDeleteTreeW(HKEY_CLASSES_ROOT, PCWSTR(ext_path.as_ptr()));
}
Ok(())
}
@@ -0,0 +1,648 @@
/// Cover image extraction for various eBook formats
///
/// Supports: EPUB, MOBI/AZW3/KF8, FB2, CBZ/CBR, TXT
use anyhow::{anyhow, Result};
use base64::engine::general_purpose;
use base64::Engine as _;
use directories_next::ProjectDirs;
use image::{imageops, DynamicImage, Rgba};
use md5::Context;
use once_cell::sync::Lazy;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::path::Path;
use zip::ZipArchive;
/// Thumbnail cache directory (per-user)
static CACHE_DIR: Lazy<Option<std::path::PathBuf>> = Lazy::new(|| {
ProjectDirs::from("app", "Readest", "").map(|pd| {
let dir = pd.cache_dir().join("thumbnails");
let _ = std::fs::create_dir_all(&dir);
dir
})
});
// ─────────────────────────────────────────────────────────────────────────────
// EPUB extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image bytes from an EPUB file.
pub fn extract_epub_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
let mut archive = ZipArchive::new(reader)?;
// Pass 1: Look for files with "cover" in the name
let mut candidates: Vec<(usize, String, u64)> = Vec::new();
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_lowercase();
let size = file.size();
drop(file);
if is_image_extension(&name) && (name.contains("cover") || name.contains("front")) {
candidates.push((i, name, size));
}
}
// Sort by priority: exact "cover" match first, then by size
if !candidates.is_empty() {
candidates.sort_by(|a, b| {
let a_exact = a.1.contains("cover.") || a.1.ends_with("cover");
let b_exact = b.1.contains("cover.") || b.1.ends_with("cover");
match (a_exact, b_exact) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
_ => b.2.cmp(&a.2),
}
});
let idx = candidates[0].0;
let mut file = archive.by_index(idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
// Pass 2: Parse container.xml to find OPF, then parse OPF for cover
let container_xml = read_zip_file_to_string(&mut archive, "META-INF/container.xml");
if let Ok(xml) = container_xml {
if let Some(rootfile) = extract_attribute(&xml, "rootfile", "full-path") {
let opf_content = read_zip_file_to_string(&mut archive, &rootfile);
if let Ok(opf) = opf_content {
if let Some(cover_id) = find_cover_id_in_opf(&opf) {
if let Some(href) = find_href_by_id_in_opf(&opf, &cover_id) {
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
return Ok(bytes);
}
}
}
if let Some(href) = find_first_image_in_manifest(&opf) {
let base = Path::new(&rootfile).parent().unwrap_or(Path::new(""));
let cover_path = base.join(&href).to_string_lossy().replace('\\', "/");
if let Ok(bytes) = read_zip_file_to_bytes(&mut archive, &cover_path) {
return Ok(bytes);
}
}
}
}
}
// Pass 3: Just grab the largest image file
let mut largest: Option<(usize, u64)> = None;
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_lowercase();
let size = file.size();
drop(file);
if is_image_extension(&name) && (largest.is_none() || size > largest.unwrap().1) {
largest = Some((i, size));
}
}
if let Some((idx, _)) = largest {
let mut file = archive.by_index(idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
Err(anyhow!("No cover image found in EPUB"))
}
// ─────────────────────────────────────────────────────────────────────────────
// MOBI/AZW3/KF8 extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from MOBI/AZW3/KF8 files.
pub fn extract_mobi_cover_bytes<R: Read + Seek>(mut reader: R) -> Result<Vec<u8>> {
let mut header = [0u8; 78];
reader.read_exact(&mut header)?;
if &header[60..68] != b"BOOKMOBI" {
return Err(anyhow!("Not a valid MOBI file"));
}
let num_records = u16::from_be_bytes([header[76], header[77]]) as usize;
let mut record_offsets: Vec<u32> = Vec::with_capacity(num_records);
for _ in 0..num_records {
let mut rec = [0u8; 8];
reader.read_exact(&mut rec)?;
record_offsets.push(u32::from_be_bytes([rec[0], rec[1], rec[2], rec[3]]));
}
if record_offsets.is_empty() {
return Err(anyhow!("No records in MOBI file"));
}
reader.seek(SeekFrom::Start(record_offsets[0] as u64))?;
let mut mobi_header = [0u8; 256];
reader.read_exact(&mut mobi_header)?;
if &mobi_header[16..20] != b"MOBI" {
return Err(anyhow!("Invalid MOBI header"));
}
let header_length = u32::from_be_bytes([
mobi_header[20],
mobi_header[21],
mobi_header[22],
mobi_header[23],
]) as usize;
let exth_flags = u32::from_be_bytes([
mobi_header[128],
mobi_header[129],
mobi_header[130],
mobi_header[131],
]);
if exth_flags & 0x40 == 0 {
return Err(anyhow!("No EXTH header in MOBI file"));
}
let exth_offset = record_offsets[0] as u64 + 16 + header_length as u64;
reader.seek(SeekFrom::Start(exth_offset))?;
let mut exth_magic = [0u8; 4];
reader.read_exact(&mut exth_magic)?;
if &exth_magic != b"EXTH" {
return Err(anyhow!("EXTH header not found"));
}
let mut exth_len_bytes = [0u8; 4];
reader.read_exact(&mut exth_len_bytes)?;
let mut exth_count_bytes = [0u8; 4];
reader.read_exact(&mut exth_count_bytes)?;
let exth_count = u32::from_be_bytes(exth_count_bytes) as usize;
let mut cover_offset: Option<u32> = None;
let first_img_idx = u32::from_be_bytes([
mobi_header[108],
mobi_header[109],
mobi_header[110],
mobi_header[111],
]);
for _ in 0..exth_count {
let mut rec_header = [0u8; 8];
if reader.read_exact(&mut rec_header).is_err() {
break;
}
let rec_type =
u32::from_be_bytes([rec_header[0], rec_header[1], rec_header[2], rec_header[3]]);
let rec_len =
u32::from_be_bytes([rec_header[4], rec_header[5], rec_header[6], rec_header[7]])
as usize;
let data_len = rec_len.saturating_sub(8);
let mut data = vec![0u8; data_len];
if reader.read_exact(&mut data).is_err() {
break;
}
if rec_type == 201 && data_len >= 4 {
cover_offset = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]));
}
}
let cover_record_idx = if let Some(offset) = cover_offset {
first_img_idx + offset
} else {
first_img_idx
};
if cover_record_idx as usize >= record_offsets.len() {
return Err(anyhow!("Cover record index out of bounds"));
}
let start = record_offsets[cover_record_idx as usize] as u64;
let end = if (cover_record_idx as usize + 1) < record_offsets.len() {
record_offsets[cover_record_idx as usize + 1] as u64
} else {
reader.seek(SeekFrom::End(0))?;
reader.stream_position()?
};
let len = (end - start) as usize;
reader.seek(SeekFrom::Start(start))?;
let mut cover_data = vec![0u8; len];
reader.read_exact(&mut cover_data)?;
if cover_data.starts_with(&[0xFF, 0xD8, 0xFF])
|| cover_data.starts_with(&[0x89, 0x50, 0x4E, 0x47])
|| cover_data.starts_with(b"GIF")
{
return Ok(cover_data);
}
Err(anyhow!("No valid cover image found in MOBI"))
}
// ─────────────────────────────────────────────────────────────────────────────
// CBZ extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from CBZ (comic book ZIP) file.
pub fn extract_cbz_cover_bytes<R: Read + Seek>(reader: R) -> Result<Vec<u8>> {
let mut archive = ZipArchive::new(reader)?;
let mut images: Vec<(usize, String)> = Vec::new();
for i in 0..archive.len() {
let file = archive.by_index(i)?;
let name = file.name().to_string();
drop(file);
if is_image_extension(&name.to_lowercase()) {
images.push((i, name));
}
}
images.sort_by(|a, b| a.1.cmp(&b.1));
if let Some((idx, _)) = images.first() {
let mut file = archive.by_index(*idx)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
Err(anyhow!("No images found in CBZ"))
}
// ─────────────────────────────────────────────────────────────────────────────
// FB2 extraction
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image from FB2 (FictionBook) file.
pub fn extract_fb2_cover_bytes<R: Read>(mut reader: R) -> Result<Vec<u8>> {
let mut content = String::new();
reader.read_to_string(&mut content)?;
let cover_id = if let Some(start) = content.find("<coverpage>") {
let end = content[start..].find("</coverpage>").unwrap_or(500);
let coverpage = &content[start..start + end];
if let Some(href_pos) = coverpage.find("href=\"#") {
let id_start = href_pos + 7;
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
Some(coverpage[id_start..id_start + id_end].to_string())
} else if let Some(href_pos) = coverpage.find("l:href=\"#") {
let id_start = href_pos + 9;
let id_end = coverpage[id_start..].find('"').unwrap_or(50);
Some(coverpage[id_start..id_start + id_end].to_string())
} else {
None
}
} else {
None
};
let search_pattern = if let Some(ref id) = cover_id {
format!("<binary id=\"{}\"", id)
} else {
"<binary".to_string()
};
if let Some(pos) = content.find(&search_pattern) {
if let Some(tag_end) = content[pos..].find('>') {
let data_start = pos + tag_end + 1;
if let Some(data_end) = content[data_start..].find("</binary>") {
let b64_data = content[data_start..data_start + data_end].trim();
let b64_clean: String = b64_data.chars().filter(|c| !c.is_whitespace()).collect();
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
return Ok(bytes);
}
}
}
if cover_id.is_some() {
if let Some(pos) = content.find("<binary") {
if let Some(tag_end) = content[pos..].find('>') {
let data_start = pos + tag_end + 1;
if let Some(data_end) = content[data_start..].find("</binary>") {
let b64_data = content[data_start..data_start + data_end].trim();
let b64_clean: String =
b64_data.chars().filter(|c| !c.is_whitespace()).collect();
let bytes = general_purpose::STANDARD.decode(&b64_clean)?;
return Ok(bytes);
}
}
}
}
Err(anyhow!("No cover image found in FB2"))
}
// ─────────────────────────────────────────────────────────────────────────────
// TXT "cover" (placeholder)
// ─────────────────────────────────────────────────────────────────────────────
/// Generate a placeholder thumbnail for TXT files.
pub fn extract_txt_cover_bytes<R: Read>(mut reader: R, size: u32) -> Result<Vec<u8>> {
let mut buf = vec![0u8; 4096];
let _n = reader.read(&mut buf)?;
let mut img = image::RgbaImage::from_pixel(size, size, Rgba([245, 245, 245, 255]));
for x in 0..size {
img.put_pixel(x, 0, Rgba([200, 200, 200, 255]));
img.put_pixel(x, size - 1, Rgba([200, 200, 200, 255]));
}
for y in 0..size {
img.put_pixel(0, y, Rgba([200, 200, 200, 255]));
img.put_pixel(size - 1, y, Rgba([200, 200, 200, 255]));
}
let mut out = Vec::new();
DynamicImage::ImageRgba8(img).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
Ok(out)
}
// ─────────────────────────────────────────────────────────────────────────────
// Unified extraction by extension
// ─────────────────────────────────────────────────────────────────────────────
/// Extract cover image bytes based on file extension.
pub fn extract_cover_bytes_by_ext(path: &Path, ext: &str) -> Result<Vec<u8>> {
let file = std::fs::File::open(path)?;
match ext.to_lowercase().as_str() {
"epub" => extract_epub_cover_bytes(file),
"mobi" | "azw" | "azw3" | "kf8" | "prc" => extract_mobi_cover_bytes(file),
"cbz" | "cbr" => extract_cbz_cover_bytes(file),
"fb2" => extract_fb2_cover_bytes(file),
"txt" => extract_txt_cover_bytes(file, 256),
_ => Err(anyhow!("Unsupported format: {}", ext)),
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Thumbnail creation with overlay
// ─────────────────────────────────────────────────────────────────────────────
/// Create a thumbnail from cover image bytes with Readest icon overlay.
pub fn create_thumbnail_with_overlay(cover_bytes: &[u8], requested_size: u32) -> Result<Vec<u8>> {
let img = image::load_from_memory(cover_bytes)?;
let thumbnail = img.thumbnail(requested_size, requested_size);
let overlay_img = load_overlay_icon();
let mut base = thumbnail.to_rgba8();
let (base_w, base_h) = (base.width(), base.height());
if let Some(ov) = overlay_img {
let overlay_size = (requested_size / 5).clamp(24, 48);
let ov_resized = ov.resize(overlay_size, overlay_size, imageops::FilterType::Lanczos3);
let ovb = ov_resized.to_rgba8();
let (ov_w, ov_h) = (ovb.width(), ovb.height());
let x = base_w.saturating_sub(ov_w + 4);
let y = base_h.saturating_sub(ov_h + 4);
for oy in 0..ov_h {
for ox in 0..ov_w {
let dst_x = x + ox;
let dst_y = y + oy;
if dst_x < base_w && dst_y < base_h {
let src_pixel = ovb.get_pixel(ox, oy);
let alpha = src_pixel.0[3] as f32 / 255.0;
if alpha > 0.0 {
let dst_pixel = base.get_pixel(dst_x, dst_y);
let mut result = dst_pixel.0;
for c in 0..3 {
let fg = src_pixel.0[c] as f32;
let bg = result[c] as f32;
result[c] = (fg * alpha + bg * (1.0 - alpha)) as u8;
}
result[3] = 255;
base.put_pixel(dst_x, dst_y, Rgba(result));
}
}
}
}
}
let mut out = Vec::new();
DynamicImage::ImageRgba8(base).write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)?;
Ok(out)
}
/// Load the Readest overlay icon.
fn load_overlay_icon() -> Option<DynamicImage> {
// Try embedded icon
let icon_bytes = include_bytes!("../../../public/icon.png");
if let Ok(img) = image::load_from_memory(icon_bytes) {
return Some(img);
}
// Fallback: try loading from filesystem
if let Ok(exe) = std::env::current_exe() {
let candidates = [
exe.parent().map(|p| p.join("icon.png")),
exe.parent().map(|p| p.join("resources").join("icon.png")),
exe.parent()
.and_then(|p| p.parent())
.map(|p| p.join("resources").join("icon.png")),
];
for candidate in candidates.into_iter().flatten() {
if candidate.exists() {
if let Ok(bytes) = std::fs::read(&candidate) {
if let Ok(img) = image::load_from_memory(&bytes) {
return Some(img);
}
}
}
}
}
None
}
// ─────────────────────────────────────────────────────────────────────────────
// Caching
// ─────────────────────────────────────────────────────────────────────────────
/// Generate a thumbnail with disk caching.
pub fn cached_thumbnail_for_path(path: &Path, ext: &str, size: u32) -> Result<Vec<u8>> {
// Compute cache key by hashing file parts for stability without loading entire file
let mut hasher = Context::new();
hasher.consume(ext.as_bytes());
hasher.consume(&size.to_le_bytes());
let file = std::fs::File::open(path)?;
let metadata = file.metadata()?;
let file_len = metadata.len();
// Read partial chunks like the TypeScript partialMD5 implementation
const STEP: u64 = 1024;
const SIZE: u64 = 1024;
let mut file = file;
for i in -1i32..=10 {
let pos = if i == -1 {
256u64
} else {
STEP << (2 * i as u32)
};
let start = pos.min(file_len);
let end = (start + SIZE).min(file_len);
if start >= file_len {
break;
}
file.seek(SeekFrom::Start(start))?;
let mut buf = vec![0u8; (end - start) as usize];
file.read_exact(&mut buf)?;
hasher.consume(&buf);
}
let digest = hasher.finalize();
let key = format!("{:x}.png", digest);
if let Some(ref dir) = *CACHE_DIR {
let cache_path = dir.join(&key);
if cache_path.exists() {
if let Ok(cached) = std::fs::read(&cache_path) {
return Ok(cached);
}
}
}
let cover = extract_cover_bytes_by_ext(path, ext)?;
let thumbnail = create_thumbnail_with_overlay(&cover, size)?;
if let Some(ref dir) = *CACHE_DIR {
let cache_path = dir.join(&key);
let _ = std::fs::write(&cache_path, &thumbnail);
}
Ok(thumbnail)
}
// ─────────────────────────────────────────────────────────────────────────────
// Helper functions
// ─────────────────────────────────────────────────────────────────────────────
fn is_image_extension(name: &str) -> bool {
name.ends_with(".jpg")
|| name.ends_with(".jpeg")
|| name.ends_with(".png")
|| name.ends_with(".gif")
|| name.ends_with(".webp")
|| name.ends_with(".bmp")
}
fn read_zip_file_to_string<R: Read + Seek>(
archive: &mut ZipArchive<R>,
name: &str,
) -> Result<String> {
let mut file = archive.by_name(name)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn read_zip_file_to_bytes<R: Read + Seek>(
archive: &mut ZipArchive<R>,
name: &str,
) -> Result<Vec<u8>> {
let mut file = archive.by_name(name)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
}
fn extract_attribute(xml: &str, tag: &str, attr: &str) -> Option<String> {
let pattern = format!("<{}", tag);
if let Some(tag_pos) = xml.find(&pattern) {
let tag_end = xml[tag_pos..].find('>').unwrap_or(500) + tag_pos;
let tag_content = &xml[tag_pos..tag_end];
let attr_pattern = format!("{}=\"", attr);
if let Some(attr_pos) = tag_content.find(&attr_pattern) {
let value_start = attr_pos + attr_pattern.len();
if let Some(value_end) = tag_content[value_start..].find('"') {
return Some(tag_content[value_start..value_start + value_end].to_string());
}
}
}
None
}
fn find_cover_id_in_opf(opf: &str) -> Option<String> {
if let Some(pos) = opf.find("name=\"cover\"") {
let window_start = pos.saturating_sub(50);
let window_end = (pos + 100).min(opf.len());
let window = &opf[window_start..window_end];
if let Some(content_pos) = window.find("content=\"") {
let start = content_pos + 9;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
if let Some(pos) = opf.find("properties=\"cover-image\"") {
let window_start = pos.saturating_sub(200);
let window_end = pos;
let window = &opf[window_start..window_end];
if let Some(id_pos) = window.rfind("id=\"") {
let start = id_pos + 4;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
None
}
fn find_href_by_id_in_opf(opf: &str, id: &str) -> Option<String> {
let pattern = format!("id=\"{}\"", id);
if let Some(pos) = opf.find(&pattern) {
let window_start = pos.saturating_sub(10);
let window_end = (pos + 200).min(opf.len());
let window = &opf[window_start..window_end];
if let Some(href_pos) = window.find("href=\"") {
let start = href_pos + 6;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
None
}
fn find_first_image_in_manifest(opf: &str) -> Option<String> {
let manifest_start = opf.find("<manifest")?;
let manifest_end = opf[manifest_start..]
.find("</manifest>")
.map(|e| manifest_start + e)?;
let manifest = &opf[manifest_start..manifest_end];
for media_type in ["image/jpeg", "image/png", "image/gif", "image/webp"] {
let pattern = format!("media-type=\"{}\"", media_type);
if let Some(pos) = manifest.find(&pattern) {
let window_start = pos.saturating_sub(200);
let window = &manifest[window_start..pos];
if let Some(href_pos) = window.rfind("href=\"") {
let start = href_pos + 6;
if let Some(end) = window[start..].find('"') {
return Some(window[start..start + end].to_string());
}
}
}
}
None
}
@@ -0,0 +1,13 @@
//! Windows Thumbnail Provider for Readest
//!
//! This module provides Windows Explorer thumbnail support for eBook files.
//! Thumbnails are only shown when Readest is set as the default application.
//!
//! Supported formats: EPUB, MOBI, AZW, AZW3, KF8, FB2, CBZ, CBR
#![allow(non_snake_case)]
mod com_provider;
mod extraction;
pub use extraction::*;
@@ -12,11 +12,14 @@ module.exports = {
'de',
'ja',
'es',
'fa',
'fr',
'it',
'el',
'ko',
'uk',
'nl',
'sv',
'pl',
'pt',
'ru',
@@ -24,7 +27,13 @@ module.exports = {
'hi',
'id',
'vi',
'ms',
'ar',
'th',
'bo',
'bn',
'ta',
'si',
'zh-CN',
'zh-TW',
],
+72 -16
View File
@@ -1,13 +1,22 @@
import withPWAInit from '@ducanh2912/next-pwa';
import withSerwistInit from '@serwist/next';
import withBundleAnalyzer from '@next/bundle-analyzer';
const isDev = process.env['NODE_ENV'] === 'development';
const appPlatform = process.env['NEXT_PUBLIC_APP_PLATFORM'];
if (isDev) {
const { initOpenNextCloudflareForDev } = await import('@opennextjs/cloudflare');
initOpenNextCloudflareForDev();
}
const exportOutput = appPlatform !== 'web' && !isDev;
/** @type {import('next').NextConfig} */
const nextConfig = {
// Ensure Next.js uses SSG instead of SSR
// https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
output: appPlatform === 'web' || isDev ? undefined : 'export',
output: exportOutput ? 'export' : undefined,
pageExtensions: exportOutput ? ['jsx', 'tsx'] : ['js', 'jsx', 'ts', 'tsx'],
// 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: {
@@ -17,6 +26,39 @@ const nextConfig = {
// Configure assetPrefix or else the server won't properly resolve your assets.
assetPrefix: '',
reactStrictMode: true,
serverExternalPackages: ['isows'],
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
nunjucks: 'nunjucks/browser/nunjucks.js',
};
return config;
},
turbopack: {
resolveAlias: {
nunjucks: 'nunjucks/browser/nunjucks.js',
},
},
transpilePackages: [
'ai',
'ai-sdk-ollama',
'@ai-sdk/react',
'@assistant-ui/react',
'@assistant-ui/react-ai-sdk',
'@assistant-ui/react-markdown',
'streamdown',
...(isDev
? []
: [
'i18next-browser-languagedetector',
'react-i18next',
'i18next',
'@tauri-apps',
'highlight.js',
'foliate-js',
'marked',
]),
],
async headers() {
return [
{
@@ -28,23 +70,37 @@ const nextConfig = {
},
],
},
{
source: '/_next/static/:path*',
headers: [
{
key: 'Cache-Control',
value: isDev
? 'public, max-age=0, must-revalidate'
: 'public, max-age=31536000, immutable',
},
],
},
];
},
};
const withPWA = withPWAInit({
dest: 'public',
disable: isDev || appPlatform !== 'web',
cacheOnFrontEndNav: true,
aggressiveFrontEndNavCaching: true,
reloadOnOnline: true,
swcMinify: true,
fallbacks: {
document: '/offline',
},
workboxOptions: {
disableDevLogs: true,
},
const pwaDisabled = isDev || appPlatform !== 'web';
const withPWA = pwaDisabled
? (config) => config
: withSerwistInit({
swSrc: 'src/sw.ts',
swDest: 'public/sw.js',
cacheOnNavigation: true,
reloadOnOnline: true,
disable: false,
register: true,
scope: '/',
});
const withAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
export default withPWA(nextConfig);
export default withPWA(withAnalyzer(nextConfig));
+5 -28
View File
@@ -1,29 +1,6 @@
// @ts-nocheck
// default open-next.config.ts file created by @opennextjs/cloudflare
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';
import type { OpenNextConfig } from '@opennextjs/aws/types/open-next.js';
import cache from '@opennextjs/cloudflare/kv-cache';
const config: OpenNextConfig = {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
// set `incrementalCache` to "dummy" to disable KV cache
incrementalCache: async () => cache,
tagCache: 'dummy',
queue: 'dummy',
},
},
middleware: {
external: true,
override: {
wrapper: 'cloudflare-edge',
converter: 'edge',
proxyExternalRequest: 'fetch',
},
},
};
export default config;
export default defineCloudflareConfig({
incrementalCache: r2IncrementalCache,
});
+131 -37
View File
@@ -1,6 +1,6 @@
{
"name": "@readest/readest-app",
"version": "0.9.23",
"version": "0.9.99",
"private": true,
"scripts": {
"dev": "dotenv -e .env.tauri -- next dev",
@@ -9,17 +9,26 @@
"dev-web": "dotenv -e .env.web -- next dev",
"build-web": "dotenv -e .env.web -- next build",
"start-web": "dotenv -e .env.web -- next start",
"build-tauri": "dotenv -e .env.tauri -- next build",
"i18n:extract": "i18next-scanner",
"lint": "next lint",
"lint": "eslint .",
"test": "dotenv -e .env -e .env.test.local vitest",
"tauri": "tauri",
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs",
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
"clippy": "cargo clippy -p Readest --no-deps -- -D warnings",
"format": "pnpm -w format",
"format:check": "pnpm -w format:check",
"prepare-public-vendor": "mkdirp ./public/vendor/pdfjs ./public/vendor/simplecc",
"copy-pdfjs-js": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/legacy/build/{pdf.worker.min.mjs,pdf.min.mjs,pdf.d.mts}\" ./public/vendor/pdfjs",
"copy-pdfjs-wasm": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/wasm/{openjpeg.wasm,qcms_bg.wasm}\" ./public/vendor/pdfjs",
"copy-pdfjs-fonts": "cpx \"../../packages/foliate-js/node_modules/pdfjs-dist/{cmaps,standard_fonts}/*\" ./public/vendor/pdfjs",
"copy-flatten-pdfjs-annotation-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/annotation_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/annotation_layer_builder.css",
"copy-flatten-pdfjs-text-layer-css": "npx postcss \"../../packages/foliate-js/vendor/pdfjs/text_layer_builder.css\" --no-map -u postcss-nested > ./public/vendor/pdfjs/text_layer_builder.css",
"copy-flatten-pdfjs-css": "pnpm copy-flatten-pdfjs-annotation-layer-css && pnpm copy-flatten-pdfjs-text-layer-css",
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
"copy-pdfjs": "pnpm copy-pdfjs-js && pnpm copy-pdfjs-wasm && pnpm copy-pdfjs-fonts && pnpm copy-flatten-pdfjs-css",
"copy-simplecc": "cpx \"../../packages/simplecc-wasm/dist/web/*\" ./public/vendor/simplecc",
"setup-pdfjs": "pnpm prepare-public-vendor && pnpm copy-pdfjs",
"setup-simplecc": "pnpm prepare-public-vendor && pnpm copy-simplecc",
"setup-vendors": "pnpm setup-pdfjs && pnpm setup-simplecc",
"build-win-x64": "dotenv -e .env.tauri.local -- tauri build --target i686-pc-windows-msvc --bundles nsis",
"build-win-arm64": "dotenv -e .env.tauri.local -- tauri build --target aarch64-pc-windows-msvc --bundles nsis",
"build-linux-x64": "dotenv -e .env.tauri.local -- tauri build --target x86_64-unknown-linux-gnu --bundles appimage",
@@ -30,75 +39,160 @@
"build-ios-appstore": "dotenv -e .env.ios-appstore.local -- tauri ios build --export-method app-store-connect",
"release-macos-universial-appstore": "dotenv -e .env.tauri.local -e .env.apple-appstore.local -- bash scripts/release-mac-appstore.sh",
"release-ios-appstore": "dotenv -e .env.ios-appstore.local -- bash scripts/release-ios-appstore.sh",
"preview": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare && wrangler dev",
"deploy": "NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare && wrangler deploy",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
"release-google-play": "dotenv -e .env.google-play.local -- bash scripts/release-google-play.sh",
"config-wrangler": "sed -i \"s/\\${TRANSLATIONS_KV_ID}/$TRANSLATIONS_KV_ID/g\" wrangler.toml",
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0 --port 3001",
"deploy": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare deploy",
"upload": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare upload",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
"patch-build-webpack": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build\"/next build --webpack\"/' package.json; else sed -i 's/next build\"/next build --webpack\"/' package.json; fi",
"restore-build-original": "if [ \"$(uname)\" = \"Darwin\" ]; then sed -i '' 's/next build --webpack\"/next build\"/' package.json; else sed -i 's/next build --webpack\"/next build\"/' package.json; fi",
"update-metadata": "bash ./scripts/sync-release-notes.sh release-notes.json ../../data/metainfo/appdata.xml",
"check:optional-chaining": "count=$(grep -rno '\\?\\.[a-zA-Z_$]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Optional chaining found in output!'; exit 1; else echo '✅ No optional chaining found.'; fi",
"check:translations": "count=$(grep -rno '__STRING_NOT_TRANSLATED__' public/locales/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Untranslated strings found!'; exit 1; else echo '✅ All strings translated.'; fi",
"check:lookbehind-regex": "count=$(grep -rnoE '\\(\\?<[!=]' .next/static/chunks/* out/_next/static/chunks/* | wc -l); if [ \"$count\" -gt 0 ]; then echo '❌ Lookbehind regex found in output!'; exit 1; else echo '✅ No lookbehind regex found.'; fi",
"check:all": "pnpm check:translations && pnpm check:lookbehind-regex",
"build-check": "pnpm build && pnpm build-web && pnpm check:all"
},
"dependencies": {
"@ai-sdk/react": "^3.0.49",
"@assistant-ui/react": "0.11.56",
"@assistant-ui/react-ai-sdk": "1.1.21",
"@assistant-ui/react-markdown": "0.11.9",
"@aws-sdk/client-s3": "^3.735.0",
"@aws-sdk/s3-request-presigner": "^3.735.0",
"@ducanh2912/next-pwa": "^10.2.9",
"@choochmeque/tauri-plugin-sharekit-api": "^0.3.0",
"@fabianlars/tauri-plugin-oauth": "2",
"@opennextjs/cloudflare": "^1.15.1",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@serwist/next": "^9.4.2",
"@stripe/react-stripe-js": "^3.7.0",
"@stripe/stripe-js": "^7.4.0",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/supabase-js": "^2.47.7",
"@tauri-apps/api": "2.3.0",
"@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-deep-link": "^2.2.0",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-haptics": "^2.2.3",
"@tauri-apps/plugin-http": "^2.3.0",
"@tauri-apps/plugin-log": "^2.2.3",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-os": "^2.2.0",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "~2.2.0",
"@tauri-apps/plugin-updater": "^2.5.1",
"@zip.js/zip.js": "^2.7.53",
"@supabase/supabase-js": "^2.76.1",
"@tauri-apps/api": "2.9.1",
"@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-deep-link": "^2.4.6",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-haptics": "^2.3.2",
"@tauri-apps/plugin-http": "^2.5.6",
"@tauri-apps/plugin-log": "^2.8.0",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "~2.3.4",
"@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-websocket": "~2.4.2",
"@zip.js/zip.js": "^2.8.16",
"abortcontroller-polyfill": "^1.7.8",
"ai": "^6.0.47",
"ai-sdk-ollama": "^3.2.0",
"app-store-server-api": "^0.17.1",
"aws4fetch": "^1.0.20",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"cors": "^2.8.5",
"cssbeautify": "^0.3.1",
"dayjs": "^1.11.13",
"dompurify": "^3.3.0",
"foliate-js": "workspace:*",
"franc-min": "^6.2.0",
"fzf": "^0.5.2",
"google-auth-library": "^10.5.0",
"googleapis": "^164.1.0",
"highlight.js": "^11.11.1",
"i18next": "^24.2.0",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-http-backend": "^3.0.1",
"iso-639-2": "^3.0.2",
"iso-639-3": "^3.0.1",
"isomorphic-ws": "^5.0.0",
"js-md5": "^0.8.3",
"jwt-decode": "^4.0.0",
"next": "15.2.2",
"posthog-js": "^1.205.0",
"react": "19.0.0",
"lucide-react": "^0.562.0",
"lunr": "^2.3.9",
"marked": "^15.0.12",
"nanoid": "^5.1.6",
"next": "16.1.6",
"nunjucks": "^3.2.4",
"overlayscrollbars": "^2.11.4",
"overlayscrollbars-react": "^0.5.6",
"posthog-js": "^1.246.0",
"react": "19.2.0",
"react-color": "^2.19.3",
"react-dom": "19.0.0",
"react-dom": "19.2.0",
"react-i18next": "^15.2.0",
"react-icons": "^5.4.0",
"react-responsive": "^10.0.0",
"react-virtuoso": "^4.17.0",
"react-window": "^1.8.11",
"remark-gfm": "^4.0.1",
"semver": "^7.7.1",
"streamdown": "^1.6.10",
"stripe": "^18.2.1",
"styled-jsx": "^5.1.7",
"tailwind-merge": "^3.4.0",
"tinycolor2": "^1.6.0",
"zustand": "5.0.1"
"uuid": "^11.1.0",
"ws": "^8.18.3",
"zod": "^4.0.8",
"zustand": "5.0.10"
},
"devDependencies": {
"@tauri-apps/cli": "2.3.1",
"@next/bundle-analyzer": "^15.4.2",
"@tailwindcss/typography": "^0.5.16",
"@tauri-apps/cli": "2.9.6",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.3.0",
"@types/cors": "^2.8.17",
"@types/cssbeautify": "^0.3.5",
"@types/node": "^22.10.1",
"@types/react": "18.3.12",
"@types/lunr": "^2.3.7",
"@types/node": "^22.15.31",
"@types/nunjucks": "^3.2.6",
"@types/react": "^19.0.0",
"@types/react-color": "^3.0.13",
"@types/react-dom": "18.3.1",
"@types/react-dom": "^19.0.0",
"@types/react-window": "^1.8.8",
"@types/semver": "^7.7.0",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.20",
"caniuse-lite": "^1.0.30001746",
"cpx2": "^8.0.0",
"daisyui": "^4.12.24",
"dotenv-cli": "^7.4.4",
"eslint": "^9.16.0",
"eslint-config-next": "15.0.3",
"eslint-config-next": "16.0.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"i18next-scanner": "^4.6.0",
"jsdom": "^26.1.0",
"mkdirp": "^3.0.1",
"node-env-run": "^4.0.2",
"postcss": "^8.4.49",
"postcss-cli": "^11.0.0",
"postcss-nested": "^7.0.2",
"raw-loader": "^4.0.2",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2"
"serwist": "^9.3.0",
"tailwindcss": "^3.4.18",
"typescript": "^5.7.2",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.15",
"wrangler": "^4.60.0"
}
}
@@ -1,11 +1,15 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "com.bilingify.readest",
"sha256_cert_fingerprints": [
"65:2D:11:67:76:12:29:14:18:42:CB:3D:18:50:B6:E4:7E:46:E1:2F:4B:E4:7F:5A:6C:14:B6:D7:12:74:1E:82"
"65:2D:11:67:76:12:29:14:18:42:CB:3D:18:50:B6:E4:7E:46:E1:2F:4B:E4:7F:5A:6C:14:B6:D7:12:74:1E:82",
"E0:E7:60:55:80:8D:3A:DE:A0:D1:CF:7C:20:85:40:A3:DD:4B:E6:4D:17:5C:0F:DE:26:57:7D:9C:5B:29:5F:51"
]
}
}
@@ -0,0 +1 @@
ed533042-5626-4704-b5f2-fa3bbd1136ed
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1 +1,27 @@
{}
{
"LXGW WenKai GB Screen": "LXGW WenKai SC",
"LXGW WenKai TC": "LXGW WenKai TC",
"Source Han Serif CN": "Source Han Serif",
"Huiwen-MinchoGBK": "Huiwen Mincho",
"KingHwa_OldSong": "KingHwa Song",
"Are you sure to delete {{count}} selected book(s)?_one": "Are you sure to delete {{count}} selected book?",
"Are you sure to delete {{count}} selected book(s)?_other": "Are you sure to delete {{count}} selected books?",
"Search in {{count}} Book(s)..._one": "Search in {{count}} book...",
"Search in {{count}} Book(s)..._other": "Search in {{count}} books...",
"{{count}} pages left in chapter_one": "{{count}} page left in chapter",
"{{count}} pages left in chapter_other": "{{count}} pages left in chapter",
"Deleted {{count}} file(s)_one": "Deleted {{count}} file",
"Deleted {{count}} file(s)_other": "Deleted {{count}} files",
"Failed to delete {{count}} file(s)_one": "Failed to delete {{count}} file",
"Failed to delete {{count}} file(s)_other": "Failed to delete {{count}} files",
"{{count}} selected_one": "{{count}} selected",
"{{count}} selected_other": "{{count}} selected",
"Are you sure to delete {{count}} selected file(s)?_one": "Are you sure to delete {{count}} selected file?",
"Are you sure to delete {{count}} selected file(s)?_other": "Are you sure to delete {{count}} selected files?",
"Successfully imported {{count}} book(s)_one": "Successfully imported {{count}} book",
"Successfully imported {{count}} book(s)_other": "Successfully imported {{count}} books",
"Set status for {{count}} book(s)_one": "Set status for {{count}} book",
"Set status for {{count}} book(s)_other": "Set status for {{count}} books",
"{{count}} book(s) synced_one": "{{count}} book synced",
"{{count}} book(s) synced_other": "{{count}} books synced"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+716
View File
@@ -1,5 +1,721 @@
{
"releases": {
"0.9.99": {
"date": "2026-02-07",
"notes": [
"Reading: Added a reading ruler (line highlight) to help guide your eyes while reading",
"Reading: Added paragraph-by-paragraph reading mode for more focused navigation",
"Reading: Added a speed reading mode to support faster reading workflows",
"Reading: Added support for gamepad input to turn pages",
"Accessibility: Improved TalkBack support in dropdown menus for smoother navigation",
"Accessibility: Page navigation buttons are now always visible to screen readers",
"Accessibility: Reading progress is now updated correctly when navigating with screen readers",
"Annotations: Expanded highlight customization with more color options",
"Annotations: Added an option to export annotations as plain text",
"Library: Added grouping by author and series for better library organization",
"Library: Added support of marking books as finished",
"Search: Added fuzzy search to quickly find settings even with partial keywords",
"Table of Contents: Added page numbers for nested TOC entries",
"Text-to-Speech: Added navigation controls to return to the current reading position",
"Linux: Added an in-app updater for AppImage builds",
"E-ink: Improved color support for color E-ink devices to display richer highlights"
]
},
"0.9.98": {
"date": "2026-01-21",
"notes": [
"Bookshelf: Added the ability to export original book files directly from the book details dialog",
"Bookshelf: Added a new sorting option to organize books by publish date",
"Bookshelf: Improved performance for large library with faster and more stable imports",
"OPDS: Improved download reliability by correctly detecting filenames for PDF and CBZ files",
"Proofreading: Added support for whole-word replacement to make text corrections more precise",
"Notes: Added the ability to export notes using a customizable template",
"PDF: Added support for panning within PDF documents for easier navigation",
"PDF: Improved performance by pre-rendering the next page for smoother page transitions",
"Text-to-Speech: Added support for word replacement to improve pronunciation during playback",
"Text-to-Speech: Fixed an issue where playback speed settings were not applied correctly on Linux",
"E-ink: Improved reading stability by preventing unwanted scrolling when animations are disabled or on e-ink devices",
"Discord: Added Discord Rich Presence to display your current reading status on Desktop platforms",
"Android: Fixed an issue that could cause occasional crashes during app startup",
"Fonts: Improved web font loading for faster and more stable text rendering"
]
},
"0.9.97": {
"date": "2026-01-10",
"notes": [
"Annotations: Added annotation bubble icons directly on text with instant note popups",
"Annotations: Enabled quick highlighting when selecting text",
"Annotations: Added the ability to edit the text range of existing highlights",
"Annotations: Added an annotation navigation bar for easier browsing",
"OPDS: Fixed an issue where CBZ files could not be downloaded from OPDS catalogs",
"OPDS: Improved compatibility with Komga OPDS servers",
"OPDS: Added support for OPDS servers using self-signed SSL certificates",
"PDF: Fixed an issue where some PDF files failed to open",
"E-ink: Optimized colors and layout for improved e-ink device readability",
"Search: Added search term history for quicker repeated searches",
"Text-to-Speech: Reduced excessive pauses between sentences for smoother playback",
"File Transfer: Introduced a background transfer queue for uploading and downloading books",
"Proofreading: Added a proofread tool that can replace text directly within books"
]
},
"0.9.96": {
"date": "2025-12-19",
"notes": [
"TTS: Resolved an issue where Edge TTS was blocked in certain regions",
"OPDS: Improved compatibility with older versions of Calibre Web",
"OPDS: Added an instant search bar for faster browsing in OPDS catalogs",
"OPDS: Added a curated book catalog from Standard Ebooks",
"Bookshelf: Added a “Group Books” action to the context menu",
"Comics: Fixed layout issues to improve comic book reading",
"Layout: Footnote popups now adapt correctly to different screen sizes",
"Layout: Fixed an issue where the maximum inline width was not applied to EPUBs",
"Sync: Improved file downloading by correctly handling special characters in filenames",
"UI: Added an option to temporarily dismiss the reading progress bar",
"Settings: Screen brightness adjustments now apply only to the reader view",
"iOS: You can now open files directly in Readest from the Files app",
"macOS: Added a global menu option to open files with Readest"
]
},
"0.9.95": {
"date": "2025-12-08",
"notes": [
"OPDS: You can now search, browse, and download ebooks directly from OPDS catalogs",
"OPDS: Improved compatibility with Calibre Web for reliable downloads",
"OPDS: Fixed an issue where book covers from self-hosted Calibre OPDS servers did not display correctly",
"Cloud Storage: You can view and delete files stored in your cloud storage directly from the app",
"Bookshelf: Added support for importing books from a folder recursively",
"Bookshelf: You can now rename your bookshelf groups",
"EPUB: Added support for SVG covers from Standard Ebooks",
"Annotations: Keyboard shortcuts no longer copy selected text into the notebook",
"Footnotes: Footnote popups now scale properly on small screens",
"Touch/Styli: Removed the context menu for smoother interaction on touch and stylus devices",
"Layout: Devices with unfoldable screens now automatically switch to a two-column layout",
"PDF: Improved zoomed-in navigation and hand-tool behavior for smoother page navigation",
"Android: Fixed highlighting of the current sentence when using native Android text-to-speech",
"Android: Back button handling now works correctly on Android 15 and above",
"Android: You can now open files shared from other apps"
]
},
"0.9.94": {
"date": "2025-12-02",
"notes": [
"OPDS: You can now browse and download ebooks directly from OPDS catalogs",
"PDF: Added a hand-tool mode that makes navigating PDF pages easier",
"Covers: You can now choose a folder to save the latest book cover image",
"CJK: Added one-tap conversion between Simplified and Traditional Chinese",
"Shortcuts: Use Ctrl + mouse wheel to quickly zoom in or out",
"Library: Added a new option to adjust cover sizes in grid view",
"Windows: Added Ebook thumbnails with book covers in Windows Explorer",
"TTS: Fixed an issue where the page would scroll when showing the annotator",
"Annotations: Resolved layout shifts when selecting text in paginated mode on Android",
"Files: Improved backup handling to prevent file corruption",
"Layout: Fixed text clipping caused by negative indents",
"E-ink: Improved sidebar and notebook background colors for e-ink devices",
"Android: The Back button now works properly in menus, dialogs, and alerts",
"iOS: Fixed occasional failures when opening books on older iOS versions (16.4 and below)"
]
},
"0.9.93": {
"date": "2025-11-20",
"notes": [
"Sync: Improved reliability for new accounts so your books sync correctly from the start",
"Library: Fixed an occasional issue where returning to the library could get stuck",
"Layout: Improved image display in paginated mode so images won't exceed the available height",
"TXT: Improved performance and stability when opening and parsing TXT files",
"Fonts: Fixed an issue where monospace fonts were not applied correctly in code blocks"
]
},
"0.9.92": {
"date": "2025-11-18",
"notes": [
"Bookshelf: Added support for nested groups to organize your books more flexibly",
"Sync: Improved reliability by automatically retrying failed book syncs",
"Sync: Improved token refresh so login sessions stay active longer on KOReader",
"E-Ink: Enhanced readability on the library page for E-Ink devices",
"UI: Storage capacity is now displayed with precise values in the user profile",
"Reader: You can now import files directly into the currently selected book group",
"Account: You can now update the email address linked to your Readest account",
"Footnotes: Added compatibility for more footnote formats"
]
},
"0.9.91": {
"date": "2025-10-29",
"notes": [
"TTS: Fixed an issue where system voices were not working on Android",
"TTS: Added options to customize the highlight style during read-aloud",
"E-Ink: Disabled all shadows to improve display clarity",
"E-Ink: Added an option to save the last book cover on Android",
"Android: Fixed a crash that occurred when toggling 'Allow Script'",
"Android: Adjusted launcher icon size for better compatibility across devices"
]
},
"0.9.90": {
"date": "2025-10-28",
"notes": [
"Sync: Faster library syncing and support for in-app one-time purchases of extra cloud storage",
"TTS: You can now choose the target language for read-aloud on translated books",
"Reader: New keyboard shortcuts make it easier to move between sections",
"Reader: Smoother scrolling for a more comfortable reading experience",
"Reader: Footnotes in definition lists are now fully supported",
"Reader: Improved search accuracy and display of search results",
"Reader: Clearer error messages when importing books",
"Reader: Improved safety when loading book content",
"Settings: You can now choose your own highlight colors",
"Settings: Added option to adjust screen brightness automatically",
"E-Ink: Better readability and clearer progress display with underlined links",
"Android: You can now store app data on an external SD card",
"iOS: Restores your last reading page if the app was closed in the background",
"Localization: Added Persian (Farsi) language support"
]
},
"0.9.88": {
"date": "2025-10-18",
"notes": [
"TTS: Fixed issue where the read-aloud indicator disappeared too quickly",
"Layout: Overriding book layout no longer affects explicitly set text alignment",
"Reader: Added localized number display for reading progress in vertical layout",
"Reader: Enhanced swipe responsiveness for smoother page transitions",
"Settings: Added an option to disable animations when using E-Ink mode"
]
},
"0.9.87": {
"date": "2025-10-16",
"notes": [
"Reader: Improved chapter detection with better chapter title recognition",
"Reader: Enhanced compatibility for parsing more footnote formats",
"Theme: Fixed auto theme switching in new reader windows",
"TTS: Improved scrolling accuracy to highlighted text during read-aloud",
"TTS: Fixed crash issues on certain Android systems"
]
},
"0.9.86": {
"date": "2025-10-14",
"notes": [
"Reader: Added support for per-book background image customization",
"Reader: Added option to override book language metadata for improved hyphenation accuracy",
"Sync: Readest sync in KOReader can now be triggered using gesture controls",
"Sync: Fixed a crash in KOReader that occurred when syncing progress without an open book",
"Sync: Improved upload performance with concurrent upload support"
]
},
"0.9.85": {
"date": "2025-10-14",
"notes": [
"TTS: Improved media session control compatibility across more Android systems",
"TTS: Made the TTS icon less distracting during read-aloud mode",
"Settings: Added an option to adjust screen brightness while reading",
"Settings: Added support for custom background images",
"PDF: Fixed an issue where scrolling and panning didnt work correctly when zoomed in",
"Android: Added adaptive launcher icon for better system integration",
"Reader: Added keyboard shortcuts for toggling bookmarks",
"Reader: Annotations are now rendered instantly when opening files"
]
},
"0.9.82": {
"date": "2025-10-01",
"notes": [
"Sync: More reliable syncing between KOReader and Readest",
"MOBI: Fixed an issue where some footnotes in MOBI/AZW files were not parsed correctly",
"Storage: Added support for changing data location on Windows, macOS, Linux, and Android",
"Windows: Portable EXE version now stores all reading data in the directory of the executable",
"Android: Added background TTS support with media session controls",
"WebView: Extended compatibility down to version 92",
"Settings: Added global settings menu to the library page",
"iOS: Fixed an issue where custom theme colors were not handled correctly on older Safari versions",
"TTS: Added more languages for Edge TTS"
]
},
"0.9.81": {
"date": "2025-09-19",
"notes": [
"TTS: Added desktop common media control support when TTS is playing",
"PDF: Disabled swipe-up gesture for toggling the action bar when zoomed in",
"Fonts: Fixed an issue where embedded fonts in ebooks were occasionally not applied",
"Reader: Fixed an issue where newly created highlights sometimes would not appear",
"Reader: Enabled scrolling in the menu when the menu is too long"
]
},
"0.9.80": {
"date": "2025-09-19",
"notes": [
"Accessibility: Added support for popular screen readers on all platforms, making books accessible to readers with visual impairments",
"Sync: Progress is now synced across different versions of the same book by aggregated metadata",
"Library: Groups are now displayed in list view for easier organization",
"Library: Added delete confirmation when removing books from the context menu",
"Fonts: Added support for fonts with subfamilies when importing TTF/OTF font files",
"Layout: Prevented layout shift in Android when the virtual keyboard appears"
]
},
"0.9.78": {
"date": "2025-09-06",
"notes": [
"TTS: Added option to keep a persistent TTS bar at the bottom of the screen",
"TTS: Added previous/next sentence navigation controls in the TTS bar",
"TTS: Added media control support on iOS lock screen and AirPods",
"TTS: Fixed an issue where headings could not be highlighted for TTS",
"Sync: Fixed occasional sync failures between KOReader and Readest",
"Fonts: Grouped font styles into custom font families for easier selection",
"Reader: Improved text selection sensitivity when using a stylus",
"Reader: Books can now be properly closed using the system back button on Android",
"EPUB: Fixed parsing to correctly detect the front cover in some EPUB files",
"PDF: Added zoom level, zoom mode, and spread mode settings for PDFs",
"Reader: Bookmarks can now be edited directly in the sidebar",
"Layout: Prevented layout shift when the virtual keyboard appears on Android",
"TXT: Improved chapter detection and added support for nested table of contents in TXT files"
]
},
"0.9.76": {
"date": "2025-08-30",
"notes": [
"Sync: Moved KOReader sync controls into the reader sidebar with a manual push/pull option",
"Sync: Improved tolerance and reliability for KOReader sync operations",
"Sync: Added support for more KOReader server implementations",
"Fonts: Added support for importing multiple styles within a font family",
"Reader: Fixed an issue where footnotes are empty in some EPUB files",
"Theme: Applied theme background color for PDFs on macOS",
"Reader: Added support for importing AZW3 files on Android",
"Reader: Reduced screen flash when launching the app and opening books",
"Layout: Fixed an issue where the grouping modal would not appear on Linux"
]
},
"0.9.75": {
"date": "2025-08-22",
"notes": [
"Fonts: Added support for importing local TTF and ODF fonts",
"TTS: Reduced the pause between sentences when using Edge TTS",
"TTS: Fixed an issue where voices list could not be opened on older versions of iOS",
"TOC: Fixed an issue where nested TOC items would not collapse properly in some books",
"Translator: Fixed an issue where translations in the popup did not refresh correctly",
"Library: Added option to sort authors with last name first",
"Layout: Fixed mismatch in close button position between the reader and library pages",
"Layout: Book title now remains centered in the available space",
"Layout: Fixed footer bar layout in landscape mode"
]
},
"0.9.72": {
"date": "2025-08-18",
"notes": [
"Fixed Edge TTS voice playback for EPUBs",
"Reduced accidental page flips when toggling toolbars",
"Fixed images with background colors not displaying correctly in some books",
"Added support for custom KOReader Sync Servers on your local network (LAN)"
]
},
"0.9.71": {
"date": "2025-08-13",
"notes": [
"Sync: Added two ways to sync reading progress with KOReader devices",
"EPUB: Applied monospace font settings",
"EPUB: Fixed initial text alignment for some EPUB files",
"PDF: Fixed issue opening large PDF files on Android",
"TTS: Improved book language detection when language code is invalid",
"iOS: Fixed applying system color scheme in auto theme mode",
"Config: Added option to choose reading progress display style (percentage or page number)",
"Library: Added option to delete only the local copy of a book",
"Account: Added 'Reset Password' button in the account page"
]
},
"0.9.69": {
"date": "2025-08-02",
"notes": [
"Updater: Fixed permission issue when updating in standalone reader window",
"Config: Added option to disable doubleclick actions",
"EPUB: Fixed missing images in some EPUB files",
"EPUB: Corrected formatting for book subjects",
"TXT: Improved language detection when parsing TXT files",
"CSS: Resolved issue where custom font colors could not be applied",
"Layout: Corrected cover image width display in 'Fit' mode for list view",
"Layout: Fixed unintended scrolling in reader view when highlighting text"
]
},
"0.9.68": {
"date": "2025-07-30",
"notes": [
"Reader: Files now open in a new window by default on Desktop",
"Translation: Skips translating content inside <pre>, <code>, and <math> tags",
"Performance: Improved multi-part downloading for large books",
"Sync: Resolved issue preventing some PDF files from syncing",
"CSS: Resolved issue with fixed font color in some EPUB files",
"TXT: Enhanced TXT parsing for better compatibility"
]
},
"0.9.67": {
"date": "2025-07-22",
"notes": [
"Reader: Fixed file opening issue when filenames contain commas",
"Translation: Added Yandex Translator as a new translation provider",
"Layout: Hovering over header now reveals macOS traffic light window controls",
"Theme: Reader background color is now customizable for EPUB and PDF",
"Library: Search now includes book format, group names, and descriptions",
"Library: Various improvements to metadata editor and bookshelf management",
"Linux: Fixed social login failure on Linux systems"
]
},
"0.9.66": {
"date": "2025-07-21",
"notes": [
"Layout: Fixed nested table of contents items not expanding in long TOC lists",
"Reader: Opening files from the OS now takes highest priority",
"TTS: Improved voice filtering for some language codes",
"App: Fixed loading issues on older browsers",
"App: Fixed wrong MIME types generated on Linux"
]
},
"0.9.65": {
"date": "2025-07-19",
"notes": [
"Reader: Added support for opening multiple reader windows on desktop",
"Layout: Added visible window borders on Linux",
"TTS: Improved handling of invalid book language",
"TTS: Skipped reading footnote anchor links",
"Translation: Fixed an issue where table of contents entries were not translated",
"Sync: Edited book metadata is now included in sync across devices",
"iOS: Disabled context menu when long-pressing on book covers"
]
},
"0.9.64": {
"date": "2025-07-16",
"notes": [
"Library: Added support to edit book metadata",
"Layout: Fixed bookmark icon placement when the sidebar is pinned",
"Reader: Fixed an issue where links to other chapters in EPUB files did not work",
"Reader: Lightened the highlight color in dark mode for better contrast",
"Reader: Pull-to-refresh gesture is now smoother and more responsive",
"Reader: Removed unintended indentation on images",
"Search: Improved responsiveness of full-text search for faster results"
]
},
"0.9.63": {
"date": "2025-07-07",
"notes": [
"Reader: Fixed links not working in some MOBI books",
"TTS: Fixed an issue where some text was skipped during reading",
"Library: You can now delete only the cloud backup of a book without removing the local copy",
"Performance: Fonts in the font list now load only when displayed for faster startup",
"Performance: Opening large MOBI books is now significantly faster",
"Layout: Fixed overlay scrollbar display in longer TOC items list",
"Layout: Improved grid cover image height in crop mode",
"Layout: Removed unsupported screen orientation lock option on iPad",
"Layout: System context menu no longer appears when selecting text on iPad",
"Translation: Added Thai (th-TH) language support"
]
},
"0.9.62": {
"date": "2025-07-02",
"notes": [
"Subscription: You can now upgrade to Readest Premium and manage your subscription",
"Reader: Added an option to toggle Parallel Reading when viewing multiple books",
"Translation: Added an option to show or hide the original text in translations",
"Layout: TOC on Android now uses an overlay scrollbar for easier navigation",
"Layout: Custom CSS can now also be applied to the library page",
"Fonts: Added three new CJK fonts to choose from",
"Shortcuts: Press ESC to quickly close the search bar"
]
},
"0.9.61": {
"date": "2025-06-26",
"notes": [
"Layout: Fixed view menu width on smaller screens",
"Layout: Added option to crop or fit book covers in library view",
"Reader: You can now apply custom CSS to the reading interface (UI)",
"Reader: Added an option to show remaining pages in the current chapter",
"Settings: Added options to reset settings from the config dialog",
"Import: Fixed encoded filenames when importing TXT files on iOS",
"Linux: Fixed missing app icon on some Linux distributions",
"GitHub: Added donation link to support Readest development"
]
},
"0.9.60": {
"date": "2025-06-22",
"notes": [
"Layout has been improved for iPad devices",
"Screen orientation changes are now smoother on iOS",
"Importing books on iOS during the first launch is now more reliable",
"Font size no longer changes unexpectedly in scrolled mode on iOS",
"Top and bottom safe insets are now ignored when no header or footer is present",
"Interactive books now load correctly on the Desktop platform",
"Translation is now disabled when the primary language is undefined",
"Android: the task list background color has been updated with current theme color"
]
},
"0.9.59": {
"date": "2025-06-19",
"notes": [
"Layout: Image size now adjusts more accurately by converting screen units to pixels properly",
"Layout: Improved header layout on iOS landscape mode — extra spacing has been removed",
"Settings: Top and bottom margin settings are now more intuitive and better constrained"
]
},
"0.9.58": {
"date": "2025-06-18",
"notes": [
"TTS: Translation now works even when TTS is playing in the background",
"TTS: Press T on your keyboard to quickly toggle TTS on or off",
"TTS: Each open book now has its own independent TTS controller",
"Annotations: Annotation tools now function properly while TTS is active",
"Settings: Margins for top, bottom, left, and right can now be adjusted individually",
"Settings: New option to always show the status bar while reading",
"Layout: Reader page now adapts better to device safe areas for a more comfortable layout",
"UI: Added creation timestamps to annotations and made various interface improvements",
"iPad: Multiple columns are now supported in portrait mode",
"iPad: Fixed issue where sidebars couldn't be resized by dragging",
"iPad: Added support for split-screen mode"
]
},
"0.9.57": {
"date": "2025-06-14",
"notes": ["TTS: Fixed an issue to detect custom TTS engines on some Android devices"]
},
"0.9.56": {
"date": "2025-06-13",
"notes": [
"Performance: Improved TOC loading for books with very large chapter lists",
"TTS: Added support for native Android TTS engines (e.g., MultiTTS)",
"TTS: Fixed an issue where the next chapter would not load properly in the background",
"Translation: Improved accuracy and clarity of some Arabic translations",
"Reading: Added support for code syntax highlighting in books with code snippets"
]
},
"0.9.55": {
"date": "2025-06-07",
"notes": [
"Sync: Notes deleted on other devices now correctly sync across all platforms",
"Layout: Now text justification can be unset in the layout settings",
"Translation: Added notification when daily translation quota is exceeded",
"Performance: Reduced unnecessary animations on Android to improve efficiency"
]
},
"0.9.53": {
"date": "2025-06-05",
"notes": [
"Reader: New option to customize book foreground and background colors",
"Reader: Fixed an issue where images appeared too small in some EPUBs",
"EPUB: Improved cover image detection for certain EPUB files",
"TTS: Improved background playback stability on iOS devices",
"Translation: Introduced a daily DeepL translation quota to ensure long-term availability"
]
},
"0.9.52": {
"date": "2025-06-03",
"notes": [
"Library: The search bar now shows the number of books currently displayed",
"Book Details: Book descriptions now support rich HTML formatting",
"Notebook: Added support for writing notes using Markdown",
"Notebook: You can now search within your notes",
"Sync: A sync status item has been added to the View menu",
"TOC: You can now sort the table of contents by page number",
"Reader: Option added to show remaining time in the current chapter"
]
},
"0.9.51": {
"date": "2025-06-01",
"notes": [
"Translator: Table of contents is now included in translations",
"Library Theme: Added light, dark, and system theme options to the library page",
"TTS: Now shows the current sentence and chapter in the media session",
"TTS: Text-to-speech continues from the last read sentence",
"Pagination Fix: Taking notes no longer breaks page turning",
"EPUB Scripts: Added a setting to allow JavaScript in EPUB files"
]
},
"0.9.50": {
"date": "2025-05-28",
"notes": [
"Bilingual TTS: You can now choose voices for each language in bilingual text-to-speech mode",
"Settings Layout: Improved header layout in the settings dialog for different screen sizes",
"CSS Fixes: Corrected anchor text color and fixed image dimensions within paragraphs"
]
},
"0.9.49": {
"date": "2025-05-27",
"notes": [
"Select All in Library: You can now select all books at once when in selection mode",
"More Translation Options: Added support for Azure and Google Translate services",
"Dark Mode Enhancement: New option to invert image colors for better visibility in dark mode",
"Bilingual TTS: Enjoy bilingual text-to-speech for books with content in two languages",
"Full Book Translation: Translate books in any language into bilingual ebooks",
"Privacy Settings: Added a new option to opt-out of telemetry"
]
},
"0.9.43": {
"date": "2025-05-19",
"notes": [
"Enhanced compatibility with Gutenberg and Feedbooks ebooks for better styling",
"Reduced sensitivity of trackpad and mouse during page flips",
"Cloud backup status is now visible for each book on mobile and desktop",
"New option to control scroll overlap when paginating in scroll mode",
"Added Dutch language translations",
"Added more font weight options for built-in fonts",
"Added previous and next section buttons in the footer bar for easier navigation"
]
},
"0.9.41": {
"date": "2025-05-13",
"notes": [
"Improved UI and layout on mobile devices",
"Avatar images are now cached for offline use",
"Book file size is now displayed in the book details view",
"Update status is now correctly shown in the About window",
"TTS control positioning improved for notebook and RTL modes",
"TXT files are segmented by paragraph count if no chapter titles are found",
"Android: App now respects system auto-rotation settings",
"Linux: Updater is disabled on Linux when not using AppImage",
"Linux: Added support for Linux ARMHF builds",
"CSS: Resolved layout and color issues caused by hardcoded CSS"
]
},
"0.9.40": {
"date": "2025-05-08",
"notes": [
"Prevented oversized images from causing layout issues",
"Resolved issue with transient navigation bar on Android 9",
"Action bar now closes properly when the footbar is dismissed",
"Smoother experience when expanding Table of Contents sections",
"Reverted unintended style overrides"
]
},
"0.9.39": {
"date": "2025-05-07",
"notes": [
"Enhanced Navigation: Added location information to the table of contents",
"Enhanced Navigation: Larger touch targets for table of contents expansion icons",
"Screen Orientation Lock: You can now lock your device's screen orientation when reading",
"Quick Access to Recent Books: Option to automatically open your last read book when starting the app",
"Better Language Support: Improved handling of CJK characters in search",
"Enhanced Text-to-Speech: More accurate language detection for TTS",
"Enhanced Text-to-Speech: Text-to-Speech now maintains your position in scroll mode",
"Enhanced Annotations: Better handling of annotations with ruby elements in Japanese books",
"Smoother Scrolling: We've made continuous scrolling more reliable for a seamless reading experience",
"Compact View: Reading view is more compact when header and footer are hidden",
"TXT Parsing: More robust handling of TXT book format"
]
},
"0.9.38": {
"date": "2025-04-29",
"notes": [
"Page Turning with Volume Keys: You can now turn pages using your devices volume buttons",
"Expanded English Voices: More English voices are available across all en locales",
"Theme Editor: Added a primary color picker for easier customization",
"System Fonts: Retrieve system fonts on iOS and Android, with improved font weight and style detection",
"Popup Footnotes: Popup footnotes now inherit the book's font for consistent appearance",
"Better Text Selection: Preserves text selection anchors when spanning paginated content",
"Android Compatibility: Navigation bar auto-hides correctly on Android 10 and below",
"Popup Dialogs: Back key now properly dismisses popup dialogs on Android"
]
},
"0.9.37": {
"date": "2025-04-25",
"notes": [
"Full-Screen Toggle: You can now press F11 to quickly toggle full-screen mode",
"List View: A new list layout is available for your bookshelf",
"Book Details: Book descriptions from metadata are now shown in the details view",
"Enhanced Android behavior: better navigation bar handling and status bar dismissal when resuming",
"TTS now defaults to the books metadata language when needed",
"Synced custom themes reliably across sessions"
]
},
"0.9.36": {
"date": "2025-04-20",
"notes": [
"📥 New download and upload buttons in the book detail dialog for easier file management",
"📂 You can now open files directly in Readest from your file manager on Android and iOS",
"🛠️ Fixed an issue where files with quotation marks in the filename could not be opened",
"📖 Improved the immersive reading experience on the reader page for Android and iOS",
"🌐 Added GuanKiapTsingKhai-T to the list of available CJK fonts"
]
},
"0.9.35": {
"date": "2025-04-15",
"notes": [
"🛠️ Fixed an issue where books without cover images can not be synced across devices",
"📱 Added in-app update support for Android",
"📚 You can now sort books by title or author in your library"
]
},
"0.9.33": {
"date": "2025-04-13",
"notes": [
"New: Redesigned update dialog with a progress bar during downloads for a smoother update experience.",
"New: You can now set header and footer visibility separately for paginated and scroll modes.",
"Fixed layout issues in the bottom configuration panel when using RTL (right-to-left) languages.",
"Fixed TXT file import issues on Android by improving support for content provider file paths.",
"Fixed transformed punctuations when highlighting text with vertical layout.",
"Improved login flow on macOS by handling OAuth with the native ASWebAuthenticationSession."
]
},
"0.9.32": {
"date": "2025-04-09",
"notes": [
"No more resetting of font size and color when you apply custom book styles",
"The underline and squiggly highlight decorations have been adjusted to sit neatly between text lines",
"With more font options available, the font menu is no longer forced in-line",
"LXGW WenKai TC is now part of our CJK font list"
]
},
"0.9.31": {
"date": "2025-04-08",
"notes": [
"iOS: Text-to-Speech (TTS) now works in the background",
"Android: Import files from native file manager",
"Desktop: Add option to keep window on top",
"Vertical layout for highlighting tools",
"Added options to auto-stop TTS after timeout",
"Added book search functionality in bookshelf",
"Improved font support for CJK users"
]
},
"0.9.30": {
"date": "2025-04-05",
"notes": [
"Added fixed storage quota when running the app in self-hosted mode",
"Link colors will only change when you override fonts",
"Added cache for native files for better performance"
]
},
"0.9.29": {
"date": "2025-04-02",
"notes": [
"Improved Login on Android without needing to rely on external Chrome browser",
"Local large book files now load more efficiently on Android",
"Balanced the load of online service between different types of users to keep things running smoothly for everyone"
]
},
"0.9.28": {
"date": "2025-03-30",
"notes": [
"Improved vertical reading mode with better support for Chinese punctuations",
"Fixed TXT file importing issue on desktop apps — TXT books now load reliably and display as expected",
"Improved annotation experience — highlighting tools are now properly dismissed when adding notes to avoid UI conflicts"
]
},
"0.9.27": {
"date": "2025-03-29",
"notes": [
"Added option to swap click-to-flip area and reset password",
"Added global shortcuts for full-text search",
"Added support for both R2 and S3 storage in cloud sync",
"Improved TTS with fewer unintended pauses, faster startup, and support for preferred voices",
"Various fixes for rendering styles and UI layout"
]
},
"0.9.26": {
"date": "2025-03-27",
"notes": [
"Support open book from file manager without importing",
"Add LXGW WenKai font in Serif fonts list",
"Various fixes on layout and styles on Windows and Android"
]
},
"0.9.25": {
"date": "2025-03-24",
"notes": [
"Support exporting annotations in markdown format",
"Support importing TXT files",
"Support setting reader language in the UI",
"Add bottom action bar on mobile platforms",
"Various fixes on layout settings and styles"
]
},
"0.9.23": {
"date": "2025-03-19",
"notes": [
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
set -e
VERSION=$(jq -r '.version' package.json)
MANIFEST="./src-tauri/gen/android/app/src/main/AndroidManifest.xml"
INSTALL_PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
STORAGE_PERMISSION_LINE='<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>'
ised() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
return $?
}
# --- REMOVE PERMISSION BEFORE BUILD ---
if grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
echo "🧹 Removing REQUEST_INSTALL_PACKAGES from AndroidManifest.xml"
if ised "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"; then
echo "✅ Successfully removed REQUEST_INSTALL_PACKAGES"
else
echo "❌ Failed to remove REQUEST_INSTALL_PACKAGES" >&2
exit 1
fi
fi
if grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then
echo "🧹 Removing MANAGE_EXTERNAL_STORAGE from AndroidManifest.xml"
if ised "/MANAGE_EXTERNAL_STORAGE/d" "$MANIFEST"; then
echo "✅ Successfully removed MANAGE_EXTERNAL_STORAGE"
else
echo "❌ Failed to remove MANAGE_EXTERNAL_STORAGE" >&2
exit 1
fi
fi
echo "🚀 Running: pnpm tauri android build"
pnpm tauri android build --config src-tauri/tauri.playstore.conf.json
# --- ADD PERMISSION BACK AFTER BUILD ---
if ! grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
echo "♻️ Restoring REQUEST_INSTALL_PACKAGES in AndroidManifest.xml"
ised "/android.permission.INTERNET/a\\
$INSTALL_PERMISSION_LINE
" "$MANIFEST"
fi
if ! grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then
echo "♻️ Restoring MANAGE_EXTERNAL_STORAGE in AndroidManifest.xml"
ised "/android.permission.WRITE_EXTERNAL_STORAGE/a\\
$STORAGE_PERMISSION_LINE
" "$MANIFEST"
fi
source .env.google-play.local
if [[ -z "$GOOGLE_PLAY_JSON_KEY_FILE" ]]; then
echo "❌ GOOGLE_PLAY_JSON_KEY_FILE is not set"
exit 1
fi
cd ../../
fastlane android upload_production
@@ -1,3 +1,22 @@
#!/bin/bash
set -e
echo "Updating bundleVersion in tauri.appstore.conf.json..."
CONFIG_FILE="src-tauri/tauri.appstore.conf.json"
CURRENT_DATE=$(date "+%Y%m%d.%H%M%S")
if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Config file $CONFIG_FILE not found!"
exit 1
fi
TMP_FILE=$(mktemp)
cat "$CONFIG_FILE" | jq --arg version "$CURRENT_DATE" '.bundle.macOS.bundleVersion = $version' > "$TMP_FILE"
mv "$TMP_FILE" "$CONFIG_FILE"
echo "Updated bundleVersion to $CURRENT_DATE"
echo "Building macOS universal app for App Store..."
pnpm run build-macos-universial-appstore
BUNDLE_DIR=../../target/universal-apple-darwin/release/bundle/macos
+183
View File
@@ -0,0 +1,183 @@
#!/bin/bash
# Sync release notes from JSON to XML AppData file
# Usage: ./sync_release_notes.sh [json_file] [xml_file]
#
# Description:
# Extracts release information from a JSON file and updates the <releases>
# section in an AppData XML file, then calculates the SHA256 checksum.
#
# Arguments:
# json_file: Path to JSON file (default: releases.json)
# xml_file: Path to XML file (default: appdata.xml)
set -e
set -o pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Helper functions
log_info() {
echo -e "${GREEN}${NC} $1"
}
log_warn() {
echo -e "${YELLOW}${NC} $1"
}
log_error() {
echo -e "${RED}${NC} $1" >&2
}
# Check if required commands are available
if ! command -v jq >/dev/null 2>&1; then
log_error "jq is required but not installed."
echo "Install it with: apt-get install jq (Debian/Ubuntu) or brew install jq (macOS)"
exit 1
fi
# Parse arguments
JSON_FILE="${1:-releases.json}"
XML_FILE="${2:-appdata.xml}"
# Validate input files
if [ ! -f "$JSON_FILE" ]; then
log_error "JSON file '$JSON_FILE' not found"
exit 1
fi
if [ ! -f "$XML_FILE" ]; then
log_error "XML file '$XML_FILE' not found"
exit 1
fi
# Validate JSON format
if ! jq empty "$JSON_FILE" 2>/dev/null; then
log_error "Invalid JSON format in '$JSON_FILE'"
exit 1
fi
# Check if releases key exists
if ! jq -e '.releases' "$JSON_FILE" >/dev/null 2>&1; then
log_error "No 'releases' key found in JSON file"
exit 1
fi
echo "================================================"
echo "Release Notes Sync Tool"
echo "================================================"
echo "Source: $JSON_FILE"
echo "Target: $XML_FILE"
echo ""
# Create backup
BACKUP_FILE="${XML_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$XML_FILE" "$BACKUP_FILE"
log_info "Created backup: $BACKUP_FILE"
# Create temporary files
TEMP_XML=$(mktemp)
TEMP_RELEASES=$(mktemp)
# Cleanup function
cleanup() {
rm -f "$TEMP_XML" "$TEMP_RELEASES"
}
trap cleanup EXIT
# Extract releases from JSON and convert to XML format
log_info "Extracting releases from JSON..."
RELEASE_COUNT=$(jq '.releases | length' "$JSON_FILE")
echo " Found $RELEASE_COUNT releases"
jq -r '
.releases | to_entries | .[0:10] | .[] |
" <release version=\"\(.key)\" date=\"\(.value.date)\">
<description>
<ul>
" +
((.value.notes | map(" <li>\(.)</li>")) | join("\n")) +
"
</ul>
</description>
</release>"
' "$JSON_FILE" > "$TEMP_RELEASES"
# Find the releases section in XML and replace it
log_info "Updating XML file..."
awk -v releases="$TEMP_RELEASES" '
BEGIN { in_releases = 0; printed_releases = 0 }
/<releases>/ {
print $0
if (printed_releases == 0) {
while ((getline line < releases) > 0) {
print line
}
close(releases)
printed_releases = 1
}
in_releases = 1
next
}
/<\/releases>/ {
in_releases = 0
print $0
next
}
in_releases { next }
{ print }
' "$XML_FILE" > "$TEMP_XML"
# Validate the generated XML
if ! grep -q "<releases>" "$TEMP_XML" || ! grep -q "</releases>" "$TEMP_XML"; then
log_error "Failed to generate valid XML structure"
log_warn "Restoring from backup..."
mv "$BACKUP_FILE" "$XML_FILE"
exit 1
fi
# Remove backup if everything is fine
rm "$BACKUP_FILE"
# Replace original file
mv "$TEMP_XML" "$XML_FILE"
log_info "XML file updated successfully"
# Calculate SHA256 checksum
echo ""
log_info "Calculating SHA256 checksum..."
if command -v sha256sum >/dev/null 2>&1; then
CHECKSUM=$(sha256sum "$XML_FILE" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
CHECKSUM=$(shasum -a 256 "$XML_FILE" | awk '{print $1}')
else
log_error "Neither sha256sum nor shasum found"
exit 1
fi
# Save checksum to file
CHECKSUM_FILE="${XML_FILE}.sha256"
echo "$CHECKSUM $XML_FILE" > "$CHECKSUM_FILE"
echo ""
echo "================================================"
echo "Summary"
echo "================================================"
echo " Updated file: $XML_FILE"
echo " Backup file: $BACKUP_FILE"
echo " Checksum file: $CHECKSUM_FILE"
echo " SHA256: $CHECKSUM"
echo " Releases: $RELEASE_COUNT"
echo ""
log_info "Sync completed successfully!"
echo ""
echo "To verify the checksum, run:"
if command -v sha256sum >/dev/null 2>&1; then
echo " sha256sum -c $CHECKSUM_FILE"
else
echo " shasum -a 256 -c $CHECKSUM_FILE"
fi
+32 -20
View File
@@ -1,6 +1,6 @@
[package]
name = "Readest"
version = "0.2.1"
version = "0.2.2"
description = "Your online library"
authors = ["Bilingify LLC"]
license = ""
@@ -14,50 +14,62 @@ rust-version = "1.77.2"
name = "readestlib"
crate-type = ["staticlib", "cdylib", "lib"]
[features]
# Internal feature to suppress warnings from old objc crate
cargo-clippy = []
[build-dependencies]
tauri-build = { version = "2.0.3", features = [] }
tauri-build = { version = "2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
log = "0.4"
thiserror = "2"
walkdir = "2"
tokio = { version = "1", features = ["fs"] }
tokio-util = { version = "0.7", features = ["codec"] }
futures-util = "0.3"
futures = "0.3.31"
read-progress-stream = "1.0.0"
reqwest = { version = "0.12", default-features = false, features = [
"json",
"stream",
] }
# FIXME: remove the devtools feature in production
tauri = { version = "2.1.1", features = [ "protocol-asset", "devtools"] }
tauri-build = "2.0.3"
tauri-plugin-log = "2.0.2"
tauri-plugin-fs = "2.0.3"
tauri-plugin-dialog = "2.0.3"
tauri-plugin-os = "2.0.1"
tauri-plugin-http = "2.0.3"
tauri-plugin-devtools = "2.0.0"
tauri = { version = "2", features = [ "protocol-asset" ] }
tauri-build = "2"
tauri-plugin-log = "2"
tauri-plugin-fs = "2"
tauri-plugin-dialog = "2"
tauri-plugin-os = "2"
tauri-plugin-http = { version = "2", features = ["dangerous-settings"] }
tauri-plugin-shell = "2"
tauri-plugin-process = "2"
tauri-plugin-oauth = "2"
tauri-plugin-opener = "2.2.2"
tauri-plugin-opener = "2"
tauri-plugin-deep-link = "2"
tauri-plugin-sign-in-with-apple = "1.0.2"
tauri-plugin-haptics = "2.2.3"
tauri-plugin-safari-auth = { path = "./plugins/tauri-plugin-safari-auth" }
[patch.crates-io]
tauri = { path = "../../../packages/tauri/crates/tauri" }
tauri-plugin-haptics = "2"
tauri-plugin-persisted-scope = "2"
tauri-plugin-native-bridge = { path = "./plugins/tauri-plugin-native-bridge" }
tauri-plugin-native-tts = { path = "./plugins/tauri-plugin-native-tts" }
tauri-plugin-websocket = "2"
tauri-plugin-sharekit = "0.3"
[target."cfg(target_os = \"macos\")".dependencies]
rand = "0.8"
cocoa = "0.25"
objc = "0.2.7"
rand = "0.8"
objc-foundation = "0.1.1"
objc_id = "0.1.1"
block = "0.1.6"
objc2 = "0.6"
objc2-authentication-services = "0.3"
objc2-foundation = { version = "0.3", features = ["NSError", "NSArray"] }
[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies]
tauri-plugin-cli = "2"
tauri-plugin-single-instance = "2.2.0"
tauri-plugin-single-instance = "2"
tauri-plugin-updater = "2"
tauri-plugin-window-state = "2.2.0"
font-enumeration = "0.9.0"
tauri-plugin-window-state = "2"
discord-rich-presence = "1.0.0"
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict>
</plist>
+362
View File
@@ -0,0 +1,362 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIHomeIndicatorAutoHidden</key>
<true/>
<key>UIRequiresFullScreen</key>
<false/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>EPUB Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>org.idpf.epub-container</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>PDF Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>FB2 Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.readest.fb2</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>CBZ Archive</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.readest.cbz</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>MOBI Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>org.mobipocket.mobi</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>AZW Document</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.amazon.azw</string>
<string>com.amazon.azw3</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Text File</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.plain-text</string>
</array>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.idpf.epub-container</string>
<key>UTTypeDescription</key>
<string>EPUB Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.composite-content</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>epub</string>
</array>
<key>public.mime-type</key>
<string>application/epub+zip</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.adobe.pdf</string>
<key>UTTypeDescription</key>
<string>PDF Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.composite-content</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>pdf</string>
</array>
<key>public.mime-type</key>
<string>application/pdf</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.readest.fb2</string>
<key>UTTypeDescription</key>
<string>FB2 Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>fb2</string>
</array>
<key>public.mime-type</key>
<string>application/xml</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.readest.cbz</string>
<key>UTTypeDescription</key>
<string>CBZ Archive</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.archive</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cbz</string>
</array>
<key>public.mime-type</key>
<string>application/x-cbz</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.mobipocket.mobi</string>
<key>UTTypeDescription</key>
<string>MOBI Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mobi</string>
</array>
<key>public.mime-type</key>
<string>application/x-mobipocket-ebook</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.amazon.azw</string>
<key>UTTypeDescription</key>
<string>AZW Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>azw</string>
<string>azw3</string>
</array>
<key>public.mime-type</key>
<string>application/vnd.amazon.ebook</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>public.plain-text</string>
<key>UTTypeDescription</key>
<string>Text File</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>txt</string>
</array>
<key>public.mime-type</key>
<string>text/plain</string>
</dict>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key>
<string>org.idpf.epub-container</string>
<key>UTTypeDescription</key>
<string>EPUB Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>epub</string>
</array>
<key>public.mime-type</key>
<string>application/epub+zip</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.readest.fb2</string>
<key>UTTypeDescription</key>
<string>FB2 Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.xml</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>fb2</string>
</array>
<key>public.mime-type</key>
<string>application/xml</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.readest.cbz</string>
<key>UTTypeDescription</key>
<string>CBZ Archive</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.archive</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>cbz</string>
</array>
<key>public.mime-type</key>
<string>application/x-cbz</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.mobipocket.mobi</string>
<key>UTTypeDescription</key>
<string>MOBI Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mobi</string>
</array>
<key>public.mime-type</key>
<string>application/x-mobipocket-ebook</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>com.amazon.azw</string>
<key>UTTypeDescription</key>
<string>AZW Document</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>azw</string>
<string>azw3</string>
</array>
<key>public.mime-type</key>
<string>application/vnd.amazon.ebook</string>
</dict>
</dict>
</array>
</dict>
</plist>
+62
View File
@@ -1,3 +1,65 @@
use std::{env, fs, path::PathBuf, process::Command};
fn main() {
println!("cargo:rerun-if-changed=../extensions/windows-thumbnail/src");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "windows" {
build_windows_thumbnail();
}
tauri_build::build()
}
fn build_windows_thumbnail() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let dll_crate_dir = manifest_dir
.join("..")
.join("extensions")
.join("windows-thumbnail");
let dll_crate_manifest = dll_crate_dir.join("Cargo.toml");
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".into());
let mut cmd = Command::new(env::var("CARGO").unwrap_or("cargo".into()));
cmd.arg("build")
.arg("--package")
.arg("windows_thumbnail")
.arg("--manifest-path")
.arg(&dll_crate_manifest);
if profile == "release" {
cmd.arg("--release");
}
let target_triple = env::var("TARGET").unwrap_or_default();
let host_triple = env::var("HOST").unwrap_or_default();
if !target_triple.is_empty() && target_triple != host_triple {
cmd.arg("--target").arg(&target_triple);
}
let status = cmd
.status()
.expect("Failed to run cargo build for windows_thumbnail");
if !status.success() {
panic!("Failed to build windows_thumbnail DLL");
}
let dll_name = "windows_thumbnail.dll";
let candidate_paths = [
dll_crate_dir.join("target").join(&profile).join(dll_name),
dll_crate_dir
.join("target")
.join(&target_triple)
.join(&profile)
.join(dll_name),
];
let dll_src = candidate_paths
.iter()
.find(|p| p.exists())
.expect("Failed to find built windows_thumbnail DLL");
let dll_dest = &dll_crate_dir.join("target").join(dll_name);
fs::copy(dll_src, dll_dest).expect("Failed to copy windows_thumbnail DLL");
println!("cargo:rerun-if-changed={}", dll_dest.display());
}
@@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main"],
"windows": ["main", "updater", "reader-*"],
"permissions": [
"core:default",
"fs:default",
@@ -39,17 +39,84 @@
}
]
},
{
"identifier": "fs:allow-cache-read",
"allow": [
{
"path": "$APPCACHE/**/*"
},
{
"path": "/private/var/mobile/Containers/Data/Application/**/*"
}
]
},
{
"identifier": "fs:allow-cache-write",
"allow": [
{
"path": "$APPCACHE/**/*"
},
{
"path": "/private/var/mobile/Containers/Data/Application/**/*"
}
]
},
{
"identifier": "fs:scope",
"allow": [
{
"path": "**/Readest"
},
{
"path": "**/Readest/**/*"
},
{
"path": "**/last-book-cover.png"
}
]
},
{
"identifier": "http:default",
"allow": [
{
"url": "https://*.readest.com"
},
{
"url": "https://github.com/readest/*"
},
{
"url": "https://*.deepl.com"
},
{
"url": "https://*.cloudflarestorage.com"
},
{
"url": "https://edge.microsoft.com"
},
{
"url": "https://translate.toil.cc"
},
{
"url": "https://*.microsofttranslator.com"
},
{
"url": "https://translate.googleapis.com"
},
{
"url": "http://*:*"
},
{
"url": "https://*:*"
},
{
"url": "http://*"
},
{
"url": "https://*"
}
]
},
"websocket:default",
"dialog:default",
"os:default",
"core:window:default",
@@ -57,12 +124,18 @@
"core:window:allow-center",
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-unmaximize",
"core:window:allow-set-size",
"core:window:allow-set-focus",
"core:window:allow-is-maximized",
"core:window:allow-start-dragging",
"core:window:allow-toggle-maximize",
"core:window:allow-set-fullscreen",
"core:window:allow-set-always-on-top",
"core:window:allow-destroy",
"core:webview:allow-create-webview-window",
"core:path:allow-resolve-directory",
"log:default",
"shell:default",
"process:default",
"process:allow-exit",
@@ -71,11 +144,41 @@
"oauth:allow-cancel",
"sign-in-with-apple:default",
"opener:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "alipays:*"
}
]
},
{
"identifier": "shell:allow-spawn",
"allow": [
{
"name": "start-readest",
"cmd": "cmd",
"args": ["/C", "start", "", { "validator": "^.*Readest(.*)\\.exe$" }]
},
{
"name": "chmod-appimage",
"cmd": "chmod",
"args": ["+x", { "validator": "^.*Readest(.*)\\.AppImage$" }]
},
{
"name": "launch-appimage",
"cmd": "setsid",
"args": [{ "validator": "^.*Readest(.*)\\.AppImage$" }]
}
]
},
"haptics:allow-vibrate",
"haptics:allow-impact-feedback",
"haptics:allow-notification-feedback",
"haptics:allow-selection-feedback",
"safari-auth:default",
"deep-link:default"
"deep-link:default",
"sharekit:default",
"native-tts:default",
"native-bridge:default"
]
}
@@ -1,10 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop-capability",
"windows": ["main"],
"windows": ["main", "updater", "reader-*"],
"platforms": ["linux", "macOS", "windows"],
"permissions": [
"updater:default",
"cli:default"
]
"permissions": ["updater:default", "cli:default"]
}
@@ -15,28 +15,29 @@ val tauriProperties = Properties().apply {
}
android {
compileSdk = 34
compileSdk = 36
namespace = "com.bilingify.readest"
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.bilingify.readest"
minSdk = 26
targetSdk = 34
targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
}
signingConfigs {
create("release") {
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
if (keystorePropertiesFile.exists()) {
create("signing") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["password"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["password"] as String
}
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["password"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["password"] as String
}
}
buildTypes {
@@ -45,14 +46,26 @@ android {
isDebuggable = true
isJniDebuggable = true
isMinifyEnabled = false
packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("signing")
}
packaging {
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
jniLibs.keepDebugSymbols.add("*/x86/*.so")
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
}
}
getByName("release") {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("signing")
}
proguardFiles(
*fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray()
)
}
}
kotlinOptions {
@@ -68,13 +81,13 @@ rust {
}
dependencies {
implementation("androidx.webkit:webkit:1.6.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.activity:activity-ktx:1.9.3")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}
apply(from = "tauri.build.gradle.kts")
apply(from = "tauri.build.gradle.kts")
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" />
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:largeHeap="true"
android:enableOnBackInvokedCallback="false"
android:label="@string/app_name"
android:theme="@style/Theme.readest"
android:hardwareAccelerated="true"
android:usesCleartextTraffic="${usesCleartextTraffic}">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:launchMode="singleTask"
android:label="@string/main_activity_title"
android:hardwareAccelerated="true"
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:scheme="file" />
<data android:mimeType="application/epub+zip" />
<data android:mimeType="application/pdf" />
<data android:mimeType="application/fb2" />
<data android:mimeType="application/x-fb2" />
<data android:mimeType="application/mobi"/>
<data android:mimeType="application/azw"/>
<data android:mimeType="application/azw3"/>
<data android:mimeType="application/x-mobipocket-ebook" />
<data android:mimeType="application/vnd.amazon.ebook" />
<data android:mimeType="application/vnd.amazon.mobi8-ebook" />
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/zip" />
<data android:mimeType="text/plain" />
<data android:mimeType="application/octet-stream"/>
<data android:mimeType="application/x-font-ttf"/>
<data android:mimeType="application/x-font-otf"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.epub" />
<data android:pathPattern=".*\\.pdf" />
<data android:pathPattern=".*\\.fb2" />
<data android:pathPattern=".*\\.fb2.zip" />
<data android:pathPattern=".*\\.mobi" />
<data android:pathPattern=".*\\.azw" />
<data android:pathPattern=".*\\.azw3" />
<data android:pathPattern=".*\\.cbz" />
<data android:pathPattern=".*\\.zip" />
<data android:pathPattern=".*\\.txt" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter android:label="oauth">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="readest" android:host="auth-callback" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- AndroidTV support -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
<intent-filter android:autoVerify="true" >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:host="web.readest.com" />
</intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
</activity>
<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<service
android:name="com.readest.native_tts.MediaPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
@@ -2,32 +2,258 @@ package com.bilingify.readest
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.KeyEvent
import android.view.MotionEvent
import android.webkit.WebView
import android.net.Uri
import android.util.Log
import android.content.Intent
import android.graphics.Color
import android.app.ActivityManager
import android.content.res.Configuration
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.enableEdgeToEdge
import androidx.activity.OnBackPressedCallback
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import com.readest.native_bridge.KeyDownInterceptor
import com.readest.native_bridge.NativeBridgePlugin
class MainActivity : TauriActivity(), KeyDownInterceptor {
private var wv: WebView? = null
private var interceptVolumeKeysEnabled = false
private var interceptBackKeyEnabled = false
override fun onWebViewCreate(webView: WebView) {
wv = webView
}
private val keyEventMap = mapOf(
KeyEvent.KEYCODE_BACK to "Back",
KeyEvent.KEYCODE_VOLUME_DOWN to "VolumeDown",
KeyEvent.KEYCODE_VOLUME_UP to "VolumeUp"
)
override fun interceptVolumeKeys(enabled: Boolean) {
Log.d("MainActivity", "Intercept volume keys: $enabled")
interceptVolumeKeysEnabled = enabled
}
override fun interceptBackKey(enabled: Boolean) {
Log.d("MainActivity", "Intercept back key: $enabled")
interceptBackKeyEnabled = enabled
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val action = when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> "touchstart"
MotionEvent.ACTION_UP -> "touchend"
MotionEvent.ACTION_CANCEL -> "touchcancel"
MotionEvent.ACTION_POINTER_DOWN -> "touchstart"
MotionEvent.ACTION_POINTER_UP -> "touchend"
else -> null
}
action?.let { eventType ->
val pointerIndex = event.actionIndex
val pointerId = event.getPointerId(pointerIndex)
val x = event.getX(pointerIndex)
val y = event.getY(pointerIndex)
val pressure = event.getPressure(pointerIndex)
wv?.evaluateJavascript(
"""
try {
if (window.onNativeTouch) {
window.onNativeTouch({
type: "$eventType",
pointerId: $pointerId,
x: $x,
y: $y,
pressure: $pressure,
pointerCount: ${event.pointerCount},
timestamp: ${event.eventTime}
});
}
} catch (err) {
console.error('Native touch error:', err);
}
""".trimIndent(),
null
)
}
return super.dispatchTouchEvent(event)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
val keyCode = event.keyCode
val keyName = keyEventMap[keyCode]
if (keyName != null) {
val shouldIntercept = when (keyCode) {
KeyEvent.KEYCODE_BACK -> interceptBackKeyEnabled
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> interceptVolumeKeysEnabled
else -> false
}
if (shouldIntercept) {
wv?.evaluateJavascript(
"""
try { window.onNativeKeyDown("$keyName", $keyCode); } catch (_) {}
""".trimIndent(),
null
)
return true
}
}
}
return super.dispatchKeyEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
val keyName = keyEventMap[keyCode]
if (keyName != null) {
wv?.evaluateJavascript(
"""
try {
window.onNativeKeyDown("$keyName", $keyCode)
} catch (err) {
false
}
""".trimIndent()
) { result ->
run {
if (result.equals("true", ignoreCase = true)) {
Log.d("Key Event", "Key event $keyName intercepted")
}
}
}
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (interceptVolumeKeysEnabled) {
true
} else {
super.onKeyDown(keyCode, event)
}
}
KeyEvent.KEYCODE_BACK -> {
if (interceptBackKeyEnabled) {
true
} else {
super.onKeyDown(keyCode, event)
}
}
else -> super.onKeyDown(keyCode, event)
}
}
return super.onKeyDown(keyCode, event)
}
class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
hideSystemUI()
handleIncomingIntent(intent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setTaskDescription(
ActivityManager.TaskDescription(
getString(R.string.app_name),
null,
Color.TRANSPARENT
)
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
OnBackInvokedCallback {
Log.d("MainActivity", "Back invoked callback triggered ${interceptBackKeyEnabled}")
if (interceptBackKeyEnabled) {
Log.d("MainActivity", "Back intercepted (OnBackInvokedCallback)")
wv?.evaluateJavascript(
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
null
)
} else {
finish()
}
}
)
}
onBackPressedDispatcher.addCallback(this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (interceptBackKeyEnabled) {
Log.d("MainActivity", "Back intercepted (OnBackPressedDispatcher)")
wv?.evaluateJavascript(
"""window.onNativeKeyDown("Back", ${KeyEvent.KEYCODE_BACK});""",
null
)
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
}
)
}
private fun hideSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.decorView.windowInsetsController?.let { controller ->
controller.hide(WindowInsets.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
NativeBridgePlugin.getInstance()?.handleActivityResult(requestCode, resultCode, data)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent?.let { handleIncomingIntent(it) }
}
private fun handleIncomingIntent(intent: Intent) {
when (intent.action) {
Intent.ACTION_SEND -> {
if (intent.type != null) {
handleSingleFile(intent)
}
}
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
)
Intent.ACTION_SEND_MULTIPLE -> {
if (intent.type != null) {
handleMultipleFiles(intent)
}
}
}
}
private fun handleSingleFile(intent: Intent) {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
uri?.let { fileUri ->
val payload = JSObject().apply {
var urls = JSArray()
urls.put(fileUri.toString())
put("urls", urls)
}
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
}
}
private fun handleMultipleFiles(intent: Intent) {
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
uris?.let { fileUris ->
val payload = JSObject().apply {
var urls = JSArray()
fileUris.forEach { urls.put(it.toString()) }
put("urls", urls)
}
NativeBridgePlugin.getInstance()?.triggerEvent("shared-intent", payload)
}
}
}
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,8 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="#FF323130" />
</item>
<item android:gravity="center" android:width="120dp" android:height="120dp">
<bitmap android:src="@drawable/splash_icon" />
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@mipmap/ic_launcher_foreground"
android:inset="22%"/>
</foreground>
</adaptive-icon>
@@ -2,8 +2,13 @@
<!-- Base application theme. -->
<style name="Theme.readest" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@drawable/splash_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:fitsSystemWindows">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>
@@ -0,0 +1,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Some files were not shown because too many files have changed in this diff Show More