` to search in a single field. The field prefixes are always
| `publisher` | Publisher | `publisher:Penguin` |
| `language` | Language | `language:Japanese` |
| `tag` | Tag name | `tag:fantasy` |
-| `availability` | Acquisition status | `availability:owned` |
+| `possession` | Possession status | `possession:owned` |
| `notes` | Private notes | `notes:"to reread"` |
| `description` | Blurb / description | `description:"middle earth"` |
@@ -21,16 +21,16 @@ Use quotes for values that contain spaces: `title:"The Silmarillion"`.
The `author:` prefix matches **any** author assigned to a book — a book with multiple authors matches if any of them contains the search value.
-### Availability values
+### Possession values
-The `availability` prefix matches the exact acquisition status. Accepted values include:
+The `possession` prefix matches the exact possession status. Accepted values include:
- `to_acquire` (or `to acquire`)
- `owned`
- `borrowed`
- `digital`
-Example: `availability:"to acquire"` shows books you want to buy.
+Example: `possession:"to acquire"` shows books you want to buy.
## Negation
@@ -45,7 +45,7 @@ Prefix a term with `-` to exclude matches.
Separate terms with spaces. All terms are combined with **AND**.
- `author:Murakami -title:Norwegian` — Murakami books except those whose title contains "Norwegian"
-- `tag:fantasy availability:owned` — owned fantasy books
+- `tag:fantasy possession:owned` — owned fantasy books
## Plain text
diff --git a/docs/index.md b/docs/index.md
index 1cf5c247..b2656a85 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -12,6 +12,9 @@ hero:
- theme: brand
text: Get Started
link: /guide/getting-started
+ - theme: alt
+ text: Release Notes
+ link: /releases
- theme: alt
text: View on GitHub
link: https://github.com/codebude/librislog
diff --git a/docs/releases.md b/docs/releases.md
new file mode 100644
index 00000000..2f2ecac5
--- /dev/null
+++ b/docs/releases.md
@@ -0,0 +1,300 @@
+# Release Notes
+
+All LibrisLog releases, newest first — what's new, what was fixed, and anything you need to know before upgrading.
+
+> **Upgrading:** releases are backwards compatible. Database migrations run automatically when the container starts (`alembic upgrade head`), so upgrading is a drop-in replacement. We always recommend [taking a backup](/guide/using-librislog/administration#backup-restore) before an upgrade.
+
+You can also browse the [GitHub Releases](https://github.com/codebude/librislog/releases) page and the [full changelog](https://github.com/codebude/librislog/commits/main).
+
+## vNext — Unreleased
+
+
+
+**Summary:** Everything merged into the development branch since v1.6.0, not yet released. Focused on a richer book model (multiple authors), a new search syntax, and a more flexible file import.
+
+**Features**
+- 👥 **Multiple authors per book** — a book can have any number of authors (normalized per-user author model). The legacy API `author` field is deprecated in favor of the `authors` list
+- 🔍 **Enhanced search** — field-specific prefixes (`author:`, `title:`, `publisher:`, `tag:`, `language:`, `possession:`, `notes:`, `description:`), quoted phrases, and `-` negation
+- 📚 **"All books" library tab** — browse every book regardless of reading status, with the usual search and sort controls
+- 📈 **Author statistics card** — total book count and distinct author count on the statistics page
+- 📥 **Improved file import**
+ - New `authors` target field (accepts a plain string or an array; legacy `author` mappings keep working)
+ - **CSV delimiter** is now user-configurable (default `,`)
+ - **`date_added`** can be imported — preserves original library dates when migrating from other tools
+ - Transforms may return **lists** for the `authors`/`tags` targets (e.g. `value.split(';')`)
+ - Preview renders `authors` and `tags` as JSON arrays
+- 📤 **Data export** — `authors` and `tags` export as lists in JSON, round-tripping through the adaptive import
+- 🏷️ **Possession naming** — the acquisition/possession field and its search prefix are now consistently called **possession**
+
+**Bug fixes**
+- 📊 Fixed the inverted Top Rated / Worst Rated ordering on the statistics page
+
+**Breaking changes**
+- ⚠️ Creating a book now requires **at least one author** (via `authors` or the legacy `author` field) — API requests without any author are rejected
+- ⚠️ The `availability:` search prefix is renamed to `possession:` (the extended search is new and unreleased, so impact is limited)
+
+[Compare with v1.6.0](https://github.com/codebude/librislog/compare/v1.6.0...main)
+
+## Latest Release
+
+::: tip ⭐ v1.6.0 — Reading Progress & Possession Tracking
+LibrisLog v1.6.0 brings improved reading-progress tracking with automatic synchronization across cards and detail views, a new possession (book ownership) tracking model, and a range of UI, statistics, and reliability improvements.
+:::
+
+### All releases
+
+| Version | Date | Type |
+|---|---|---|
+| [vNext](#vnext-—-unreleased) | — | Unreleased |
+| [v1.6.0](#v1-6-0-—-reading-progress-possession-tracking) | 2026-08-23 | Feature release |
+| [v1.5.2](#v1-5-2-—-maintenance) | 2026-06-22 | Maintenance |
+| [v1.5.1](#v1-5-1-—-maintenance) | 2026-06-22 | Maintenance |
+| [v1.5.0](#v1-5-0-—-password-reset-usability) | 2026-06-22 | Feature release |
+| [v1.4.0](#v1-4-0-—-embeddable-views-arm64) | 2026-06-14 | Feature release |
+| [v1.3.1](#v1-3-1-—-maintenance) | 2026-06-09 | Maintenance |
+| [v1.3.0](#v1-3-0-—-more-languages) | 2026-06-09 | Feature release |
+| [v1.2.2](#v1-2-2-—-maintenance) | 2026-06-08 | Maintenance |
+| [v1.2.1](#v1-2-1-—-import-reliability-multi-user-consistency) | 2026-06-08 | Feature release |
+| [v1.2.0](#v1-2-0-—-startup-screen-update-checks) | 2026-06-01 | Feature release |
+| [v1.1.1](#v1-1-1-—-maintenance) | 2026-06-01 | Maintenance |
+| [v1.1.0](#v1-1-0-—-polish-missing-covers) | 2026-05-31 | Feature release |
+| [v1.0.0](#v1-0-0-—-initial-release) | 2026-05-28 | Initial release |
+
+---
+
+## v1.6.0 — Reading Progress & Possession Tracking
+
+
+
+**Summary:** Improved reading-progress tracking with automatic cross-view synchronization, a new possession model for tracking what you own, and a wave of UI, statistics, and reliability improvements.
+
+**Features**
+- 📖 Automatic synchronization of reading progress across book cards and the detail view
+- 📚 Possession tracking — mark books as owned, borrowed, digitally available, or to acquire
+- 📊 Possession information added to the statistics page
+- 🏷️ Visual "needs to be acquired" indicators on book cards
+- 📝 Improved reading-status transitions, including a start-date prompt and smarter progress-completion handling
+- 📅 Timezone-aware progress charts with better visualization
+- 🎨 Refined sort selector and other UI improvements
+- 🔒 Updated dependencies and applied frontend security patches
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.5.2...v1.6.0)
+
+---
+
+## v1.5.2 — Maintenance
+
+
+
+**Summary:** Small maintenance release improving the accuracy of reading-progress visualizations.
+
+**Bug fixes**
+- 📊 Fixed the fallback logic for the start date used in the reading-progress chart in the book detail view
+- 🐛 Improved reliability of progress timeline calculations
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.5.1...v1.5.2)
+
+---
+
+## v1.5.1 — Maintenance
+
+
+
+**Summary:** Small maintenance release fixing an issue in the book edit drawer.
+
+**Bug fixes**
+- 🐛 Fixed an issue where the edit drawer could fail to open correctly in certain situations
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.5.0...v1.5.1)
+
+---
+
+## v1.5.0 — Password Reset & Usability
+
+
+
+**Summary:** Adds password-reset functionality with email support, improves cover imports, and brings several usability and statistics enhancements.
+
+**Features**
+- 🔐 Password reset via email
+- 🖼️ Cover imports now follow HTTP redirects automatically
+- 📱 Android back-button support for navigation drawers
+- 💡 Author and publisher suggestions on the Data Hygiene page
+- 📊 Reading-progress charts scaled by actual elapsed time
+
+**Bug fixes**
+- 🐛 Various UI and usability fixes
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.4.0...v1.5.0)
+
+---
+
+## v1.4.0 — Embeddable Views & ARM64
+
+
+
+**Summary:** Introduces embeddable library views, ARM64 Docker support, improved data-hygiene workflows, and auto-generated database documentation.
+
+**Features**
+- 🖼️ New HTML iframe **embed endpoint** for dashboards, homepages, and other applications
+- 🏗️ **ARM64 Docker images** for Raspberry Pi and other ARM-based systems
+- 🧹 Improved Data Hygiene UX for incomplete or inconsistent metadata
+- 📊 Integration documentation for Dashy and Glance
+- 🗄️ Auto-generated database schema documentation
+- 🧪 Frontend test and type improvements
+
+**Contributors:** Thank you to **@Jossey28** for adding ARM64 Docker build support.
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.3.1...v1.4.0)
+
+---
+
+## v1.3.1 — Maintenance
+
+
+
+**Summary:** Small maintenance release improving the reliability of update notifications.
+
+**Bug fixes**
+- 🔔 Fixed a caching issue affecting the version update indicator
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.3.0...v1.3.1)
+
+---
+
+## v1.3.0 — More Languages
+
+
+
+**Summary:** Expands localization support, refines statistics calculations, and improves documentation.
+
+**Features**
+- 🌍 Added **Spanish, French, and Chinese (Simplified)** UI languages with expanded localization coverage
+- 📈 Improved pages-per-day statistics calculations
+- 📖 Integration documentation (Dashy, Home Assistant) and general documentation improvements
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.2.2...v1.3.0)
+
+---
+
+## v1.2.2 — Maintenance
+
+
+
+**Summary:** Small maintenance release focused on Goodreads import data handling.
+
+**Bug fixes**
+- 🐛 Fixed the Goodreads notes transformation during import processing
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.2.1...v1.2.2)
+
+---
+
+## v1.2.1 — Import Reliability & Multi-User Consistency
+
+
+
+**Summary:** Focused on import reliability, multi-user data consistency, and quality-of-life improvements.
+
+**Features**
+- 📥 Improved Goodreads import mapping templates and book import handling
+- 👥 **Per-user ISBN uniqueness** — the same ISBN can now exist for different users without conflict
+- 🌐 Support for a custom documentation domain
+
+**Bug fixes**
+- 🐛 Fixed issues affecting Goodreads imports
+
+**Contributors:** Thank you to **@badcrc** for their first contribution.
+
+**Breaking changes:** None. (The ISBN uniqueness change is a database migration and runs automatically on upgrade.)
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.2.0...v1.2.1)
+
+---
+
+## v1.2.0 — Startup Screen & Update Checks
+
+
+
+**Summary:** Improves the initial user experience and adds automatic update awareness.
+
+**Features**
+- 🚀 New startup loading screen for a smoother launch
+- 🔔 **Release update check** that notifies you when a new version is available
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.1.1...v1.2.0)
+
+---
+
+## v1.1.1 — Maintenance
+
+
+
+**Summary:** Maintenance release focused on thumbnails, bug fixes, and stability.
+
+**Bug fixes**
+- 🖼️ Improved thumbnail generation and image quality
+- 🐛 Fixed several cover and thumbnail handling issues
+- 🧪 Test-suite maintenance and documentation build workflow improvements
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.1.0...v1.1.1)
+
+---
+
+## v1.1.0 — Polish & Missing Covers
+
+
+
+**Summary:** A refinement release focused on usability, workflow completeness, and overall polish.
+
+**Features**
+- 📚 Improved missing-book-cover workflow for incomplete metadata
+- 🌐 Refined translations and i18n coverage
+- 🧩 UX and UI improvements across the application
+- 📖 Documentation updates
+
+**Breaking changes:** None.
+
+[Full changelog](https://github.com/codebude/librislog/compare/v1.0.0...v1.1.0)
+
+---
+
+## v1.0.0 — Initial Release
+
+
+
+**Summary:** The first stable release of LibrisLog — a self-hosted, multi-user book tracking web app with full data ownership.
+
+**Features**
+- 📚 Library management with four reading states (Want to Read, Reading, Read, Did Not Finish)
+- 📖 Reading-progress tracking with per-book history
+- 📊 Statistics dashboard (heatmaps, charts, reading trends)
+- 📷 ISBN barcode scanning (browser-based, mobile-friendly)
+- 📥 Imports from Goodreads, Open Library, Google Books, and custom CSV/JSON
+- 🖼️ Automatic cover-art fetching with manual fallback
+- 👥 Multi-user support with roles and optional OIDC login
+- 🔌 REST API with OpenAPI documentation
+- 🐳 Self-hosted via Docker Compose (SQLite, lightweight setup)
+- 🎨 Light/dark themes and responsive UI
+
+[Full changelog](https://github.com/codebude/librislog/commits/v1.0.0)
\ No newline at end of file
diff --git a/frontend/e2e/specs/03-library-browsing.spec.ts b/frontend/e2e/specs/03-library-browsing.spec.ts
index 0971e9c6..b1735d7d 100644
--- a/frontend/e2e/specs/03-library-browsing.spec.ts
+++ b/frontend/e2e/specs/03-library-browsing.spec.ts
@@ -67,14 +67,14 @@ test.describe('Library Browsing', () => {
await expect(body).toContainText(/no books|empty/i);
});
- test('3.5 manual creation requires availability and persists the selected value', async ({ page }) => {
+ test('3.5 manual creation requires possession and persists the selected value', async ({ page }) => {
await deleteAllBooks(page);
const library = new LibraryPage(page);
await library.goto();
await page.getByRole('button', { name: '+ Add Book' }).click();
const modal = page.locator('.modal-box');
- const availability = modal.getByRole('combobox', { name: /Availability/ });
+ const availability = modal.getByRole('combobox', { name: /Possession/ });
await expect(availability).toHaveValue('');
await modal.getByLabel('Title *').fill('Digital E2E Book');
@@ -91,7 +91,7 @@ test.describe('Library Browsing', () => {
expect((await response.json()).books[0].acquisition_status).toBe('digital_access');
});
- test('3.6 filters Want to Read books by availability and marks books to acquire', async ({ page }) => {
+ test('3.6 filters Want to Read books by possession and marks books to acquire', async ({ page }) => {
await deleteAllBooks(page);
await createWantToReadBook(page, 'Owned E2E Book', 'owned');
await createWantToReadBook(page, 'Acquire E2E Book', 'to_acquire');
@@ -106,4 +106,36 @@ test.describe('Library Browsing', () => {
await expect(page.getByText('Acquire E2E Book')).toBeVisible();
await expect(page.getByText('Owned E2E Book')).not.toBeVisible();
});
+
+ test('3.7 All Books tab shows every book regardless of status with search and sort', async ({ page }) => {
+ const library = new LibraryPage(page);
+ await library.goto();
+
+ await page.getByRole('tab', { name: /All Books/ }).click();
+ await expect(page).toHaveURL(/\/library\?status=all/);
+
+ // All 12 seeded books are shown, spanning every reading status.
+ await expect(library.getBookCards()).toHaveCount(12);
+ await expect(page.getByText('The Great Gatsby')).toBeVisible();
+ await expect(page.getByText('The Three-Body Problem')).toBeVisible();
+ await expect(page.getByText('1984')).toBeVisible();
+ await expect(page.getByText('Atlas Shrugged')).toBeVisible();
+
+ // Smart sort is hidden on the All tab; the sort selects are enabled.
+ await expect(page.locator('input[name="smart-sort"]')).toHaveCount(0);
+ await expect(page.locator('select[name="sort-field"]')).toBeEnabled();
+
+ // Sort by title ascending: "1984" is alphabetically first among the seeds.
+ await page.locator('select[name="sort-field"]').selectOption('title');
+ await page.locator('select[name="sort-order"]').selectOption('asc');
+ await expect(page.locator('button.card h2').first()).toHaveText('1984');
+
+ // Search narrows the All tab results.
+ const searchInput = page.getByPlaceholder(/Search books/);
+ await searchInput.fill('Dune');
+ await searchInput.press('Enter');
+ await expect(page.locator('button.card')).toHaveCount(1);
+ await expect(page.getByText('Dune')).toBeVisible();
+ await expect(page.getByText('1984')).not.toBeVisible();
+ });
});
diff --git a/frontend/src/lib/components/AddBookModal.test.ts b/frontend/src/lib/components/AddBookModal.test.ts
index bdac0797..698eff31 100644
--- a/frontend/src/lib/components/AddBookModal.test.ts
+++ b/frontend/src/lib/components/AddBookModal.test.ts
@@ -128,7 +128,7 @@ describe('AddBookModal', () => {
fireEvent.keyDown(authorInput, { key: 'Enter' });
const pagesInput = screen.getByLabelText(/Pages/);
fireEvent.input(pagesInput, { target: { value: '412' } });
- fireEvent.change(screen.getByRole('combobox', { name: /Availability/ }), { target: { value: 'owned' } });
+ fireEvent.change(screen.getByRole('combobox', { name: /Possession/ }), { target: { value: 'owned' } });
}
it('submits form and calls api.books.create', async () => {
diff --git a/frontend/src/lib/components/SearchHelp.svelte b/frontend/src/lib/components/SearchHelp.svelte
index fc56fce3..d058c455 100644
--- a/frontend/src/lib/components/SearchHelp.svelte
+++ b/frontend/src/lib/components/SearchHelp.svelte
@@ -20,7 +20,7 @@
{ name: 'title', example: 'title:fragezeichen' },
{ name: 'tag', example: 'tag:cars' },
{ name: 'language', example: 'language:en' },
- { name: 'availability', example: 'availability:owned' },
+ { name: 'possession', example: 'possession:owned' },
{ name: 'notes', example: 'notes:reading' },
{ name: 'description', example: 'description:desert' }
];
@@ -74,7 +74,7 @@
{$_('search.help.negate')}
- {$_('search.help.availabilityValues')}
+ {$_('search.help.possessionValues')}
{/if}
diff --git a/frontend/src/lib/components/SearchHelp.test.ts b/frontend/src/lib/components/SearchHelp.test.ts
index 1c319479..9bac602a 100644
--- a/frontend/src/lib/components/SearchHelp.test.ts
+++ b/frontend/src/lib/components/SearchHelp.test.ts
@@ -33,7 +33,7 @@ describe('SearchHelp', () => {
expect(dialog).toHaveTextContent('title');
expect(dialog).toHaveTextContent('tag');
expect(dialog).toHaveTextContent('language');
- expect(dialog).toHaveTextContent('availability');
+ expect(dialog).toHaveTextContent('possession');
expect(dialog).toHaveTextContent('notes');
expect(dialog).toHaveTextContent('description');
});
diff --git a/frontend/src/lib/i18n/locales/de.json b/frontend/src/lib/i18n/locales/de.json
index 480bf7b7..5d4ee6e9 100644
--- a/frontend/src/lib/i18n/locales/de.json
+++ b/frontend/src/lib/i18n/locales/de.json
@@ -84,7 +84,8 @@
"want_to_read": "Möchte ich lesen",
"currently_reading": "Lese ich gerade",
"read": "Gelesen",
- "did_not_finish": "Abgebrochen"
+ "did_not_finish": "Abgebrochen",
+ "all": "Alle Bücher"
},
"common": {
"all": "Alle",
@@ -303,7 +304,7 @@
"multiWord": "Setze mehrteilige Werte in doppelte Anführungszeichen, z. B. author:\"Marlen Haushofer\".",
"combine": "Du kannst mehrere Präfixe kombinieren; die Ergebnisse müssen allen entsprechen.",
"negate": "Stelle einem Begriff ein - voran, um ihn auszuschließen, z. B. tag:cars -tag:audi.",
- "availabilityValues": "Verfügbarkeitswerte: owned, borrowed, digital_access, to_acquire"
+ "possessionValues": "Possession-Werte: owned, borrowed, digital_access, to_acquire"
}
},
"languages": {
diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json
index c75b2a74..1c9d0376 100644
--- a/frontend/src/lib/i18n/locales/en.json
+++ b/frontend/src/lib/i18n/locales/en.json
@@ -84,7 +84,8 @@
"want_to_read": "Want to Read",
"currently_reading": "Currently Reading",
"read": "Read",
- "did_not_finish": "Did Not Finish"
+ "did_not_finish": "Did Not Finish",
+ "all": "All Books"
},
"common": {
"all": "All",
@@ -147,7 +148,7 @@
"blurb": "Description",
"about": "About",
"dateStarted": "Date started",
- "acquisitionStatus": "Availability",
+ "acquisitionStatus": "Possession",
"selectAcquisitionStatus": "Select availability...",
"startDatePromptTitle": "Set a start date?",
"startDatePromptMessage": "This book has no start date. Set it to today or choose another date before marking it as read.",
@@ -303,7 +304,7 @@
"multiWord": "Wrap multi-word values in double quotes, e.g. author:\"Marlen Haushofer\".",
"combine": "You can combine multiple prefixes; results must match all of them.",
"negate": "Prefix any term with - to exclude it, e.g. tag:cars -tag:audi.",
- "availabilityValues": "availability values: owned, borrowed, digital_access, to_acquire"
+ "possessionValues": "possession values: owned, borrowed, digital_access, to_acquire"
}
},
"languages": {
@@ -722,4 +723,4 @@
"candidatesError": "Cover search failed. You can still use manual import.",
"retry": "Retry"
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/lib/i18n/locales/es.json b/frontend/src/lib/i18n/locales/es.json
index 3a1f4e9d..d3bc2a6e 100644
--- a/frontend/src/lib/i18n/locales/es.json
+++ b/frontend/src/lib/i18n/locales/es.json
@@ -84,7 +84,8 @@
"want_to_read": "Quiero leer",
"currently_reading": "Leyendo",
"read": "Leído",
- "did_not_finish": "Abandonado"
+ "did_not_finish": "Abandonado",
+ "all": "Todos los libros"
},
"common": {
"all": "Todos",
@@ -303,7 +304,7 @@
"multiWord": "Envuelve los valores de varias palabras entre comillas dobles, p. ej. author:\"Marlen Haushofer\".",
"combine": "Puedes combinar varios prefijos; los resultados deben coincidir con todos.",
"negate": "Antepón - a cualquier término para excluirlo, p. ej. tag:cars -tag:audi.",
- "availabilityValues": "valores de disponibilidad: owned, borrowed, digital_access, to_acquire"
+ "possessionValues": "valores de posesión: owned, borrowed, digital_access, to_acquire"
}
},
"languages": {
diff --git a/frontend/src/lib/i18n/locales/fr.json b/frontend/src/lib/i18n/locales/fr.json
index ecbf445f..daea5c23 100644
--- a/frontend/src/lib/i18n/locales/fr.json
+++ b/frontend/src/lib/i18n/locales/fr.json
@@ -84,7 +84,8 @@
"want_to_read": "À lire",
"currently_reading": "En cours",
"read": "Lu",
- "did_not_finish": "Abandonné"
+ "did_not_finish": "Abandonné",
+ "all": "Tous les livres"
},
"common": {
"all": "Tous",
@@ -303,7 +304,7 @@
"multiWord": "Place les valeurs multi-mots entre guillemets doubles, p. ex. author:\"Marlen Haushofer\".",
"combine": "Tu peux combiner plusieurs préfixes ; les résultats doivent correspondre à tous.",
"negate": "Préfixe tout terme par - pour l'exclure, p. ex. tag:cars -tag:audi.",
- "availabilityValues": "valeurs de disponibilité : owned, borrowed, digital_access, to_acquire"
+ "possessionValues": "valeurs de possession : owned, borrowed, digital_access, to_acquire"
}
},
"languages": {
diff --git a/frontend/src/lib/i18n/locales/zh.json b/frontend/src/lib/i18n/locales/zh.json
index 8fc59cf4..15b1f227 100644
--- a/frontend/src/lib/i18n/locales/zh.json
+++ b/frontend/src/lib/i18n/locales/zh.json
@@ -84,7 +84,8 @@
"want_to_read": "想读",
"currently_reading": "在读",
"read": "已读",
- "did_not_finish": "弃读"
+ "did_not_finish": "弃读",
+ "all": "所有书籍"
},
"common": {
"all": "全部",
@@ -303,7 +304,7 @@
"multiWord": "多词值请用双引号括起来,例如 author:\"Marlen Haushofer\"。",
"combine": "可以组合多个前缀;结果必须满足所有条件。",
"negate": "在任何词条前加 - 以将其排除,例如 tag:cars -tag:audi。",
- "availabilityValues": "可用性值:owned、borrowed、digital_access、to_acquire"
+ "possessionValues": "持有值:owned、borrowed、digital_access、to_acquire"
}
},
"languages": {
diff --git a/frontend/src/routes/library/+page.svelte b/frontend/src/routes/library/+page.svelte
index 9a2c26e9..a3e3fd68 100644
--- a/frontend/src/routes/library/+page.svelte
+++ b/frontend/src/routes/library/+page.svelte
@@ -14,10 +14,10 @@
import AddBookModal from '$lib/components/AddBookModal.svelte';
import SearchBar from '$lib/components/SearchBar.svelte';
import SearchHelp from '$lib/components/SearchHelp.svelte';
- import { BookOpen as BookOpenIcon, Book as BookIcon, Check, X } from '@lucide/svelte';
+ import { BookOpen as BookOpenIcon, Book as BookIcon, Check, Library, X } from '@lucide/svelte';
type Tab = {
- status: ReadingStatus;
+ status: ReadingStatus | 'all';
labelKey: string;
Icon: typeof BookOpenIcon;
};
@@ -26,20 +26,22 @@
{ status: 'want_to_read', labelKey: 'status.want_to_read', Icon: BookOpenIcon },
{ status: 'currently_reading', labelKey: 'status.currently_reading', Icon: BookIcon },
{ status: 'read', labelKey: 'status.read', Icon: Check },
- { status: 'did_not_finish', labelKey: 'status.did_not_finish', Icon: X }
+ { status: 'did_not_finish', labelKey: 'status.did_not_finish', Icon: X },
+ { status: 'all', labelKey: 'status.all', Icon: Library }
];
const STATUS_LABEL_KEYS: Record = {
want_to_read: 'status.want_to_read',
currently_reading: 'status.currently_reading',
read: 'status.read',
- did_not_finish: 'status.did_not_finish'
+ did_not_finish: 'status.did_not_finish',
+ all: 'status.all'
};
const PAGE_SIZE = 40;
- let activeStatus = $derived(
- ($page.url.searchParams.get('status') as ReadingStatus) ?? 'want_to_read'
+ let activeStatus = $derived(
+ ($page.url.searchParams.get('status') as ReadingStatus | 'all') ?? 'want_to_read'
);
let requestedBookId = $derived.by(() => {
const raw = $page.url.searchParams.get('bookId');
@@ -85,7 +87,7 @@
return numberFormatter.format(value);
}
- function getStatusCount(status: ReadingStatus): number | null {
+ function getStatusCount(status: ReadingStatus | 'all'): number | null {
if (!statusCounts) return null;
switch (status) {
case 'want_to_read':
@@ -96,6 +98,8 @@
return statusCounts.books_read;
case 'did_not_finish':
return statusCounts.books_did_not_finish;
+ case 'all':
+ return statusCounts.total_books;
}
}
@@ -112,7 +116,7 @@
let drawerOpen = $state(false);
let addBookOpen = $state(false);
- function changeTab(status: ReadingStatus) {
+ function changeTab(status: ReadingStatus | 'all') {
if (status === activeStatus) return;
void goto(`/library?status=${status}`);
}
@@ -156,7 +160,7 @@
loading = true;
try {
const response = await api.books.list({
- status: activeStatus,
+ status: activeStatus === 'all' ? undefined : activeStatus,
acquisition_status: activeStatus === 'want_to_read' && acquisitionFilter ? acquisitionFilter : undefined,
q: searchQuery || undefined,
smart_sort: smartSort,
@@ -190,7 +194,7 @@
loadingMore = true;
try {
const response = await api.books.list({
- status: activeStatus,
+ status: activeStatus === 'all' ? undefined : activeStatus,
acquisition_status: activeStatus === 'want_to_read' && acquisitionFilter ? acquisitionFilter : undefined,
q: searchQuery || undefined,
smart_sort: smartSort,
@@ -252,7 +256,7 @@
function handleSave(updated: Book) {
selectedBook = updated;
- if (updated.reading_status !== activeStatus) {
+ if (activeStatus !== 'all' && updated.reading_status !== activeStatus) {
detailOpen = false;
drawerOpen = false;
books = books.filter((b) => b.id !== updated.id);
@@ -274,7 +278,7 @@
}
function handleAdded(book: Book) {
- if (book.reading_status === activeStatus) {
+ if (activeStatus === 'all' || book.reading_status === activeStatus) {
books = [book, ...books];
}
addBookOpen = false;
@@ -401,18 +405,20 @@
-
-