Add syncing of annotations along with last read position

This commit is contained in:
Kovid Goyal 2020-07-01 12:40:42 +05:30
parent 0c4010b3af
commit 14b87b8446
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 55 additions and 12 deletions

View File

@ -62,10 +62,9 @@ def sync_data_received(library_id, lrmap, load_type, xhr, ev):
print('Failed to get book sync data') print('Failed to get book sync data')
return return
data = JSON.parse(xhr.responseText) data = JSON.parse(xhr.responseText)
last_read_data = data.last_read_positions
db = get_db() db = get_db()
for key in last_read_data: for key in data:
new_vals = data[key] new_vals = data[key]
entry = {'last_read': None, 'last_read_position': None, 'annotations_map': None} entry = {'last_read': None, 'last_read_position': None, 'annotations_map': None}
prev_last_read = lrmap[key] prev_last_read = lrmap[key]

View File

@ -53,6 +53,11 @@ def unwrap_crw(crw):
unwrap(node) unwrap(node)
def unwrap_all_crw():
for node in document.querySelectorAll('span[data-calibre-range-wrapper]'):
unwrap(node)
def select_crw(crw): def select_crw(crw):
nodes = document.querySelectorAll(f'span[data-calibre-range-wrapper="{crw}"]') nodes = document.querySelectorAll(f'span[data-calibre-range-wrapper="{crw}"]')
r = document.createRange() r = document.createRange()

View File

@ -10,6 +10,7 @@ from book_list.globals import get_session_data
from book_list.theme import cached_color_to_rgba, get_color from book_list.theme import cached_color_to_rgba, get_color
from dom import clear, ensure_id, svgicon, unique_id from dom import clear, ensure_id, svgicon, unique_id
from modals import error_dialog, question_dialog from modals import error_dialog, question_dialog
from read_book.annotations import merge_annotation_maps
from read_book.globals import ui_operations from read_book.globals import ui_operations
from read_book.shortcuts import shortcut_for_key_event from read_book.shortcuts import shortcut_for_key_event
@ -24,6 +25,17 @@ class AnnotationsManager:
highlights = highlights or v'[]' highlights = highlights or v'[]'
self.highlights = {h.uuid: h for h in highlights} self.highlights = {h.uuid: h for h in highlights}
def merge_highlights(self, highlights):
highlights = highlights or v'[]'
updated = False
if highlights.length:
base = {'highlight': Object.values(self.highlights)}
newvals = {'highlight': highlights}
updated, ans = merge_annotation_maps(base, newvals)
if updated:
self.set_highlights(ans)
return updated
def remove_highlight(self, uuid): def remove_highlight(self, uuid):
h = self.highlights[uuid] h = self.highlights[uuid]
if h: if h:

View File

