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
|
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):
|
def remove(node):
|
||||||
if node.parentNode:
|
if node.parentNode:
|
||||||
node.parentNode.removeChild(node)
|
node.parentNode.removeChild(node)
|
||||||
|
@ -6,8 +6,8 @@ import traceback
|
|||||||
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 (
|
||||||
highlight_associated_with_selection, last_span_for_crw, reset_highlight_counter,
|
all_annots_in_selection, highlight_associated_with_selection, last_span_for_crw,
|
||||||
select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range
|
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.cfi import cfi_for_selection, range_from_cfi
|
||||||
from read_book.extract import get_elements
|
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.hints import apply_prefix_to_hints, hint_visible_links, unhint_links
|
||||||
from read_book.mathjax import apply_mathjax
|
from read_book.mathjax import apply_mathjax
|
||||||
from read_book.paged_mode import (
|
from read_book.paged_mode import (
|
||||||
anchor_funcs as paged_anchor_funcs,
|
anchor_funcs as paged_anchor_funcs, auto_scroll_action as paged_auto_scroll_action,
|
||||||
auto_scroll_action as paged_auto_scroll_action, calc_columns_per_screen,
|
calc_columns_per_screen, cancel_drag_scroll as cancel_drag_scroll_paged,
|
||||||
cancel_drag_scroll as cancel_drag_scroll_paged, current_cfi,
|
current_cfi,
|
||||||
ensure_selection_boundary_visible as ensure_selection_boundary_visible_paged,
|
ensure_selection_boundary_visible as ensure_selection_boundary_visible_paged,
|
||||||
get_columns_per_screen_data, handle_gesture as paged_handle_gesture,
|
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,
|
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,
|
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
|
||||||
start_drag_scroll as start_drag_scroll_paged, will_columns_per_screen_change
|
start_drag_scroll as start_drag_scroll_paged, will_columns_per_screen_change
|
||||||
)
|
)
|
||||||
from read_book.referencing import (
|
from read_book.referencing import elem_for_ref, end_reference_mode, start_reference_mode
|
||||||
elem_for_ref, end_reference_mode, start_reference_mode
|
|
||||||
)
|
|
||||||
from read_book.resources import finalize_resources, unserialize_html
|
from read_book.resources import finalize_resources, unserialize_html
|
||||||
from read_book.settings import (
|
from read_book.settings import (
|
||||||
apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts,
|
apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts,
|
||||||
@ -808,6 +806,44 @@ class IframeBoss:
|
|||||||
else:
|
else:
|
||||||
end_reference_mode()
|
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):
|
def annotations_msg_received(self, data):
|
||||||
dtype = data?.type
|
dtype = data?.type
|
||||||
if dtype is 'move-end-of-selection':
|
if dtype is 'move-end-of-selection':
|
||||||
@ -863,48 +899,20 @@ class IframeBoss:
|
|||||||
# not hide itself on multiline selections
|
# not hide itself on multiline selections
|
||||||
window.getSelection().removeAllRanges()
|
window.getSelection().removeAllRanges()
|
||||||
elif dtype is 'apply-highlight':
|
elif dtype is 'apply-highlight':
|
||||||
sel = window.getSelection()
|
existing = all_annots_in_selection(window.getSelection(), annot_id_uuid_map)
|
||||||
if not sel.rangeCount:
|
if existing.length is 0 or (existing.length is 1 and existing[0] is data.existing):
|
||||||
return
|
self.apply_highlight(data.uuid, data.existing, data.has_notes, data.style)
|
||||||
anchor_before = find_anchor_before_range(sel.getRangeAt(0), self.book.manifest.toc_anchor_map, self.anchor_funcs)
|
else:
|
||||||
text = sel.toString()
|
self.send_message(
|
||||||
bounds = cfi_for_selection()
|
'annotations', type='highlight-overlapped',
|
||||||
style = highlight_style_as_css(data.style, opts.is_dark_theme, opts.color_scheme.foreground)
|
uuid=data.uuid, existing=data.existing, has_notes=data.has_notes, style=data.style)
|
||||||
cls = 'crw-has-dot' if data.has_notes else None
|
elif dtype is 'apply-highlight-overwrite':
|
||||||
annot_id, intersecting_wrappers = wrap_text_in_range(style, None, cls, self.add_highlight_listeners)
|
self.apply_highlight(data.uuid, data.existing, data.has_notes, data.style)
|
||||||
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()
|
|
||||||
elif dtype is 'cite-current-selection':
|
elif dtype is 'cite-current-selection':
|
||||||
sel = window.getSelection()
|
sel = window.getSelection()
|
||||||
if not sel.rangeCount:
|
if not sel.rangeCount:
|
||||||
return
|
return
|
||||||
bounds = cfi_for_selection()
|
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()
|
text = sel.toString()
|
||||||
self.send_message('annotations', type='cite-data', bounds=bounds, highlighted_text=text)
|
self.send_message('annotations', type='cite-data', bounds=bounds, highlighted_text=text)
|
||||||
else:
|
else:
|
||||||
|
@ -1117,6 +1117,19 @@ class SelectionBar:
|
|||||||
toc_family = family_for_toc_node(before.id)
|
toc_family = family_for_toc_node(before.id)
|
||||||
self.annotations_manager.add_highlight(
|
self.annotations_manager.add_highlight(
|
||||||
msg, self.current_highlight_style.style, notes, toc_family)
|
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':
|
elif msg.type is 'edit-highlight':
|
||||||
if self.state is WAITING:
|
if self.state is WAITING:
|
||||||
self.create_highlight()
|
self.create_highlight()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user