Implement viewing of highlight notes by clicking on the highlight

This commit is contained in:
Kovid Goyal 2020-04-15 21:41:07 +05:30
parent be261dcd71
commit 807e5f0403
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 148 additions and 17 deletions

View File

@ -63,7 +63,7 @@ def select_crw(crw):
sel.addRange(r)
def create_wrapper_function(wrapper_elem, r, intersecting_wrappers):
def create_wrapper_function(wrapper_elem, r, intersecting_wrappers, process_wrapper):
start_node = r.startContainer
end_node = r.endContainer
start_offset = r.startOffset
@ -86,6 +86,8 @@ def create_wrapper_function(wrapper_elem, r, intersecting_wrappers):
if crw:
intersecting_wrappers[crw] = True
current_range.surroundContents(current_wrapper)
if process_wrapper:
process_wrapper(current_wrapper)
return current_wrapper
return wrap_node
@ -94,9 +96,12 @@ def create_wrapper_function(wrapper_elem, r, intersecting_wrappers):
wrapper_counter = 0
def wrap_text_in_range(style, r):
def wrap_text_in_range(style, r, process_wrapper):
if not r:
r = window.getSelection().getRangeAt(0)
sel = window.getSelection()
if not sel or not sel.rangeCount:
return None, v'[]'
r = sel.getRangeAt(0)
if r.isCollapsed:
return None, v'[]'
@ -106,7 +111,7 @@ def wrap_text_in_range(style, r):
wrapper_elem.setAttribute('style', style)
intersecting_wrappers = {}
wrap_node = create_wrapper_function(wrapper_elem, r, intersecting_wrappers)
wrap_node = create_wrapper_function(wrapper_elem, r, intersecting_wrappers, process_wrapper)
text_nodes_in_range(r).map(wrap_node)
crw = wrapper_elem.dataset.calibreRangeWrapper
v'delete intersecting_wrappers[crw]'

View File

