mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
E-book viewer: Ask for confirmation when creating a highlight that will overwrite existing highlights. Fixes #1991597 [Highlights in reader, when nested, overwrite one another](https://bugs.launchpad.net/calibre/+bug/1991597)
This commit is contained in:
parent
fe310342d0
commit
eebdd57a90
@ -48,6 +48,34 @@ def first_annot_in_range(r, annot_id_uuid_map):
|
||||
break
|
||||
|
||||
|
||||
def all_annots_in_range(r, annot_id_uuid_map, ans):
|
||||
parent = r.commonAncestorContainer
|
||||
doc = parent.ownerDocument or document
|
||||
iterator = doc.createNodeIterator(parent)
|
||||
in_range = False
|
||||
while True:
|
||||
node = iterator.nextNode()
|
||||
if not node:
|
||||
break
|
||||
if not in_range and node.isSameNode(r.startContainer):
|
||||
in_range = True
|
||||
if in_range:
|
||||
if node.dataset and node.dataset.calibreRangeWrapper:
|
||||
annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper]
|
||||
if annot_id:
|
||||
ans.push(annot_id)
|
||||
if node.isSameNode(r.endContainer):
|
||||
break
|
||||
return ans
|
||||
|
||||
|
||||
def all_annots_in_selection(sel, annot_id_uuid_map):
|
||||
ans = v'[]'
|
||||
for i in range(sel.rangeCount):
|
||||
all_annots_in_range(sel.getRangeAt(i), annot_id_uuid_map, ans)
|
||||
return ans
|
||||
|
||||
|
||||
def remove(node):
|
||||
if node.parentNode:
|
||||
node.parentNode.removeChild(node)
|
||||
|
@ -6,8 +6,8 @@ import traceback
|
||||
from fs_images import fix_fullscreen_svg_images
|
||||
from iframe_comm import IframeClient
|
||||
from range_utils import (
|
||||
highlight_associated_with_selection, last_span_for_crw, reset_highlight_counter,
|
||||
select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range
|
||||
all_annots_in_selection, highlight_associated_with_selection, last_span_for_crw,
|
||||
reset_highlight_counter, select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range
|
||||
)
|
||||
from read_book.cfi import cfi_for_selection, range_from_cfi
|
||||
from read_book.extract import get_elements
|
||||
@ -35,9 +35,9 @@ from read_book.highlights import highlight_style_as_css
|
||||
from read_book.hints import apply_prefix_to_hints, hint_visible_links, unhint_links
|
||||
from read_book.mathjax import apply_mathjax
|
||||
from read_book.paged_mode import (
|
||||
anchor_funcs as paged_anchor_funcs,
|
||||
auto_scroll_action as paged_auto_scroll_action, calc_columns_per_screen,
|
||||
cancel_drag_scroll as cancel_drag_scroll_paged, current_cfi,
|
||||
anchor_funcs as paged_anchor_funcs, auto_scroll_action as paged_auto_scroll_action,
|
||||
calc_columns_per_screen, cancel_drag_scroll as cancel_drag_scroll_paged,
|
||||
current_cfi,
|
||||
ensure_selection_boundary_visible as ensure_selection_boundary_visible_paged,
|
||||
get_columns_per_screen_data, handle_gesture as paged_handle_gesture,
|
||||
handle_shortcut as paged_handle_shortcut, jump_to_cfi as paged_jump_to_cfi,
|
||||
@ -49,9 +49,7 @@ from read_book.paged_mode import (
|
||||
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
|
||||
start_drag_scroll as start_drag_scroll_paged, will_columns_per_screen_change
|
||||
)
|
||||
from read_book.referencing import (
|
||||
elem_for_ref, end_reference_mode, start_reference_mode
|
||||
)
|
||||
from read_book.referencing import elem_for_ref, end_reference_mode, start_reference_mode
|
||||
from read_book.resources import finalize_resources, unserialize_html
|
||||
from read_book.settings import (
|
||||
apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts,
|
||||
@ -808,6 +806,44 @@ class IframeBoss:
|
||||
else:
|
||||
end_reference_mode()
|
||||
|
||||
def apply_highlight(self, uuid, existing, has_notes, style):
|
||||
sel = window.getSelection()
|
||||
if not sel.rangeCount:
|
||||
return
|
||||
anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.anchor_funcs)
|
||||
text = sel.toString()
|
||||
bounds = cfi_for_selection()
|
||||
style = highlight_style_as_css(style, opts.is_dark_theme, opts.color_scheme.foreground)
|
||||
cls = 'crw-has-dot' if has_notes else None
|
||||
annot_id, intersecting_wrappers = wrap_text_in_range(style, None, cls, self.add_highlight_listeners)
|
||||
removed_highlights = v'[]'
|
||||
if annot_id is not None:
|
||||
intersecting_uuids = {annot_id_uuid_map[x]:True for x in intersecting_wrappers}
|
||||
if existing and intersecting_uuids[existing]:
|
||||
uuid = existing
|
||||
elif intersecting_wrappers.length is 1 and annot_id_uuid_map[intersecting_wrappers[0]]:
|
||||
uuid = annot_id_uuid_map[intersecting_wrappers[0]]
|
||||
intersecting_wrappers = v'[]'
|
||||
removed_highlights = {}
|
||||
for crw in intersecting_wrappers:
|
||||
unwrap_crw(crw)
|
||||
if annot_id_uuid_map[crw] and annot_id_uuid_map[crw] is not uuid:
|
||||
removed_highlights[annot_id_uuid_map[crw]] = True
|
||||
v'delete annot_id_uuid_map[crw]'
|
||||
removed_highlights = Object.keys(removed_highlights)
|
||||
sel.removeAllRanges()
|
||||
annot_id_uuid_map[annot_id] = uuid
|
||||
self.send_message(
|
||||
'annotations',
|
||||
type='highlight-applied',
|
||||
uuid=uuid, ok=annot_id is not None,
|
||||
bounds=bounds,
|
||||
removed_highlights=removed_highlights,
|
||||
highlighted_text=text,
|
||||
anchor_before=anchor_before
|
||||
)
|
||||
reset_find_caches()
|
||||
|
||||
def annotations_msg_received(self, data):
|
||||
dtype = data?.type
|
||||
if dtype is 'move-end-of-selection':
|
||||
@ -863,48 +899,20 @@ class IframeBoss:
|
||||
# not hide itself on multiline selections
|
||||
window.getSelection().removeAllRanges()
|
||||
elif dtype is 'apply-highlight':
|
||||
sel = window.getSelection()
|
||||
if not sel.rangeCount:
|
||||
return
|
||||
anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.anchor_funcs)
|
||||
text = sel.toString()
|
||||
bounds = cfi_for_selection()
|
||||
style = highlight_style_as_css(data.style, opts.is_dark_theme, opts.color_scheme.foreground)
|
||||
cls = 'crw-has-dot' if data.has_notes else None
|
||||
annot_id, intersecting_wrappers = wrap_text_in_range(style, None, cls, self.add_highlight_listeners)
|
||||
removed_highlights = v'[]'
|
||||
if annot_id is not None:
|
||||
intersecting_uuids = {annot_id_uuid_map[x]:True for x in intersecting_wrappers}
|
||||
if data.existing and intersecting_uuids[data.existing]:
|
||||
data.uuid = data.existing
|
||||
elif intersecting_wrappers.length is 1 and annot_id_uuid_map[intersecting_wrappers[0]]:
|
||||
data.uuid = annot_id_uuid_map[intersecting_wrappers[0]]
|
||||
intersecting_wrappers = v'[]'
|
||||
removed_highlights = {}
|
||||
for crw in intersecting_wrappers:
|
||||
unwrap_crw(crw)
|
||||
if annot_id_uuid_map[crw] and annot_id_uuid_map[crw] is not data.uuid:
|
||||
removed_highlights[annot_id_uuid_map[crw]] = True
|
||||
v'delete annot_id_uuid_map[crw]'
|
||||
removed_highlights = Object.keys(removed_highlights)
|
||||
sel.removeAllRanges()
|
||||
annot_id_uuid_map[annot_id] = data.uuid
|
||||
self.send_message(
|
||||
'annotations',
|
||||
type='highlight-applied',
|
||||
uuid=data.uuid, ok=annot_id is not None,
|
||||
bounds=bounds,
|
||||
removed_highlights=removed_highlights,
|
||||
highlighted_text=text,
|
||||
anchor_before=anchor_before
|
||||
)
|
||||
reset_find_caches()
|
||||
existing = all_annots_in_selection(window.getSelection(), annot_id_uuid_map)
|
||||
if existing.length is 0 or (existing.length is 1 and existing[0] is data.existing):
|
||||
self.apply_highlight(data.uuid, data.existing, data.has_notes, data.style)
|
||||
else:
|
||||
self.send_message(
|
||||
'annotations', type='highlight-overlapped',
|
||||
uuid=data.uuid, existing=data.existing, has_notes=data.has_notes, style=data.style)
|
||||
elif dtype is 'apply-highlight-overwrite':
|
||||
self.apply_highlight(data.uuid, data.existing, data.has_notes, data.style)
|
||||
elif dtype is 'cite-current-selection':
|
||||
sel = window.getSelection()
|
||||
if not sel.rangeCount:
|
||||
return
|
||||
bounds = cfi_for_selection()
|
||||
anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.anchor_funcs)
|
||||
text = sel.toString()
|
||||
self.send_message('annotations', type='cite-data', bounds=bounds, highlighted_text=text)
|
||||
else:
|
||||
|
@ -1117,6 +1117,19 @@ class SelectionBar:
|
||||
toc_family = family_for_toc_node(before.id)
|
||||
self.annotations_manager.add_highlight(
|
||||
msg, self.current_highlight_style.style, notes, toc_family)
|
||||
elif msg.type is 'highlight-overlapped':
|
||||
question_dialog(
|
||||
_('Are you sure?'), _('This highlight overlaps existing highlights. Creating it will cause notes'
|
||||
' in the existing highlights to be lost. Create it anyway?'),
|
||||
def (yes):
|
||||
if yes:
|
||||
self.send_message(
|
||||
'apply-highlight-overwrite', style=msg.style, uuid=msg.uuid, existing=msg.existing, has_notes=msg.has_notes)
|
||||
else:
|
||||
if self.current_notes:
|
||||
self.show_editor(self.current_highlight_style, self.current_notes)
|
||||
,
|
||||
)
|
||||
elif msg.type is 'edit-highlight':
|
||||
if self.state is WAITING:
|
||||
self.create_highlight()
|
||||
|
Loading…
x
Reference in New Issue
Block a user