diff --git a/main.lua b/main.lua index a212977..fb3bf43 100644 --- a/main.lua +++ b/main.lua @@ -879,8 +879,18 @@ function Bookhoard:collectAnnotations() for _, bm in ipairs(entries) do local sel_text, user_note if model == "v2" then - sel_text = bm.text or "" -- highlighted text - user_note = bm.note or "" -- user note / bookmark label + if bm.drawer then + sel_text = bm.text or "" -- highlighted text + user_note = bm.note or "" -- user note + else + -- Page bookmark. `drawer` is the only reliable highlight + -- discriminator: KOReader auto-fills text = "in Chapter X" + -- on bookmarks (updateItemByXPointer), so text-presence + -- would misclassify every bookmark as a highlight on echo. + -- The user label lives in `note`. + sel_text = "" + user_note = bm.note or "" + end else sel_text = bm.notes or "" -- v1: notes held the highlighted text user_note = bm.text or "" -- v1: text held the note/label @@ -910,6 +920,13 @@ function Bookhoard:collectAnnotations() if percentage then entry.percentage = Math.roundPercent(percentage) end + if bm.bookhoard_dedup_key then + -- Echo identity for entries received from the server: lets the + -- server match this push to the original row instead of + -- minting a duplicate (device locators ≠ web locators, so the + -- computed key would never match). + entry.dedup_key = bm.bookhoard_dedup_key + end local has_text = sel_text ~= "" local has_notes = user_note ~= "" @@ -1202,7 +1219,22 @@ function Bookhoard:applyServerAnnotations(annotations) local changed = false local has_pages = self.ui.document.info.has_pages - local function findLocalByPos0(pos0) + local function findLocal(server_entry) + -- Identity match by dedup key: survives pos0 drift (improved + -- server conversion) and, crucially, never cross-matches a + -- DIFFERENT annotation that merely shares the position. + local key = server_entry.dedup_key + if key and key ~= "" then + for i, bm in ipairs(entries) do + if bm.bookhoard_dedup_key == key then + return i + end + end + return nil -- keyed but not present: a real new entry + end + -- Legacy serve (no key): fall back to pos0 matching. + local pos0 = server_entry.pos0 or "" + if pos0 == "" then return nil end for i, bm in ipairs(entries) do local p = bm.pos0 if type(p) == "table" then p = tostring(p.page or "") end @@ -1267,7 +1299,7 @@ function Bookhoard:applyServerAnnotations(annotations) if not isValidPos0(pos0) then return end local srv_text = server_entry.text or "" local srv_notes = server_entry.notes or "" - local idx = findLocalByPos0(pos0) + local idx = findLocal(server_entry) if idx then local bm = entries[idx] local cur_text, cur_note = getFields(bm) @@ -1286,18 +1318,17 @@ function Bookhoard:applyServerAnnotations(annotations) pos1 = makeLocalPos(server_entry.pos1 ~= "" and server_entry.pos1 or pos0), page = makePageFromPos(pos0), } + if server_entry.dedup_key and server_entry.dedup_key ~= "" then + entry.bookhoard_dedup_key = server_entry.dedup_key + end if has_text then entry.drawer = "lighten" entry.text = srv_text if srv_notes ~= "" then entry.note = srv_notes end - -- KOReader renders highlight colors from a fixed name set - -- (Blitbuffer.HIGHLIGHT_COLORS); anything else (e.g. a web - -- hex value slipping through) draws nothing useful, so only - -- keep plain names and let the device default apply. - local srv_color = server_entry.color or "" - if srv_color ~= "" and not srv_color:find("^#") then - entry.color = srv_color - end + -- No color imposed: devices render their own default and + -- cannot round-trip web colors; the web color only changes + -- when the highlight is edited here (the edit sets a device + -- color, which syncs back by name). else if srv_text ~= "" then entry.note = srv_text end -- bookmark label end @@ -1312,7 +1343,9 @@ function Bookhoard:applyServerAnnotations(annotations) notes = srv_text, text = srv_notes, } - if server_entry.color and server_entry.color ~= "" then entry.color = server_entry.color end + if server_entry.dedup_key and server_entry.dedup_key ~= "" then + entry.bookhoard_dedup_key = server_entry.dedup_key + end if server_entry.chapter and server_entry.chapter ~= "" then entry.chapter = server_entry.chapter end table.insert(entries, entry) changed = true @@ -1337,30 +1370,49 @@ function Bookhoard:applyServerAnnotations(annotations) end end + local function findLocalByTombstone(del, want_highlight) + local key = del.dedup_key + if key and key ~= "" then + for i, bm in ipairs(entries) do + if bm.bookhoard_dedup_key == key then + return i + end + end + return nil -- keyed tombstone: only its own entry may be removed + end + -- Legacy tombstone (no key): pos0 match, restricted to the same + -- annotation kind so a highlight tombstone never eats a bookmark + -- that shares the position. + local pos0 = del.pos0 or "" + if pos0 == "" then return nil end + for i, bm in ipairs(entries) do + local p = bm.pos0 + if type(p) == "table" then p = tostring(p.page or "") end + if p == pos0 and (not not bm.drawer) == want_highlight then + return i + end + end + return nil + end + if annotations.deleted_highlights then for _, del in ipairs(annotations.deleted_highlights) do - local pos0 = del.pos0 or "" - if pos0 ~= "" then - local idx = findLocalByPos0(pos0) - local bm = idx and entries[idx] - local cur_text = bm and (getFields(bm)) or "" - if idx and cur_text ~= "" then - table.remove(entries, idx) - changed = true - end + local idx = findLocalByTombstone(del, true) + local bm = idx and entries[idx] + local cur_text = bm and (getFields(bm)) or "" + if idx and cur_text ~= "" then + table.remove(entries, idx) + changed = true end end end if annotations.deleted_bookmarks then for _, del in ipairs(annotations.deleted_bookmarks) do - local pos0 = del.pos0 or "" - if pos0 ~= "" then - local idx = findLocalByPos0(pos0) - if idx then - table.remove(entries, idx) - changed = true - end + local idx = findLocalByTombstone(del, false) + if idx then + table.remove(entries, idx) + changed = true end end end