@ -42,6 +42,14 @@ class AnnotationsManager:
if h:
return h.notes
def style_for_highlight(self, uuid):
h = self.highlights[uuid]
if h:
return h.style
def data_for_highlight(self, uuid):
return self.highlights[uuid]
def add_highlight(self, msg, style, notes):
now = Date().toISOString()
for uuid in msg.removed_highlights:
@ -95,6 +103,13 @@ highlight_colors = {
default_highlight_color = '#fce2ae'
def default_highlight_style():
return {
'background-color': default_highlight_color,
'color': highlight_colors[default_highlight_color]
}
def selection_handle(invert, style):
ans = svgicon('selection-handle')
use = ans.querySelector('use')
@ -191,10 +206,7 @@ class CreateAnnotation:
button(bb, 'pencil', _('Add a note'), self.add_notes)
sd = get_session_data()
style = sd.get('highlight_style') or {
'background-color': default_highlight_color,
'color': highlight_colors[default_highlight_color]
}
style = sd.get('highlight_style') or default_highlight_style()
self.current_highlight_style = style
lh = selection_handle(False, style)
@ -496,6 +508,10 @@ class CreateAnnotation:
def send_message(self, type, **kw):
self.view.iframe_wrapper.send_message('annotations', type=type, **kw)
def edit_highlight(self, uuid):
self.send_message('edit-highlight', uuid=uuid)
self.show()
def handle_message(self, msg):
if msg.type is 'create-annotation':
self.editing_annot_uuid = None
@ -508,6 +524,7 @@ class CreateAnnotation:
if msg.extents and msg.extents.start.x is not None:
self.place_handles(msg.extents)
self.in_flow_mode = msg.in_flow_mode
self.editing_annot_uuid = msg.existing or None
self.send_message('set-highlight-style', style=self.current_highlight_style)
elif msg.type is 'position-handles':
if self.state is WAITING_FOR_CLICK:
@ -533,6 +550,8 @@ class CreateAnnotation:
_('Failed to apply highlighting, try adjusting extent of highlight')
)
self.annotations_manager.add_highlight(msg, self.current_highlight_style, self.current_notes)
elif msg.type is 'annotation-activated':
self.view.view_annotation.show(msg.uuid)
else:
print('Ignoring annotations message with unknown type:', msg.type)
@ -563,3 +582,88 @@ class CreateAnnotation:
self.state = WAITING_FOR_DRAG
self.left_line_height = extents.start.height
self.right_line_height = extents.end.height
class ViewAnnotation:
container_id = 'view-annotation-overlay'
def __init__(self, view):
self.view = view
self.annotations_manager = view.annotations_manager
c = self.container
self.showing_uuid = None
c.style.flexDirection = 'column'
c.style.justifyContent = 'flex-end'
c.appendChild(E.div(
style='pointer-events: auto; padding: 1ex 1rem; box-sizing: border-box; border-top: solid 2px currentColor; overflow: hidden',
onclick=def(ev):
ev.preventDefault(), ev.stopPropagation()
,
E.div(
style='display: flex; justify-content: space-between; align-items: flex-start',
E.a(
svgicon('close', f'{BAR_SIZE}px', f'{BAR_SIZE}px'),
class_='simple-link', href='javascript: void', title=_('Close'),
onclick=def(ev):
self.hide()
),
E.div(
style='margin-left: 2rem; margin-right: 2rem; max-height: 20vh; overflow-y: auto; overflow-x: hidden; box-sizing: border-box; padding-top: 1ex',
class_='highlight-notes-viewer'
),
E.a(
svgicon('pencil', f'{BAR_SIZE}px', f'{BAR_SIZE}px'),
class_='simple-link', href='javascript: void', title=_('Edit this highlight'),
onclick=def(ev):
self.edit_current()
),
)
))
@property
def container(self):
return document.getElementById(self.container_id)
def show(self, uuid):
c = self.container
c.style.display = 'flex'
self.showing_uuid = uuid
s = self.annotations_manager.style_for_highlight(uuid) or default_highlight_style()
c = c.firstChild
c.style.color = s.color
c.style.backgroundColor = s['background-color']
text = self.annotations_manager.notes_for_highlight(uuid) or ''
self.display_text(text)
def display_text(self, text):
text = text or _('This highlight has no added notes')
text = text.strip()
div = self.container.querySelector('.highlight-notes-viewer')
clear(div)
current_block = ''
def add_para():
nonlocal current_block
div.appendChild(E.p(current_block))
if div.childNodes.length > 1:
div.lastChild.style.marginTop = '2ex'
current_block = ''
for line in text.splitlines():
if not line or not line.strip():
if current_block:
add_para()
continue
current_block += line + '\n'
if current_block:
add_para()
def hide(self):
self.container.style.display = 'none'
self.showing_uuid = None
def edit_current(self):
if self.showing_uuid:
self.view.create_annotation.edit_highlight(self.showing_uuid)
self.hide()

View File

