Archived
feat: uv config other feats
- add uv configuration for the backend - update frontend to make auth work - add new auth endpoints - add bookmars feat - add reader feat
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
# US: Customizable Mobile Reading Experience
|
||||
|
||||
**Issue:** https://gitea-dev.codescripters.org/HermesFactory/cloud-reader/issues (TBD)
|
||||
|
||||
## Overview
|
||||
|
||||
Add a full-screen reading view for ebooks with customizable typography, themes,
|
||||
table of contents navigation, and orientation support. Mobile-first, responsive
|
||||
design that adapts to any screen size.
|
||||
|
||||
---
|
||||
|
||||
## Backend Specification
|
||||
|
||||
### New Models
|
||||
|
||||
#### `apps.books.models.Chapter`
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------------|--------------------|--------------------------------|
|
||||
| id | AutoField (PK) | |
|
||||
| book | FK -> Book | related_name="chapters" |
|
||||
| title | CharField(512) | Chapter title |
|
||||
| number | PositiveIntegerField | Chapter ordering / TOC index |
|
||||
| content | TextField | Chapter text/markdown content |
|
||||
| created_at | DateTimeField | auto_now_add |
|
||||
| updated_at | DateTimeField | auto_now |
|
||||
|
||||
**Constraints:** UniqueConstraint(book, chapter_number)
|
||||
**Ordering:** [book, number]
|
||||
**Index:** FK to book with db_index
|
||||
|
||||
#### `apps.books.models.ReadingProgress`
|
||||
|
||||
| Field | Type | Notes |
|
||||
|------------------|--------------------|--------------------------------|
|
||||
| id | AutoField (PK) | |
|
||||
| user | FK -> User | related_name="reading_progress"|
|
||||
| book | FK -> Book | related_name="reading_progress"|
|
||||
| current_chapter | PositiveIntegerField | Last chapter number |
|
||||
| current_position | PositiveIntegerField | Position within chapter (paragraph) |
|
||||
| percentage | FloatField | 0.0 - 100.0 overall progress |
|
||||
| updated_at | DateTimeField | auto_now |
|
||||
|
||||
**Constraints:** UniqueConstraint(user, book)
|
||||
**Indexes:** (user, book) composite, (user) filter for list queries
|
||||
|
||||
#### `apps.reader.models.ReadingSettings`
|
||||
|
||||
New app `apps/reader/` for reading preferences, isolated from book data model.
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------------------|--------------------|-------------------------------|
|
||||
| id | AutoField (PK) | |
|
||||
| user | OneToOneField -> User | related_name="reading_settings" |
|
||||
| font_family | CharField(32) | "sans-serif", "serif", "monospace" |
|
||||
| font_size | PositiveSmallIntegerField | 12-32, default 18 |
|
||||
| line_height | FloatField | 1.2 - 2.0, default 1.6 |
|
||||
| margin_width | PositiveSmallIntegerField | 8-48, default 16 (px) |
|
||||
| background_color | CharField(7) | Hex color, default "#f5f0eb" |
|
||||
| text_color | CharField(7) | Hex color, default "#1a1a1a" |
|
||||
| brightness | PositiveSmallIntegerField | 0-100, default 100 |
|
||||
| orientation_lock | CharField(16) | "auto", "portrait", "landscape" |
|
||||
| theme | CharField(32) | "sepia", "dark", "light", "paper" |
|
||||
| created_at | DateTimeField | auto_now_add |
|
||||
| updated_at | DateTimeField | auto_now |
|
||||
|
||||
### New API Endpoints
|
||||
|
||||
All under `/api/` prefix, authenticated with JWT.
|
||||
|
||||
#### Reader Settings (`/api/reader/settings/`)
|
||||
|
||||
| Method | URL | Action |
|
||||
|--------|------------------------------|---------------------------|
|
||||
| GET | /api/reader/settings/ | Get current user settings |
|
||||
| PUT | /api/reader/settings/ | Create/update settings |
|
||||
| PATCH | /api/reader/settings/ | Partial update settings |
|
||||
|
||||
- Single-object endpoint (one settings record per user, auto-created on first GET)
|
||||
- Validation: font_size 12-32, line_height 1.2-2.0, margin_width 8-48
|
||||
|
||||
#### Reading Progress (`/api/books/{id}/progress/`)
|
||||
|
||||
| Method | URL | Action |
|
||||
|--------|----------------------------------------|------------------------------|
|
||||
| GET | /api/books/{id}/progress/ | Get reading progress for book|
|
||||
| PUT | /api/books/{id}/progress/ | Create/update reading progress|
|
||||
|
||||
- Nested under book detail
|
||||
- Auto-creates progress record on first PUT
|
||||
|
||||
#### Chapters (`/api/books/{id}/chapters/`)
|
||||
|
||||
| Method | URL | Action |
|
||||
|--------|----------------------------------------|------------------------------|
|
||||
| GET | /api/books/{id}/chapters/ | List chapters for book (TOC) |
|
||||
| GET | /api/books/{id}/chapters/{number}/ | Get specific chapter content |
|
||||
|
||||
- Ordering by `number`
|
||||
- Used by frontend TOC sidebar and content loading
|
||||
|
||||
---
|
||||
|
||||
## Frontend Specification
|
||||
|
||||
### New Pages
|
||||
|
||||
#### `/reader/:bookId` — ReadingPage
|
||||
|
||||
Full-screen reading view with:
|
||||
- Chapter content display (left/right swiping or scroll)
|
||||
- Bottom toolbar: TOC toggle, Settings toggle, Progress indicator
|
||||
- Top bar: Back button, Book title, Chapter title
|
||||
- Swipe/tap/page navigation between chapters
|
||||
|
||||
### New Components
|
||||
|
||||
#### `ReaderToolbar`
|
||||
- Fixed bottom toolbar
|
||||
- TOC button (opens TOC drawer)
|
||||
- Settings/theme button (opens settings panel)
|
||||
- Progress bar showing overall reading progress
|
||||
|
||||
#### `TableOfContents`
|
||||
- Slide-in drawer from left
|
||||
- Lists all chapters with current chapter highlighted
|
||||
- Tap on chapter to navigate
|
||||
- Shows reading progress per chapter
|
||||
|
||||
#### `ReadingSettingsPanel`
|
||||
- Slide-in drawer from right (or bottom sheet on mobile)
|
||||
- Controls:
|
||||
- Theme presets: Sepia, Dark, Light, Paper
|
||||
- Font family: Sans-serif, Serif, Monospace
|
||||
- Font size slider (12-32)
|
||||
- Line height slider (1.2-2.0)
|
||||
- Margin/padding control
|
||||
- Orientation lock toggle (Auto / Portrait / Landscape)
|
||||
- All changes persist immediately via API
|
||||
- LocalStorage fallback when offline
|
||||
|
||||
### New Hooks
|
||||
|
||||
#### `useReadingSettings(bookId)`
|
||||
- Fetches user reading settings from API
|
||||
- Returns current settings + update function
|
||||
- Applies CSS custom properties to document root
|
||||
- Falls back to defaults if API unavailable
|
||||
|
||||
#### `useChapters(bookId)`
|
||||
- Fetches chapter list for TOC
|
||||
- Returns chapters array, current chapter, navigate function
|
||||
- Prefetches next/prev chapter content
|
||||
|
||||
#### `useReadingProgress(bookId)`
|
||||
- Fetches/updates reading progress
|
||||
- Auto-saves position on chapter change and periodic interval
|
||||
|
||||
### New Types
|
||||
|
||||
```typescript
|
||||
interface Chapter {
|
||||
id: number;
|
||||
book: number;
|
||||
title: string;
|
||||
number: number;
|
||||
content?: string; // Only present when fetching individual chapter
|
||||
}
|
||||
|
||||
interface ChapterSummary {
|
||||
id: number;
|
||||
book: number;
|
||||
title: string;
|
||||
number: number;
|
||||
}
|
||||
|
||||
interface ReadingSettings {
|
||||
font_family: "sans-serif" | "serif" | "monospace";
|
||||
font_size: number;
|
||||
line_height: number;
|
||||
margin_width: number;
|
||||
background_color: string;
|
||||
text_color: string;
|
||||
brightness: number;
|
||||
orientation_lock: "auto" | "portrait" | "landscape";
|
||||
theme: "sepia" | "dark" | "light" | "paper";
|
||||
}
|
||||
|
||||
interface ReadingProgress {
|
||||
current_chapter: number;
|
||||
current_position: number;
|
||||
percentage: number;
|
||||
updated_at: string;
|
||||
}
|
||||
```
|
||||
|
||||
### CSS / Theming
|
||||
|
||||
Reading view uses CSS custom properties driven by reading settings:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--reader-bg: var(--bg-color, #f5f0eb);
|
||||
--reader-text: var(--text-color, #1a1a1a);
|
||||
--reader-font-family: var(--font-family, "Georgia", serif);
|
||||
--reader-font-size: var(--font-size, 18px);
|
||||
--reader-line-height: var(--line-height, 1.6);
|
||||
--reader-margin: var(--margin-width, 16px);
|
||||
}
|
||||
```
|
||||
|
||||
Three theme presets:
|
||||
- **Sepia**: `bg:#f5f0eb`, `text:#1a1a1a` — warm, easy on eyes
|
||||
- **Dark**: `bg:#1a1a2e`, `text:#e0e0e0` — for low-light reading
|
||||
- **Light**: `bg:#ffffff`, `text:#1a1a1a` — crisp and clean
|
||||
- **Paper**: `bg:#e8e0d4`, `text:#2c2c2c` — book-like feel
|
||||
|
||||
### Orientation Support
|
||||
|
||||
- CSS `@media (orientation: portrait)` and `@media (orientation: landscape)` breakpoints
|
||||
- Reading settings panel includes orientation lock toggle
|
||||
- On mobile, landscape mode expands content horizontally with wider margins
|
||||
- Portrait mode stacks controls vertically for thumb-reachable UI
|
||||
|
||||
### Routing
|
||||
|
||||
Add to App.tsx:
|
||||
```
|
||||
/ → LibraryPage
|
||||
/books/:bookId → BookDetailPage
|
||||
/reader/:bookId → ReadingPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Backend models + migrations (Chapter, ReadingProgress, ReadingSettings)
|
||||
2. Backend serializers + views + URLs
|
||||
3. Frontend types + API client
|
||||
4. Frontend hooks (useReadingSettings, useChapters, useReadingProgress)
|
||||
5. Frontend components (ReadingSettingsPanel, TableOfContents, ReaderToolbar)
|
||||
6. Frontend page (ReadingPage)
|
||||
7. Routing updates
|
||||
8. CSS / theming
|
||||
@@ -0,0 +1,73 @@
|
||||
# 011 — Web reader (react-reader)
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-03
|
||||
|
||||
## Objective
|
||||
|
||||
Replace the custom HTML chapter reader with [react-reader](https://github.com/gerhardsletten/react-reader) (epub.js) for paginated EPUB reading in the web app. Keep the existing toolbar and settings panel chrome. Block PDFs from the in-browser reader.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Library (EPUB only) → /read/:id → ReadingPage
|
||||
→ GET /api/books/ebooks/{id}/ (metadata, format guard)
|
||||
→ GET /api/books/ebooks/{id}/file/ (authenticated EPUB blob)
|
||||
→ ReactReader (blob URL + CFI location)
|
||||
→ PATCH /api/books/ebooks/{id}/progress/ (epub_location + percentage)
|
||||
```
|
||||
|
||||
EPUB files are fetched with JWT via the API, converted to a blob URL client-side, and passed to react-reader. This avoids unauthenticated `/media/` URLs and CORS issues in dev.
|
||||
|
||||
## Backend changes
|
||||
|
||||
### `GET /api/books/ebooks/{id}/file/`
|
||||
|
||||
- Authenticated, owner-only
|
||||
- Returns `FileResponse` with `Content-Type: application/epub+zip`
|
||||
- Returns `400` if format is not `epub`
|
||||
|
||||
### `ReadingProgress.epub_location`
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `epub_location` | CharField(2048) | EPUB CFI string for resume position |
|
||||
|
||||
Exposed on `GET/PATCH /api/books/ebooks/{id}/progress/` and in `EBookDetail.progress`.
|
||||
|
||||
`current_position` stores whole-book percentage (0–100) derived from epub.js locations.
|
||||
|
||||
## Frontend changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `pages/ReadingPage.tsx` | `ReactReader` replaces chapter HTML rendering |
|
||||
| `hooks/useEpubReader.ts` | Blob load, CFI state, debounced progress save |
|
||||
| `utils/epubRendition.ts` | Theme/font application via `getRendition` |
|
||||
| `api/books.ts` | `getEpubFile(id)` |
|
||||
| `pages/Library.tsx` | Block PDF open with notice |
|
||||
| `App.tsx` | Single route `/read/:id`; `/reader/:id` redirects |
|
||||
|
||||
### Removed (web-only)
|
||||
|
||||
- `useChapters.ts`, `TableOfContents.tsx`, `Reader.tsx`, `useReadingProgress.ts`
|
||||
|
||||
Backend `/toc/` and `/content/` endpoints remain for mobile/API consumers.
|
||||
|
||||
## EPUB-only policy
|
||||
|
||||
- Library: clicking a PDF shows a dismissible notice; no navigation to reader
|
||||
- ReadingPage: deep-link guard if `format !== 'epub'`
|
||||
- Upload still accepts PDF for storage; web reader is EPUB-only
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Frontend: `react-reader` (^2.0.15)
|
||||
|
||||
## Verification
|
||||
|
||||
1. Open an EPUB from library → paginated reading, swipe/tap page turns, built-in TOC
|
||||
2. Close and reopen → resumes at saved CFI
|
||||
3. Change theme/font in settings → applies inside epub iframe
|
||||
4. Click a PDF in library → notice shown, reader not opened
|
||||
5. `GET /api/books/ebooks/{id}/file/` without auth → 401
|
||||
@@ -0,0 +1,56 @@
|
||||
# 012 — Library book context menu
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-03
|
||||
|
||||
## Objective
|
||||
|
||||
Add a custom right-click context menu on each book card in the library with **Sync metadata** (Open Library refresh) and **Remove** (delete from library). Show toast notifications for success, warning, and error outcomes.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Library book card (contextmenu)
|
||||
→ BookContextMenu
|
||||
→ Sync metadata: POST /api/books/ebooks/{id}/enrich-metadata/
|
||||
→ Remove: confirm dialog → DELETE /api/books/ebooks/{id}/
|
||||
→ ToastProvider (app-wide) → success | warning | error toasts
|
||||
```
|
||||
|
||||
Backend endpoints already exist; no API changes required.
|
||||
|
||||
## Frontend changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `api/books.ts` | `enrichEBookMetadata(id)` → `POST .../enrich-metadata/` |
|
||||
| `types/book.ts` | `OpenLibraryMetadata` with `match_status` |
|
||||
| `hooks/useToast.tsx` | `ToastProvider`, `showToast({ message, variant })`, auto-dismiss ~4s |
|
||||
| `components/ToastContainer.tsx` | Fixed bottom-right toast stack |
|
||||
| `components/BookContextMenu.tsx` | Positioned menu; sync + remove actions |
|
||||
| `pages/Library.tsx` | `onContextMenu` on cards; local state updates |
|
||||
| `App.tsx` | Wrap routes with `ToastProvider` |
|
||||
|
||||
## Context menu behavior
|
||||
|
||||
- Opens on right-click (`contextmenu`); browser default menu suppressed
|
||||
- Position clamped to viewport; closes on outside click, Escape, or scroll
|
||||
- **Sync metadata:** updates card title/author/cover from response; toast by `metadata.match_status`:
|
||||
- `matched` → success
|
||||
- `not_found` → warning
|
||||
- other → neutral success
|
||||
- **Remove:** `window.confirm` before delete; removes card from list on success
|
||||
- Menu clicks do not trigger card navigation to reader
|
||||
|
||||
## Dependencies
|
||||
|
||||
None (no new npm packages).
|
||||
|
||||
## Verification
|
||||
|
||||
1. Right-click a book → menu appears at cursor
|
||||
2. Sync metadata on a matched book → cover/title may update, success toast
|
||||
3. Sync on unmatched book → warning toast, no crash
|
||||
4. Remove → confirm → book disappears, success toast
|
||||
5. Cancel remove → book stays, menu closes
|
||||
6. Left-click still opens reader (EPUB) or PDF notice
|
||||
@@ -0,0 +1,54 @@
|
||||
# 013 — Frontend i18n (react-i18n-lite)
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-04
|
||||
|
||||
## Objective
|
||||
|
||||
Internationalize the web UI in English (`en-US`) and Spanish (`es-ES`) using [react-i18n-lite](https://www.npmjs.com/package/react-i18n-lite).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
App.tsx
|
||||
└── I18nProvider (TranslationContainer)
|
||||
├── resolveDefaultLanguage() — localStorage → navigator → en-US
|
||||
├── LanguagePersistence — sync setLanguage ↔ localStorage, html[lang]
|
||||
└── AuthProvider → ToastProvider → routes
|
||||
```
|
||||
|
||||
Components call `useTranslation()` and `t('namespace.key', { interpolation })`.
|
||||
|
||||
## Locale files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `frontend/src/locales/en-US.ts` | English dictionary |
|
||||
| `frontend/src/locales/es-ES.ts` | Spanish dictionary |
|
||||
| `frontend/src/locales/index.ts` | `locales` map, `SupportedLanguage`, helpers |
|
||||
|
||||
Key namespaces: `common`, `auth`, `library`, `contextMenu`, `toast`, `settings`, `reader`, `addBook`, `bookDetail`, `search`, `annotations`.
|
||||
|
||||
## Language selection
|
||||
|
||||
- **Settings page** — UI Language dropdown (English / Español)
|
||||
- **Persistence** — `localStorage` key `cloud-reader.locale`
|
||||
- **Default** — saved preference, else `navigator.language` (`es*` → `es-ES`), else `en-US`
|
||||
|
||||
## Scope
|
||||
|
||||
**Translated:** All user-facing chrome (library, auth, reader toolbar/settings/TOC, add book, book detail, annotations, context menu, toasts).
|
||||
|
||||
**Not translated:** EPUB body content, API error bodies, backend `reading_status_display`, Open Library metadata fields.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `react-i18n-lite` (^1.0.10)
|
||||
|
||||
## Verification
|
||||
|
||||
1. Open app — labels match browser or saved language
|
||||
2. Settings → switch to Español — library/auth update without reload
|
||||
3. Refresh — language persists
|
||||
4. Context menu and toasts show translated strings
|
||||
5. `yarn build` succeeds
|
||||
@@ -0,0 +1,46 @@
|
||||
# 014 — Library reading progress badges
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-04
|
||||
|
||||
## Objective
|
||||
|
||||
Show accurate reading state on library book cards using saved EPUB progress instead of always displaying "Want to Read" (`Quiero leer`).
|
||||
|
||||
## Data source
|
||||
|
||||
- `GET /api/books/ebooks/` returns `progress` (0–100) from `ReadingProgress.current_position` via `EBookListSerializer.get_progress`.
|
||||
- Progress is updated by the web reader (`useEpubReader` → `PATCH .../progress/`).
|
||||
|
||||
## Display rules
|
||||
|
||||
| Progress | `opened` | Badge | Cover extra |
|
||||
|----------|----------|-------|-------------|
|
||||
| `null` or `0` | false | Want to Read | — |
|
||||
| `0` | true | Opened | — |
|
||||
| `1–98` | — | Reading · N% | Green progress bar on cover |
|
||||
| `≥ 99` | — | Finished | — |
|
||||
|
||||
**Opened** is set when the reader first displays (CFI saved, 0% progress). Turning pages moves to **Reading**.
|
||||
|
||||
Threshold constant: `FINISHED_PROGRESS_THRESHOLD = 99` in `frontend/src/utils/libraryStatus.ts`.
|
||||
|
||||
## Frontend changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `utils/libraryStatus.ts` | `deriveReadingStatus`, `normalizeProgressPercent` |
|
||||
| `pages/Library.tsx` | Map API progress → status; badge labels; client-side status filter |
|
||||
| `locales/en-US.ts`, `es-ES.ts` | `library.readingStatus.readingWithProgress` |
|
||||
| `pages/Library.module.css` | Cover progress bar styles |
|
||||
|
||||
## Status filter
|
||||
|
||||
The reading-status filter in the library panel now filters client-side by derived status (`want_to_read`, `reading`, `finished`).
|
||||
|
||||
## Verification
|
||||
|
||||
1. New upload → **Want to Read** / **Quiero leer**
|
||||
2. Partial read → **Reading · N%** + progress bar
|
||||
3. Near end (≥ 99%) → **Finished** / **Terminado**
|
||||
4. Filters by Leyendo / Terminado work as expected
|
||||
@@ -0,0 +1,83 @@
|
||||
# 015 — EPUB bookmarks and notes (physical-book UX)
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-04
|
||||
|
||||
## Objective
|
||||
|
||||
Let users select passage text while reading an EPUB, save a bookmark (with optional thought), list markers in chapter order, and jump back to the exact location. Global view groups markers by book in a Reddit-style thread layout.
|
||||
|
||||
## Fix: AnnotationsProvider
|
||||
|
||||
`AnnotationsProvider` wraps all routes in [`App.tsx`](frontend/src/App.tsx) so `/bookmarks-notes` and the reader can use `useAnnotations()`.
|
||||
|
||||
## Data model
|
||||
|
||||
`Bookmark` (annotations app) references **`books.EBook`**, not catalog `Book`:
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| `ebook` | FK to uploaded ebook |
|
||||
| `epub_cfi` | EPUB CFI anchor |
|
||||
| `chapter_index` | Spine index for sort order |
|
||||
| `chapter_title` | Display label |
|
||||
| `location_text` | Selected passage quote |
|
||||
| `content` | Optional user thought (empty = bookmark only) |
|
||||
| `page` | Legacy display field (`chapter_index + 1`) |
|
||||
|
||||
Unique: `(user, ebook, epub_cfi)`.
|
||||
|
||||
Default API ordering: `chapter_index`, `epub_cfi`.
|
||||
|
||||
Legacy `Note` model remains for old API; new UX uses `Bookmark.content` only.
|
||||
|
||||
## Reader flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant EpubView
|
||||
participant Popover
|
||||
participant API
|
||||
User->>EpubView: Select text
|
||||
EpubView->>Popover: Show near selection
|
||||
User->>Popover: Save optional thought
|
||||
Popover->>API: POST /annotations/bookmarks/
|
||||
```
|
||||
|
||||
1. Text selection via epub.js `selected` event and content `mouseup` hook.
|
||||
2. [`SelectionPopover`](frontend/src/components/reader/SelectionPopover.tsx) — floating UI, optional textarea.
|
||||
3. Toolbar bookmark icon opens [`BookMarkersPanel`](frontend/src/components/reader/BookMarkersPanel.tsx) (current ebook only).
|
||||
4. “Go to passage” sets reader `location` to stored CFI (`/read/:id` with router state).
|
||||
|
||||
## Global page (`/bookmarks-notes`)
|
||||
|
||||
[`MarkerThreadsView`](frontend/src/components/annotations/MarkerThreadsView.tsx):
|
||||
|
||||
- Groups markers by ebook (collapsible book rows).
|
||||
- Within each book: chapter order, passage as blockquote, thought indented below (Reddit-style).
|
||||
- Optional filter: `/bookmarks-notes/:ebookId`.
|
||||
|
||||
## API
|
||||
|
||||
- `GET /api/annotations/bookmarks/?ebook={id}`
|
||||
- `POST /api/annotations/bookmarks/` — body: `ebook`, `epub_cfi`, `chapter_index`, `chapter_title`, `location_text`, `content`
|
||||
|
||||
## i18n
|
||||
|
||||
New keys under `annotations.*` (EN/ES): `saveMarker`, `thoughtPlaceholder`, `bookmarkOnly`, `selectTextHint`, `goToPassage`, `inBookPanel`, `markerCount`, etc.
|
||||
|
||||
## Verification
|
||||
|
||||
1. `/bookmarks-notes` loads without provider error.
|
||||
2. Select text in reader → popover → save with/without thought.
|
||||
3. Markers appear in reader panel and global page under correct book, in chapter order.
|
||||
4. “Go to passage” opens the correct location.
|
||||
|
||||
See also [016 — Bookmark reading anchor](016-bookmark-reading-anchor.md) for preserving reading position while peeking at bookmarks.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- PDF selection
|
||||
- Multiple replies per passage
|
||||
- Migrating legacy `Note` rows into `Bookmark`
|
||||
@@ -0,0 +1,87 @@
|
||||
# 016 — Bookmark reading anchor
|
||||
|
||||
**Status:** Implemented
|
||||
**Created:** 2026-06-04
|
||||
**See also:** [015 — EPUB bookmarks and notes](015-epub-bookmarks-notes.md)
|
||||
|
||||
## Objective
|
||||
|
||||
Let users jump to a bookmark to review a passage without overwriting their true reading position. While peeking, show a high-visibility control to return to where they were reading.
|
||||
|
||||
## Definitions
|
||||
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| **Reading anchor** | EPUB CFI (+ optional %) captured immediately before a bookmark peek |
|
||||
| **Peek mode** | Temporary view at a bookmark location; server `ReadingProgress` is not updated |
|
||||
| **Resume** | Jump back to the reading anchor and re-enable progress persistence |
|
||||
|
||||
## Problem (before)
|
||||
|
||||
“Go to passage” called `jumpToCfi`, which triggered `locationChanged` and debounced `PATCH /books/ebooks/{id}/progress/`, replacing `epub_location` and `current_position` with the bookmark. Library progress and the next reading session started at the bookmark instead of the real position.
|
||||
|
||||
## Triggers (enter peek mode)
|
||||
|
||||
- In-reader: **Go to passage** in [`BookMarkersPanel`](../frontend/src/components/reader/BookMarkersPanel.tsx)
|
||||
- Global: **Go to passage** on [`/bookmarks-notes`](../frontend/src/components/annotations/BookmarksNotesPage.tsx) → `/read/:id` with `state.epubLocation` (bookmark CFI)
|
||||
|
||||
## Non-triggers
|
||||
|
||||
- Table of contents navigation
|
||||
- Prev / next page buttons
|
||||
- Creating a new bookmark from text selection
|
||||
- Opening the book normally from the library (no `epubLocation` in router state)
|
||||
|
||||
## UX
|
||||
|
||||
### Resume control
|
||||
|
||||
- Component: [`ResumeReadingButton`](../frontend/src/components/reader/ResumeReadingButton.tsx)
|
||||
- Visible only when `isBookmarkPeekActive && readingAnchor != null`
|
||||
- Position: right edge, **above** the next-page chevron (`.reader-page-nav--next`)
|
||||
- Style: warm accent (`#ea580c` / `#f97316`), white label/icon; distinct from muted gray nav buttons
|
||||
- Action: `resumeReadingAnchor()` — hides control, returns to anchor CFI
|
||||
- i18n: `reader.resumeReading`, `reader.resumeReadingAria`
|
||||
|
||||
### Anchor policy
|
||||
|
||||
- First anchor is captured when peek starts; **additional “Go to passage” clicks while peeking do not replace the anchor** until the user resumes or leaves the reader.
|
||||
|
||||
## Progress rules
|
||||
|
||||
| Mode | `PATCH .../progress/` |
|
||||
|------|------------------------|
|
||||
| Normal reading | Yes (debounced on `locationChanged`, sync after locations ready) |
|
||||
| Bookmark peek | **No** — `flushProgress` / `scheduleProgress` no-op |
|
||||
| After resume | Yes — flush anchor CFI and percentage once |
|
||||
|
||||
### Load from bookmarks page
|
||||
|
||||
1. Fetch saved `ReadingProgress` from API.
|
||||
2. If `state.epubLocation` is set and saved `epub_location` exists → store saved location as **anchor**, set peek mode, open at bookmark CFI.
|
||||
3. Do not persist bookmark location as progress during peek.
|
||||
|
||||
### In-reader peek
|
||||
|
||||
1. Capture current `location` (CFI) as anchor (if valid).
|
||||
2. Enter peek mode, jump to bookmark CFI.
|
||||
|
||||
## API
|
||||
|
||||
No backend changes in v1. Anchor is session-only in [`useEpubReader`](../frontend/src/hooks/useEpubReader.ts).
|
||||
|
||||
## Verification
|
||||
|
||||
1. Read to ~30%, open markers, **Go to passage** on an early bookmark → jumps; after debounce, library/API progress still reflects ~30% (not bookmark).
|
||||
2. Orange **Back to reading** appears above the next chevron only during peek.
|
||||
3. Tap resume → returns to ~30%; button hides; progress saves resume.
|
||||
4. From `/bookmarks-notes`, **Go to passage** → peek + resume using saved progress as anchor.
|
||||
5. TOC / prev / next do not show the resume button.
|
||||
6. EN/ES strings present.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- PDF reader
|
||||
- Multiple anchor history stack
|
||||
- Backend `resume_epub_location` field
|
||||
- Peek mode for TOC jumps
|
||||
Reference in New Issue
Block a user