Files
bookorbit/koreader-plugin
Jadehawk 1f4420b454 feat(koreader): support customizable dashboard sections (#820)
* feat(koreader): add dashboard row configuration model

* feat(koreader): reuse catalog for dashboard source selection

* feat(koreader): configure dashboard rows on 1.4.0

* chore(koreader): preserve dashboard source line endings

* fix(koreader): keep BookOrbit tools menu loadable

* fix(koreader): keep dashboard requests out of rendering

* revert(koreader): restore stock 1.4.0 dashboard renderer

* revert(koreader): use stock single dashboard row

* fix(koreader): apply selected dashboard row source

* feat(koreader): add second dashboard row setting

* feat(koreader): render second dashboard row

* fix(koreader): fetch configured dashboard rows directly

* fix(koreader): honor configured dashboard row sources

* feat(koreader): support four configurable dashboard sections

* fix(koreader): use real catalog dashboard sources

* fix(koreader): select dashboard shelves from catalog

* fix(koreader): reuse catalog for dashboard source selection

* fix(koreader): close dashboard menu before catalog selection

* test(koreader): split dashboard configuration coverage

* chore(koreader): bump version to 1.5.0

* fix(koreader): address dashboard review feedback

* fix(koreader): address dashboard section review feedback

* test(koreader): cover dashboard behavior at runtime

* test(koreader): use Nest setup for dashboard DTO

* chore(ci): trigger pipeline

* fix(koreader): migrate untagged dashboard section settings

Plugin 1.4.x stored the configurable dashboard rows as a bare ordered
list with no schema marker, so gating the migration on schemaVersion == 1
never matched anything a device had actually written. Every upgrading
user who had ever picked a row was read positionally instead: the Stats
strip disappeared and Discover ended up in two slots, with a stored
SmartScope dropped entirely.

Treat an untagged stored list as the legacy shape and migrate it into the
slots those rows used to occupy. Cover it with the settings shapes 1.4.x
really wrote, rather than a marker nothing ever produced.

Also drop store() and primary(), which no caller uses now that slots are
written through storeAt(), along with the dead grow loop inside storeAt()
that normalize() already makes unreachable.

* fix(koreader): keep dashboard shelves in aspect, inside the page, and deduped

The four-slot renderer handed each shelf a height derived from the
leftover vertical budget while its cover cards kept the width computed
from the natural row, so any page with more than one shelf drew covers
out of aspect. It also had no answer for a page too small for the
configured slots: shelves clamped to a 48px floor and the layout simply
overflowed the available height.

Size shelves from their cards instead. A shelf that does not fit gets
more, narrower cards per row, so its height is always the height those
cards need. When even the narrowest row does not fit, drop slots the way
the pre-four-slot renderer did: the stats strip first, then shelves from
the bottom up.

Fetch each distinct shelf source once per dashboard load. Two slots
showing Discover previously rendered the same list twice, and two slots
on the same catalog filter each paid for their own request; a source that
fails is now also not retried by the next slot that wants it.

Drop what the four-slot rewrite left behind: the unused capabilities
require, the tall-layout helper and its constants, and the section
accessors and request helper that no longer have callers. Name the
per-shelf page size and the minimum cover width instead of repeating the
literals. Add setDashboardSections() so a whole configuration can be
applied in one refresh.

Add a layout spec that drives updateDashboardItems and asserts covers
stay in aspect, the drop order holds, and nothing is laid out past the
available height.

* fix(koreader): close the dashboard slot menu through its own API

Reset to Default wrote section_stale as a boolean where the renderer
reads a per-slot table, so the reset never actually marked anything
pending and stale books stayed on screen under the new headers until the
refresh landed. It also dereferenced current_context without a guard and
wrote the setting behind the catalog's back. Route it through the new
setDashboardSections() so the slots and the cache signature move
together in one refresh.

Close the invoking touch menu with closeMenu() rather than handing the
widget to UIManager:close(), which skips the close_callback that owns its
container.

Drop the section parameter from catalogDashboard() and the unused
catalogDashboardSection() helper: the plugin builds its shelves from the
ordinary catalog endpoints now and sends neither. The server keeps both
routes for devices still on 1.4.x, so its DTO test stays; rename it to
match the file it covers and drop the empty Nest module compile.

* revert(koreader): keep the plugin at 1.4.0

1.4.0 has not shipped yet: 1.3.0 is the last released plugin version, so
the dashboard slot work can land inside 1.4.0 rather than claiming a
version of its own. Devices in the field are on 1.3.0 and see a single
1.3.0 -> 1.4.0 step either way.

* fix(koreader): stop rotating upright read-status badges

buildReadStatusBadge turned every status icon a quarter turn, but only
two of the seven are corner folds. The dogear assets are drawn with their
right angle at the top left and the badge sits in the cover's top-right
corner, so those do need the turn. Bookmark, check and reload are upright
glyphs and were being drawn on their side: a want-to-read cover showed
its bookmark ribbon lying sideways like a chevron.

Rotate only the dogear icons.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-07-30 13:37:26 -06:00
..

BookOrbit Sync plugin for KOReader

Syncs your KOReader reading life into BookOrbit:

  • Catalog browsing: a reading-first dashboard on the device with libraries, collections, SmartScopes, authors, series and search, plus one configurable book row you choose the source of. Mosaic or list view, read-status/format filters, downloads and bulk downloads, "On device" indicators, and a "Read" action for books already on the device.
  • Progress sync: pulls progress on book open (with a conflict prompt), pushes periodically and on close/suspend.
  • Reading statistics: uploads KOReader's per-page time events; BookOrbit turns them into reading sessions and daily stats.
  • Two-way highlights: device highlights appear as native highlights on the web reader; highlights, edits and deletions made on the web come back to the device.
  • Status & ratings: reading/complete/abandoned status and star ratings sync both ways, newest-change-wins.

Only books BookOrbit already knows about are synced, matched by the same partial-MD5 file hash KOReader uses internally. Unmatched books on the device are re-checked automatically as your library changes.

  1. In BookOrbit web settings: Settings > KOReader, create sync credentials (KOReader sync permission required on your account).
  2. Click "Download preconfigured plugin". The zip embeds your server address and credentials.
  3. Unzip and copy bookorbit.koplugin/ into koreader/plugins/ on your device.
  4. Restart KOReader. The plugin configures itself on startup and removes the embedded credentials file.

Re-downloading later (e.g. after changing your password or server address) and reinstalling applies the new config, even over an existing login. Each download carries a generation stamp, so replaying an old zip after logging out won't log you back in.

Install (manual)

  1. Copy bookorbit.koplugin/ from the repository into koreader/plugins/ on your device.
  2. Restart KOReader.
  3. In BookOrbit web settings: Settings > KOReader, create sync credentials.
  4. On the device: Tools > BookOrbit (sits below Calibre) - set the server address, log in, and use "Open dashboard" to browse/download books.

Setup

  1. Tools > BookOrbit > "Auto sync current book" to sync the open book automatically. Leave the stock "Progress sync" plugin unconfigured to avoid double syncing.
  2. Optional: Settings > Sync > "Periodically sync every # pages" (default 10, 0 disables mid-session pushes).
  3. Optional: Settings > Dashboard > "Open dashboard on startup".
  4. Optional: Settings > Dashboard > "Show below Continue reading" - pick Discover (the default), Recently added, Want to read, Up next in series, or one of your SmartScopes. Needs a server new enough to advertise the section capability; older servers keep showing Discover.
  5. Optional: assign "BookOrbit: sync this book" / "sync all books" to gestures.

The plugin manages settings/reader_menu_order.lua and settings/filemanager_menu_order.lua to keep its menu entry pinned below Calibre across updates; delete those files to reset your menu order.

From the dashboard menu, the plugin update check is top-level; from the Tools menu, it lives under Settings > Plugin.

How syncing works

  • With auto sync on: progress pulls on open (with conflict prompts), pushes every N page turns (debounced, skipped offline), and the whole book state (progress, highlights, status, rating, reading time) uploads from live memory on close and suspend.
  • "Sync current book now": checks BookOrbit for newer progress first, then does the same snapshot on demand.
  • "Sync all books now" (manual only): a full-library sweep - matches new books, then uploads reading time, highlights, statuses and progress for everything.
  • Everything is incremental and resumable: watermarks, modification-time checks and change detection mean an interrupted sync just resumes, and resends are server-side no-ops.
  • Plugin state lives in settings/bookorbit_sync_state.lua. Deleting it is safe: the next sync re-uploads everything and the server deduplicates.

A first sync on a device with years of history uploads in batches of 500 events (roughly 400 requests per 200k events, a few minutes of background work), and is safe to interrupt.

Two-way highlight and bookmark sync

Requires server 0.4+; against an older server the plugin falls back to upload-only automatically. Bookmark sync additionally requires a server that advertises the bookmarkSync capability; on an older one the bookmark paths stay dormant and highlights sync exactly as before.

  • Device to web: highlight positions (crengine xpointers) convert to reader CFIs, verified against the highlighted text, so they render as native, editable highlights on the web.
  • Web to device: applied on book open (with auto sync on), on manual sync, and during the full sweep for closed books, with positions re-verified/re-anchored against the text.
  • Deletions go both ways through a trash/restore flow, so nothing is lost outright.
  • Identity is the highlight's creation datetime plus position, so extending a highlight on the device is recognized as a move, not a delete-and-recreate.
  • Styles map across formats (e.g. squiggly <-> underline, invert, named colors <-> hex).
  • Toggle via Settings > Sync > "Two-way highlights & bookmarks" (uploads keep working either way).

Position-only bookmarks (dogears) sync the same way:

  • Device to web: the dogear xpointer converts to a reader CFI and shows up in the web reader's bookmark list. Its title is the note you attached, else the chapter, else the page.
  • Web to device: applied on book open, on manual sync, and during the full sweep for closed books. The position is only placed when it resolves; an entry that does not is retried on the next exchange.
  • Deletions and device-side renames propagate; the web has no rename surface yet, so edits flow device to web only.
  • A dogear has no highlighted text to re-anchor against, so a converted position can sit slightly off where a highlight would have been repaired exactly.

Limitations

  • Web highlight changes reach a closed book only via a manual sweep or its next open.
  • Web-created PDF highlights aren't supported (device PDF highlights sync up but aren't drawn over the PDF).
  • Books with reading stats but no sidecar path sync reading time only, until the full sweep covers them.
  • PDF bookmarks don't sync in either direction (the web has no PDF bookmark surface).
  • A device clock far in the past can delay edit detection for web-modified highlights.
  • The full sweep is manual-only; books you never reopen only sync when you run it.
  • A cloned device with a different KOReader device_id double-counts reading time; clones keeping the same device_id deduplicate naturally.