From 78dea8e4398ecf5543593bdcd31b543dacf185e6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 23 Jun 2021 10:17:02 +0530 Subject: [PATCH] E-book viewer: Allow clicking links in popup footnotes. Fixes #1931646 [Footnotes/Endnotes don't return to text](https://bugs.launchpad.net/calibre/+bug/1931646) --- src/pyj/read_book/content_popup.pyj | 8 +++++ src/pyj/read_book/footnotes.pyj | 46 +++++++++++++++++++++++++++-- src/pyj/read_book/iframe.pyj | 6 +++- src/pyj/read_book/view.pyj | 7 +++++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/pyj/read_book/content_popup.pyj b/src/pyj/read_book/content_popup.pyj index 4cf568e940..ad3be23c8c 100644 --- a/src/pyj/read_book/content_popup.pyj +++ b/src/pyj/read_book/content_popup.pyj @@ -58,6 +58,8 @@ class ContentPopupOverlay: 'ready': self.on_iframe_ready, 'error': self.view.on_iframe_error, 'content_loaded': self.on_content_loaded, + 'print': self.on_print, + 'link_activated': self.on_link_activated, } iframe_kw = { 'seamless': True, 'sandbox': 'allow-scripts', 'style': 'width: 100%; max-height: 70vh' @@ -72,6 +74,12 @@ class ContentPopupOverlay: c = self.container c.firstChild.appendChild(iframe) + def on_print(self, data): + print(data.string) + + def on_link_activated(self, data): + self.view.link_in_content_popup_activated(data.name, data.frag, data.is_popup, data.title) + @property def container(self): return document.getElementById('book-content-popup-overlay') diff --git a/src/pyj/read_book/footnotes.pyj b/src/pyj/read_book/footnotes.pyj index d32c7e8d06..9a48ca783e 100644 --- a/src/pyj/read_book/footnotes.pyj +++ b/src/pyj/read_book/footnotes.pyj @@ -4,6 +4,7 @@ from __python__ import bound_methods, hash_literals from dom import clear from iframe_comm import IframeClient +from read_book.globals import runtime, current_spine_item, set_current_spine_item from read_book.resources import finalize_resources, unserialize_html from read_book.settings import ( apply_settings, set_color_scheme_class, update_settings @@ -194,11 +195,12 @@ class PopupIframeBoss: self.blob_url_map = {} self.name = None self.frag = None + self.link_attr = None def initialize(self, data): - window.addEventListener('error', self.onerror) + window.addEventListener('error', self.on_error) - def onerror(self, evt): + def on_error(self, evt): msg = evt.message script_url = evt.filename line_number = evt.lineno @@ -217,6 +219,16 @@ class PopupIframeBoss: self.book = data.book self.name = data.name self.frag = data.frag + self.link_attr = 'data-' + self.book.manifest.link_uid + spine = self.book.manifest.spine + index = spine.indexOf(data.name) + set_current_spine_item({ + 'name':data.name, + 'is_first':index is 0, + 'is_last':index is spine.length - 1, + 'index': index, + 'initial_position':data.initial_position + }) update_settings(data.settings) for name in self.blob_url_map: window.URL.revokeObjectURL(self.blob_url_map[name]) @@ -224,6 +236,35 @@ class PopupIframeBoss: root_data, self.mathjax, self.blob_url_map = finalize_resources(self.book, data.name, data.resource_data) self.resource_urls = unserialize_html(root_data, self.content_loaded, self.show_only_footnote, data.name) + def connect_links(self): + for a in document.body.querySelectorAll(f'a[{self.link_attr}]'): + a.addEventListener('click', self.link_activated) + if runtime.is_standalone_viewer: + # links with a target get turned into requests to open a new window by Qt + for a in document.body.querySelectorAll('a[target]'): + a.removeAttribute('target') + + def link_activated(self, evt): + try: + data = JSON.parse(evt.currentTarget.getAttribute(self.link_attr)) + except: + print('WARNING: Failed to parse link data {}, ignoring'.format(evt.currentTarget?.getAttribute?(self.link_attr))) + return + self.activate_link(data.name, data.frag, evt.currentTarget) + + def activate_link(self, name, frag, target_elem): + if not name: + name = current_spine_item().name + try: + is_popup = is_footnote_link(target_elem, name, frag, current_spine_item().name, self.book.manifest.link_to_map or {}) + title = target_elem.textContent + except: + import traceback + traceback.print_exc() + is_popup = False + title = '' + self.comm.send_message('link_activated', is_popup=is_popup, name=name, frag=frag, title=title) + def on_clear(self, data): clear(document.head) clear(document.body) @@ -242,6 +283,7 @@ class PopupIframeBoss: if not self.comm.encrypted_communications: window.setTimeout(self.content_loaded, 2) return + self.connect_links() # this is the loading styles used to suppress scrollbars during load # added in unserialize_html document.head.removeChild(document.head.firstChild) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index ee2506d45b..149620390a 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -154,6 +154,7 @@ class IframeBoss: 'scroll_to_anchor': self.on_scroll_to_anchor, 'scroll_to_frac': self.on_scroll_to_frac, 'scroll_to_ref': self.on_scroll_to_ref, + 'fake_popup_activation': self.on_fake_popup_activation, 'set_reference_mode': self.set_reference_mode, 'toggle_autoscroll': self.toggle_autoscroll, 'fake_wheel_event': self.fake_wheel_event, @@ -718,7 +719,7 @@ class IframeBoss: traceback.print_exc() is_popup = False if is_popup: - self.send_message('show_footnote', name=name, frag=frag, title=target_elem.textContent, cols_per_screen=calc_columns_per_screen()) + self.on_fake_popup_activation({'name': name, 'frag': frag, 'title': target_elem.textContent}) return if name is current_spine_item().name: self.replace_history_on_next_cfi_update = False @@ -726,6 +727,9 @@ class IframeBoss: else: self.send_message('scroll_to_anchor', name=name, frag=frag) + def on_fake_popup_activation(self, data): + self.send_message('show_footnote', name=data.name, frag=data.frag, title=data.title, cols_per_screen=calc_columns_per_screen()) + def scroll_to_anchor(self, frag): if frag: elem = document.getElementById(frag) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index feddd39dd6..f02ac35559 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -1062,6 +1062,13 @@ class View: def on_scroll_to_anchor(self, data): self.show_name(data.name, initial_position={'type':'anchor', 'anchor':data.frag, 'replace_history':False}) + def link_in_content_popup_activated(self, name, frag, is_popup, title): + self.content_popup_overlay.hide() + if is_popup: + self.iframe_wrapper.send_message('fake_popup_activation', name=name, frag=frag, title=title) + else: + self.goto_named_destination(name, frag) + def goto_cfi(self, bookpos, add_to_history): cfiname, internal_cfi = self.parse_cfi(bookpos, self.book) if cfiname and internal_cfi: