diff --git a/docs/guide/using-librislog/progress.md b/docs/guide/using-librislog/progress.md index 6e8f103d..046c7165 100644 --- a/docs/guide/using-librislog/progress.md +++ b/docs/guide/using-librislog/progress.md @@ -33,3 +33,12 @@ Click the delete button next to an entry to remove it from the log. A confirmati ## Reading Percentage For books with a page count, the detail view shows your current page, total pages, reading percentage, and a visual progress bar. + +## Reading Sessions & Re-reads + +Books can be read more than once. Each reading is tracked as its own session with independent dates and progress. + +- **Read again** (book detail dialog, for finished books): starts a fresh reading today, keeping all earlier sessions and progress untouched. Use this for a genuine re-read. +- **Changing status from "read" to "currently reading"** (edit drawer): the legacy correction path. It can keep the previous finish date or clear it, and may leave the book in a "currently reading" state that still carries the old finish date. Use this only to correct a book's status, not to re-read it. + +For books that already have reading sessions, the start and finish dates are mirrors of the session and are read-only in the edit drawer; edit them via the reading history instead. diff --git a/docs/releases.md b/docs/releases.md index 8e6d70f1..16944eed 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -45,7 +45,7 @@ LibrisLog v1.10.0 adds four more metadata sources to the import search (BnF, Inv **Features** - 📖 **Multiple reading sessions per book.** Each book keeps a list of reading periods (start and finish dates) so re-reads never overwrite earlier dates. A new `reading_session` table stores them; `Book.date_started` / `Book.date_finished` remain a mirror of the current session, so every existing consumer (sorting, timeline, statistics, import/export) keeps working unchanged -- 🔁 **"Read again" action.** A ghost button in the book detail dialog starts a new reading session for a finished book. Earlier sessions and their progress stay untouched, and the progress bar resets to 0 % +- 🔁 **"Read again" action.** A ghost button in the book detail dialog starts a new reading session for a finished book. Earlier sessions and their progress stay untouched, and the progress bar resets to 0 %. This is the recommended way to re-read; changing a book's status from "read" to "currently reading" via the edit drawer is the legacy correction path and can keep the previous finish date - 📜 **Reading history.** A collapsible block in the book detail dialog lists every session (#, started, finished, status, pages) with per-row edit and delete, plus "Add a past reading" for back-filling handwritten history - 🏷️ **"read ×N" badge.** Cards, list rows and the book detail dialog show a small badge once a book has been read at least twice - 📊 **Statistics distinguish unique books from total completions.** A new "Total reads" card shows the number of completed reads (with a "N re-read books" sub-line). `books_finished_per_month/year`, the `books_per_month/year` goals, and `pages_read` now count every completion; "Pages read" in the bucket still counts each book once, so no single-session user sees any change diff --git a/frontend/src/lib/components/BookCard.svelte b/frontend/src/lib/components/BookCard.svelte index 458fa47b..cc4c3ce3 100644 --- a/frontend/src/lib/components/BookCard.svelte +++ b/frontend/src/lib/components/BookCard.svelte @@ -104,10 +104,9 @@ {$_(STATUS_LABEL_KEYS[book.reading_status])} {#if (book.read_count ?? 0) >= 2} - ×{book.read_count} + + ×{book.read_count} + {/if} diff --git a/frontend/src/lib/components/BookDetailDialog.svelte b/frontend/src/lib/components/BookDetailDialog.svelte index 070d473a..c071253a 100644 --- a/frontend/src/lib/components/BookDetailDialog.svelte +++ b/frontend/src/lib/components/BookDetailDialog.svelte @@ -5,7 +5,9 @@ import { formatDate, formatDateTime, + fromDateInputValue, fromDateTimeInputValue, + toDateInputValue, toDateTimeInputValue } from '#lib/date.js'; import { getTimezone } from '#lib/stores/timezone.js'; @@ -13,6 +15,7 @@ import { toasts } from '#lib/toasts.js'; import { formatLanguageCode } from '#lib/utils/language.js'; import { formatAuthors } from '#lib/utils/authors.js'; + import AdaptiveDateInput from './AdaptiveDateInput.svelte'; import StarRating from './StarRating.svelte'; import { Line } from 'svelte-chartjs'; import { X } from '@lucide/svelte'; @@ -175,19 +178,22 @@ try { const payload: { status: ReadingStatus; date_started: string | null; date_finished: string | null } = { status: addSessionStatus, - date_started: addSessionStarted ? fromDateTimeInputValue(addSessionStarted, tz) : null, - date_finished: addSessionFinished ? fromDateTimeInputValue(addSessionFinished, tz) : null + date_started: addSessionStarted ? fromDateInputValue(addSessionStarted, tz) : null, + date_finished: addSessionFinished ? fromDateInputValue(addSessionFinished, tz) : null }; + let result; if (editingSessionId !== null) { - await api.books.sessions.update(book.id, editingSessionId, payload); + result = await api.books.sessions.update(book.id, editingSessionId, payload); } else { - await api.books.sessions.create(book.id, payload); + result = await api.books.sessions.create(book.id, payload); } + book = result.book; + onSave?.(result.book); addSessionOpen = false; addSessionStarted = ''; addSessionFinished = ''; editingSessionId = null; - await loadSessions(); + await loadSessionsAndProgress(); } catch (e: unknown) { toasts.add( e instanceof Error ? e.message : $_('common.actionFailed', { values: { action: $_('book.addPastReading') } }), @@ -234,8 +240,8 @@ function openEditSession(s: ReadingSession) { addSessionStatus = s.status === 'currently_reading' ? 'read' : s.status; - addSessionStarted = s.date_started ? toDateTimeInputValue(s.date_started, tz) : ''; - addSessionFinished = s.date_finished ? toDateTimeInputValue(s.date_finished, tz) : ''; + addSessionStarted = s.date_started ? toDateInputValue(s.date_started, tz) : ''; + addSessionFinished = s.date_finished ? toDateInputValue(s.date_finished, tz) : ''; editingSessionId = s.id; addSessionOpen = true; } @@ -584,9 +590,11 @@
{#if (book.read_count ?? 0) >= 2} ×{book.read_count} + class="tooltip tooltip-bottom" + data-tip={$_('book.readCount', { values: { count: book.read_count } })} + > + ×{book.read_count} + {/if} {$_(STATUS_LABEL_KEYS[book.reading_status])}
@@ -938,11 +946,11 @@