diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 2187d6a95f..af4916bad4 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -60,6 +60,11 @@ def unwrap(node): p.normalize() +def unwrap_crw(crw): + for node in document.querySelectorAll(f'span[data-calibre-range-wrapper="{crw}"]'): + unwrap(node) + + def create_wrapper_function(wrapper_elem, r): start_node = r.startContainer end_node = r.endContainer @@ -109,3 +114,33 @@ def wrap_text_in_range(style, r): def reset_highlight_counter(): nonlocal wrapper_counter wrapper_counter = 0 + + +def is_text_node(node): + return node.nodeType is Node.TEXT_NODE or node.nodeType is Node.CDATA_SECTION_NODE + + +def range_wrappers_intersecting_selection(sel): + ans = {} + sel = sel or window.getSelection() + if not sel: + return ans + r = sel.getRangeAt(0) + if not r: + return ans + walker = document.createTreeWalker(r.commonAncestorContainer, NodeFilter.SHOW_ALL, None, False) + in_selection = False + while True: + node = walker.nextNode() + if not node: + break + if not in_selection and node is not r.startContainer: + continue + in_selection = True + if node.nodeType is Node.ELEMENT_NODE and node.dataset.calibreRangeWrapper: + ans[node.dataset.calibreRangeWrapper] = True + elif is_text_node(node) and node.parentNode.dataset.calibreRangeWrapper: + ans[node.parentNode.dataset.calibreRangeWrapper] = True + if node is r.endContainer: + break + return Object.keys(ans) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 9ef91b543d..c65be42bed 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -11,7 +11,10 @@ from select import ( from fs_images import fix_fullscreen_svg_images from iframe_comm import IframeClient -from range_utils import reset_highlight_counter, wrap_text_in_range +from range_utils import ( + range_wrappers_intersecting_selection, reset_highlight_counter, unwrap_crw, + wrap_text_in_range +) from read_book.cfi import cfi_for_selection, scroll_to as scroll_to_cfi from read_book.extract import get_elements from read_book.find import reset_find_caches, select_search_result @@ -646,12 +649,28 @@ class IframeBoss: elif data.type is 'set-highlight-style': set_selection_style(data.style) elif data.type is 'apply-highlight': + sel = window.getSelection() + text = '' + if sel: + text = sel.toString() bounds = cfi_for_selection() + intersecting_wrappers = range_wrappers_intersecting_selection() annot_id = wrap_text_in_range(data.style) + removed_highlights = {} if annot_id is not None: - window.getSelection().removeAllRanges() + sel.removeAllRanges() self.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) + for crw in intersecting_wrappers: + unwrap_crw(crw) + removed_highlights[self.annot_id_uuid_map[crw]] = True + self.send_message( + 'annotations', + type='highlight-applied', + uuid=data.uuid, ok=annot_id is not None, + bounds=bounds, + removed_highlights=Object.keys(removed_highlights), + highlighted_text=text, + ) reset_find_caches() else: console.log('Ignoring annotations message to iframe with unknown type: ' + data.type)