feat(koplugin): support sync bookmarks between Koreader and Readest devices (#3580)

This commit is contained in:
Huang Xin
2026-03-22 00:58:38 +08:00
committed by GitHub
parent 76b239f382
commit f5df80f154
2 changed files with 100 additions and 65 deletions
+26 -26
View File
@@ -45,38 +45,38 @@
<div align="left">✅ Implemented</div>
| **Feature** | **Description** | **Status** |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF | ✅ |
| **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 and use instant mode for quicker interactions. | ✅ |
| **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. | ✅ |
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
| **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. | ✅ |
| **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. | ✅ |
| **Visual & Focus Aids** | Reading ruler, paragraph-by-paragraph reading mode, and speed reading features. | ✅ |
| **Feature** | **Description** | **Status** |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------- |
| **Multi-Format Support** | Support EPUB, MOBI, KF8 (AZW3), FB2, CBZ, TXT, PDF | ✅ |
| **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 and use instant mode for quicker interactions. | ✅ |
| **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. | ✅ |
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
| **OPDS/Calibre Integration** | Integrate OPDS/Calibre to access online libraries and catalogs. | ✅ |
| **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. | ✅ |
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
| [**Sync with Koreader**][link-kosync-wiki] | Synchronize reading progress, notes, and bookmarks with [Koreader][link-koreader] devices. | ✅ |
| **Accessibility** | Provides full keyboard navigation and supports for screen readers such as VoiceOver, TalkBack, NVDA, and Orca. | ✅ |
| **Visual & Focus Aids** | Reading ruler, paragraph-by-paragraph reading mode, and speed reading features. | ✅ |
## Planned Features
<div align="left">🛠 Building</div>
<div align="left">🔄 Planned</div>
| **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. | 🛠 |
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🛠 |
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
| **In-Library Full-Text Search** | Search across your entire ebook library to find topics and quotes. | 🔄 |
| **Feature** | **Description** | **Priority** |
| ------------------------------- | -------------------------------------------------------------------------- | ------------ |
| **AI-Powered Summarization** | Generate summaries of books or chapters using AI for quick insights. | 🛠 |
| **Advanced Reading Stats** | Track reading time, pages read, and more for detailed insights. | 🛠 |
| **Audiobook Support** | Extend functionality to play and manage audiobooks. | 🔄 |
| **Handwriting Annotations** | Add support for handwriting annotations using a pen on compatible devices. | 🔄 |
| **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. 😊
+74 -39
View File
@@ -60,18 +60,18 @@ function SyncAnnotations:getAnnotations(ui, settings, book_hash, meta_hash)
local notes = {}
for _, item in ipairs(annotations) do
local updated_at = self:parseDatetimeToMs(item.datetime_updated or item.datetime)
if updated_at <= last_sync then
goto skip
end
local pos0 = item.pos0
local pos1 = item.pos1
if type(pos0) == "table" then pos0 = nil end
if type(pos1) == "table" then pos1 = nil end
if pos0 then
local updated_at = self:parseDatetimeToMs(item.datetime_updated or item.datetime)
if updated_at <= last_sync then
goto skip
end
local note_type = item.drawer and "annotation" or "bookmark"
local id = self:generateNoteId(book_hash, note_type, tostring(pos0), pos1 and tostring(pos1))
if item.drawer and pos0 then
-- Annotation (highlight/underline/strikeout): has drawer and pos0/pos1
local style = "highlight"
if item.drawer == "underscore" then
style = "underline"
@@ -79,23 +79,38 @@ function SyncAnnotations:getAnnotations(ui, settings, book_hash, meta_hash)
style = "squiggly"
end
local note = {
local id = self:generateNoteId(book_hash, "annotation", tostring(pos0), pos1 and tostring(pos1))
table.insert(notes, {
bookHash = book_hash,
metaHash = meta_hash,
id = id,
type = note_type,
type = "annotation",
xpointer0 = tostring(pos0),
xpointer1 = pos1 and tostring(pos1) or nil,
text = item.text or "",
note = item.note or "",
style = note_type == "annotation" and style or nil,
color = note_type == "annotation" and KO_TO_READEST_COLOR[item.color or "yellow"] or nil,
style = style,
color = KO_TO_READEST_COLOR[item.color or "yellow"],
page = item.pageno,
createdAt = self:parseDatetimeToMs(item.datetime),
updatedAt = updated_at,
}
table.insert(notes, note)
})
elseif not item.drawer and type(item.page) == "string" then
-- Bookmark: no drawer, position in page field (xpointer string)
local page_xp = item.page
local id = self:generateNoteId(book_hash, "bookmark", page_xp)
table.insert(notes, {
bookHash = book_hash,
metaHash = meta_hash,
id = id,
type = "bookmark",
xpointer0 = page_xp,
text = item.text or "",
note = item.note or "",
page = item.pageno,
createdAt = self:parseDatetimeToMs(item.datetime),
updatedAt = updated_at,
})
end
::skip::
end
@@ -208,10 +223,16 @@ function SyncAnnotations:pull(ui, settings, client, book_hash, meta_hash, dialog
local annotation_mgr = ui.annotation
if not annotation_mgr then return end
local existing_positions = {}
-- Build dedup sets: annotations by pos0|pos1, bookmarks by page xpointer
local existing_annotations = {}
local existing_bookmarks = {}
for _, item in ipairs(annotation_mgr.annotations) do
local key = tostring(item.pos0) .. "|" .. tostring(item.pos1 or "")
existing_positions[key] = true
if item.drawer then
local key = tostring(item.pos0) .. "|" .. tostring(item.pos1 or "")
existing_annotations[key] = true
elseif type(item.page) == "string" then
existing_bookmarks[item.page] = true
end
end
local added = 0
@@ -221,37 +242,51 @@ function SyncAnnotations:pull(ui, settings, client, book_hash, meta_hash, dialog
end
local xp0 = note.xpointer0
local xp1 = note.xpointer1
if not xp0 then goto continue end
local key = xp0 .. "|" .. (xp1 or "")
if existing_positions[key] then goto continue end
local drawer = "lighten"
local note_type = note.type
local item
if note_type == "bookmark" then
drawer = nil
elseif note.style == "underline" then
drawer = "underscore"
elseif note.style == "squiggly" then
drawer = "strikeout"
if existing_bookmarks[xp0] then goto continue end
item = {
page = xp0,
text = note.text or "",
note = note.note or "",
pageno = note.page,
datetime = os.date("%Y-%m-%d %H:%M:%S"),
}
existing_bookmarks[xp0] = true
else
local xp1 = note.xpointer1
local key = xp0 .. "|" .. (xp1 or "")
if existing_annotations[key] then goto continue end
local drawer = "lighten"
if note.style == "underline" then
drawer = "underscore"
elseif note.style == "squiggly" then
drawer = "strikeout"
end
item = {
pos0 = xp0,
pos1 = xp1 or xp0,
page = xp0,
text = note.text or "",
note = note.note or "",
drawer = drawer,
color = READEST_TO_KO_COLOR[note.color] or "yellow",
pageno = note.page,
datetime = os.date("%Y-%m-%d %H:%M:%S"),
}
existing_annotations[key] = true
end
local item = {
pos0 = xp0,
pos1 = xp1 or xp0,
page = xp0,
text = note.text or "",
note = note.note or "",
drawer = drawer,
color = READEST_TO_KO_COLOR[note.color] or "yellow",
pageno = note.page,
datetime = os.date("%Y-%m-%d %H:%M:%S"),
}
local index = annotation_mgr:addItem(item)
ui:handleEvent(Event:new("AnnotationsModified", { item, index_modified = index }))
logger.dbg("ReadestSync: Added annotation from sync:", item)
existing_positions[key] = true
added = added + 1
::continue::