mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-30 21:41:57 -04:00
Implement viewing of highlight notes by clicking on the highlight
This commit is contained in:
parent
be261dcd71
commit
807e5f0403
@ -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]'
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user