@ -12,8 +12,8 @@ from select import (
from fs_images import fix_fullscreen_svg_images from fs_images import fix_fullscreen_svg_images
from iframe_comm import IframeClient from iframe_comm import IframeClient
from range_utils import ( from range_utils import (
reset_highlight_counter, select_crw, set_selection_to_highlight, unwrap_crw, reset_highlight_counter, select_crw, set_selection_to_highlight, unwrap_all_crw,
wrap_text_in_range unwrap_crw, wrap_text_in_range
) )
from read_book.cfi import ( from read_book.cfi import (
cfi_for_selection, range_from_cfi, scroll_to as scroll_to_cfi cfi_for_selection, range_from_cfi, scroll_to as scroll_to_cfi
@ -138,6 +138,7 @@ class IframeBoss:
'handle_navigation_shortcut': self.on_handle_navigation_shortcut, 'handle_navigation_shortcut': self.on_handle_navigation_shortcut,
'annotations': self.annotations_msg_received, 'annotations': self.annotations_msg_received,
'copy_selection': self.copy_selection, 'copy_selection': self.copy_selection,
'replace_highlights': self.replace_highlights,
} }
self.comm = IframeClient(handlers) self.comm = IframeClient(handlers)
self.last_window_ypos = 0 self.last_window_ypos = 0
@ -770,6 +771,7 @@ class IframeBoss:
def apply_highlights_on_load(self, highlights): def apply_highlights_on_load(self, highlights):
clear_annot_id_uuid_map() clear_annot_id_uuid_map()
reset_highlight_counter()
strcmp = v'new Intl.Collator().compare' strcmp = v'new Intl.Collator().compare'
highlights.sort(def (a, b): return strcmp(a.timestamp, b.timestamp);) highlights.sort(def (a, b): return strcmp(a.timestamp, b.timestamp);)
for h in highlights: for h in highlights:
@ -784,6 +786,11 @@ class IframeBoss:
unwrap_crw(crw) unwrap_crw(crw)
v'delete annot_id_uuid_map[crw]' v'delete annot_id_uuid_map[crw]'
def replace_highlights(self, data):
highlights = data.highlights
unwrap_all_crw()
self.apply_highlights_on_load(highlights or v'[]')
def add_highlight_listeners(self, wrapper): def add_highlight_listeners(self, wrapper):
wrapper.addEventListener('dblclick', self.highlight_wrapper_dblclicked) wrapper.addEventListener('dblclick', self.highlight_wrapper_dblclicked)

View File

@ -131,8 +131,8 @@ class SyncBook: # {{{
container.appendChild(E.div( container.appendChild(E.div(
style='display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%', style='display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%',
E.div(style='margin:1ex 1em; max-width: 80vw', E.div(style='margin:1ex 1em; max-width: 80vw',
E.h2(_('Syncing to last read position')), E.h2(_('Syncing last read position and annotations')),
E.p(_('Downloading last read data from server, please wait...')), E.p(_('Downloading data from server, please wait...')),
E.div(style='display:flex; justify-content:flex-end', E.div(style='display:flex; justify-content:flex-end',
create_button(_('Cancel'), action=self.cancel), create_button(_('Cancel'), action=self.cancel),
) )
@ -154,25 +154,28 @@ class SyncBook: # {{{
if xhr.responseText is 'login required for sync': if xhr.responseText is 'login required for sync':
error_dialog(_('Failed to fetch sync data'), _('You must setup user accounts and login to use the sync functionality')) error_dialog(_('Failed to fetch sync data'), _('You must setup user accounts and login to use the sync functionality'))
else: else:
error_dialog(_('Failed to fetch sync data'), _('Failed to download last read data from server, click "Show details" for more information.'), xhr.error_html) error_dialog(_('Failed to fetch sync data'), _('Failed to download sync data from server, click "Show details" for more information.'), xhr.error_html)
return return
data = JSON.parse(xhr.responseText) data = JSON.parse(xhr.responseText)
book = self.overlay.view.book book = self.overlay.view.book
dev = get_device_uuid() dev = get_device_uuid()
epoch = 0 epoch = 0
ans = None ans = None
new_annotations_map = None
for key in data: for key in data:
book_id, fmt = key.partition(':')[::2] book_id, fmt = key.partition(':')[::2]
if book_id is str(book.key[1]) and fmt.upper() is book.key[2].upper(): if book_id is str(book.key[1]) and fmt.upper() is book.key[2].upper():
last_read_positions = data[key] new_vals = data[key]
last_read_positions = new_vals.last_read_positions
new_annotations_map = new_vals.annotations_map
for d in last_read_positions: for d in last_read_positions:
if d.device is not dev and d.epoch > epoch: if d.device is not dev and d.epoch > epoch:
epoch = d.epoch epoch = d.epoch
ans = d ans = d
if ans is not None: break
cfi = ans.cfi cfi = ans?.cfi
if cfi: if new_annotations_map or cfi:
self.overlay.view.goto_cfi(cfi) self.overlay.view.sync_data_received(cfi, new_annotations_map)
# }}} # }}}

View File

@ -74,6 +74,7 @@ class ReadUI:
ui_operations.toggle_toc = self.toggle_toc.bind(self) ui_operations.toggle_toc = self.toggle_toc.bind(self)
ui_operations.toggle_full_screen = self.toggle_full_screen.bind(self) ui_operations.toggle_full_screen = self.toggle_full_screen.bind(self)
ui_operations.highlights_changed = self.highlights_changed.bind(self) ui_operations.highlights_changed = self.highlights_changed.bind(self)
ui_operations.annotations_synced = self.annotations_synced.bind(self)
def on_resize(self): def on_resize(self):
self.view.on_resize() self.view.on_resize()
@ -201,6 +202,12 @@ class ReadUI:
self.db.update_annotations_data_from_key(library_id, book_id, fmt, amap) self.db.update_annotations_data_from_key(library_id, book_id, fmt, amap)
ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap, def (): pass;) ajax_send(f'book-update-annotations/{library_id}/{book_id}/{fmt}', amap, def (): pass;)
def annotations_synced(self, amap):
library_id = self.base_url_data.library_id
book_id = self.base_url_data.book_id
fmt = self.base_url_data.fmt
self.db.update_annotations_data_from_key(library_id, book_id, fmt, amap)
@property @property
def url_data(self): def url_data(self):
ans = {'library_id':self.base_url_data.library_id, 'book_id':self.base_url_data.book_id, 'fmt': self.base_url_data.fmt} ans = {'library_id':self.base_url_data.library_id, 'book_id':self.base_url_data.book_id, 'fmt': self.base_url_data.fmt}

View File

@ -989,6 +989,16 @@ class View:
process_node(toc) process_node(toc)
return found return found
def sync_data_received(self, reading_pos_cfi, annotations_map):
if annotations_map:
ui_operations.annotations_synced(annotations_map)
if annotations_map.highlight:
if self.annotations_manager.merge_highlights(annotations_map.highlight):
hl = self.annotations_manager.highlights_for_currently_showing()
self.iframe_wrapper.send_message('replace_highlights', highlights=hl)
if reading_pos_cfi:
self.goto_cfi(reading_pos_cfi)
def on_next_spine_item(self, data): def on_next_spine_item(self, data):
spine = self.book.manifest.spine spine = self.book.manifest.spine
idx = spine.indexOf(self.currently_showing.name) idx = spine.indexOf(self.currently_showing.name)