@ -12,7 +12,7 @@ from select import (
from fs_images import fix_fullscreen_svg_images
from iframe_comm import IframeClient
from range_utils import (
reset_highlight_counter, set_selection_to_highlight, unwrap_crw,
reset_highlight_counter, select_crw, set_selection_to_highlight, unwrap_crw,
wrap_text_in_range
)
from read_book.cfi import (
@ -591,6 +591,12 @@ class IframeBoss:
if refnum?:
self.scroll_to_ref(refnum)
def ensure_selection_visible(self):
if current_layout_mode() is 'flow':
ensure_selection_visible()
else:
snap_to_selection()
def find(self, data, from_load):
if data.searched_in_spine:
window.getSelection().removeAllRanges()
@ -606,10 +612,7 @@ class IframeBoss:
def show_search_result(self, data, from_load):
if select_search_result(data.search_result):
if current_layout_mode() is 'flow':
ensure_selection_visible()
else:
snap_to_selection()
self.ensure_selection_visible()
else:
self.send_message('search_result_not_found', search_result=data.search_result)
@ -623,7 +626,7 @@ class IframeBoss:
else:
end_reference_mode()
def initiate_creation_of_annotation(self):
def initiate_creation_of_annotation(self, existing):
self.auto_scroll_action('stop')
in_flow_mode = current_layout_mode() is 'flow'
self.send_message(
@ -631,6 +634,7 @@ class IframeBoss:
type='create-annotation',
in_flow_mode=in_flow_mode,
extents=selection_extents(in_flow_mode),
existing=existing or None,
)
def annotations_msg_received(self, data):
@ -657,13 +661,19 @@ class IframeBoss:
self.send_message('annotations', type='update-handles', extents=selection_extents(in_flow_mode))
elif data.type is 'set-highlight-style':
set_selection_style(data.style)
elif data.type is 'edit-highlight':
crw_ = {v: k for k, v in Object.entries(self.annot_id_uuid_map)}[data.uuid]
if crw_:
select_crw(crw_)
self.ensure_selection_visible()
self.initiate_creation_of_annotation(data.uuid)
elif data.type is 'apply-highlight':
sel = window.getSelection()
text = sel.toString()
if not sel.rangeCount:
return
bounds = cfi_for_selection()
annot_id, intersecting_wrappers = wrap_text_in_range(data.style)
annot_id, intersecting_wrappers = wrap_text_in_range(data.style, None, self.add_highlight_listeners)
removed_highlights = v'[]'
if annot_id is not None:
intersecting_uuids = [self.annot_id_uuid_map[x] for x in intersecting_wrappers]
@ -704,13 +714,22 @@ class IframeBoss:
if not r:
continue
style = f'color: {h.style.color}; background-color: {h.style["background-color"]}'
annot_id, intersecting_wrappers = wrap_text_in_range(style, r)
annot_id, intersecting_wrappers = wrap_text_in_range(style, r, self.add_highlight_listeners)
if annot_id is not None:
self.annot_id_uuid_map[annot_id] = h.uuid
for crw in intersecting_wrappers:
unwrap_crw(crw)
v'delete self.annot_id_uuid_map[crw]'
def add_highlight_listeners(self, wrapper):
wrapper.addEventListener('click', self.highlight_wrapper_clicked)
def highlight_wrapper_clicked(self, ev):
crw = ev.currentTarget.dataset.calibreRangeWrapper
uuid = self.annot_id_uuid_map[crw]
if uuid:
self.send_message('annotations', type='annotation-activated', uuid=uuid)
def copy_selection(self):
text = window.getSelection().toString()
if text:

View File

@ -13,7 +13,7 @@ from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
from iframe_comm import IframeWrapper
from modals import error_dialog, warning_dialog
from read_book.content_popup import ContentPopupOverlay
from read_book.create_annotation import AnnotationsManager, CreateAnnotation
from read_book.create_annotation import AnnotationsManager, CreateAnnotation, ViewAnnotation
from read_book.globals import (
current_book, runtime, set_current_spine_item, ui_operations
)
@ -220,6 +220,7 @@ class View:
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; overflow: auto; display:none', id='book-overlay'), # main overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='controls-help-overlay'), # controls help overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none; overflow: hidden', id=CreateAnnotation.container_id, tabindex='0'), # create annotation overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; pointer-events:none; display:none; z-index: 4000', id=ViewAnnotation.container_id), # view annotation overlay
)
),
E.div(
@ -285,6 +286,7 @@ class View:
self.book_scrollbar.apply_visibility()
self.annotations_manager = AnnotationsManager(self)
self.create_annotation = CreateAnnotation(self)
self.view_annotation = ViewAnnotation(self)
@property
def iframe(self):
@ -567,6 +569,7 @@ class View:
self.content_popup_overlay.hide()
self.reference_mode_overlay.style.display = 'none'
self.create_annotation.hide()
self.view_annotation.hide()
self.focus_iframe()
def focus_iframe(self):