From 0c815b507ebaa2ab98bce4077a978353e0222601 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2017 20:14:44 +0530 Subject: [PATCH] More work on viewer search --- src/pyj/read_book/iframe.pyj | 14 ++++++++++++-- src/pyj/read_book/resources.pyj | 17 +++++++++++++++++ src/pyj/read_book/search.pyj | 23 +++++++++++++++++++++++ src/pyj/read_book/view.pyj | 28 ++++++++++++++++++++++++---- src/pyj/utils.pyj | 4 ++++ 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 6d56b8fd4f..59cacdbaa2 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -5,6 +5,8 @@ from __python__ import bound_methods, hash_literals import traceback from aes import GCM from gettext import install, gettext as _ +from utils import html_escape + from read_book.cfi import at_current, scroll_to as scroll_to_cfi from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode, current_book from read_book.mathjax import apply_mathjax @@ -193,6 +195,8 @@ class IframeBoss: self.scroll_to_anchor(ipos.anchor) elif ipos.type is 'cfi': self.jump_to_cfi(ipos.cfi) + elif ipos.type is 'search': + self.find(ipos.search_data, True) self.onscroll() document.documentElement.addEventListener('contextmenu', def(ev): ev.preventDefault() @@ -274,12 +278,18 @@ class IframeBoss: if elem: scroll_to_elem(elem) - def find(self, data): + def find(self, data, from_load): + if data.searched_in_spine: + window.getSelection().removeAllRanges() if window.find(data.text, False, data.backwards): if current_layout_mode() is not 'flow': snap_to_selection() else: - self.send_message('find_in_spine', text=data.text, backwards=data.backwards) + if from_load: + self.send_message('error', title=_('Invisible text'), msg=_( + 'The text {} is present on this page but not visible').format(html_escape(data.text))) + else: + self.send_message('find_in_spine', text=data.text, backwards=data.backwards, searched_in_spine=data.searched_in_spine) def init(): script = document.getElementById('bootstrap') diff --git a/src/pyj/read_book/resources.pyj b/src/pyj/read_book/resources.pyj index 64e4d350f3..598ab18f36 100644 --- a/src/pyj/read_book/resources.pyj +++ b/src/pyj/read_book/resources.pyj @@ -264,3 +264,20 @@ def unserialize_html(serialized_data, proceed): else: proceeded = True proceed() + +def text_from_serialized_html(data): + serialized_data = JSON.parse(data) + tag_map = serialized_data.tag_map + ans = v'[]' + stack = v'[tree[2]]' + ignore_text = {'script':True, 'style':True} + while stack.length: + node = stack.pop() + src = tag_map[node[0]] + if not ignore_text[src.n] and src.x: + ans.push(src.x) + if src.l: + ans.push(src.l) + for v'var i = node.length - 1; i >= 1; i--': + stack.push(node[i]) + return ans.join('') diff --git a/src/pyj/read_book/search.pyj b/src/pyj/read_book/search.pyj index cbbc84fa59..adda9a4e19 100644 --- a/src/pyj/read_book/search.pyj +++ b/src/pyj/read_book/search.pyj @@ -8,6 +8,7 @@ from keycodes import get_key from elementmaker import E from gettext import gettext as _ from book_list.theme import get_color +from read_book.resources import text_from_serialized_html CLASS_NAME = 'book-search-container' @@ -72,3 +73,25 @@ class SearchOverlay: def find_previous(self): self.find(self.search_text, True) + + +def find_in_serialized_html(data, text): + haystack = text_from_serialized_html(data) + return haystack.toLowerCase().indexOf(text) > -1 + + +def find_in_spine(names, book, db, text, proceed): + text = text.toLowerCase() + + def got_one(data, name, mimetype): + if find_in_serialized_html(data, text): + proceed(name) + else: + do_one() + + def do_one(): + name = names.shift() + if name: + db.get_file(book, name, got_one) + else: + proceed(None) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index b8e81e1fce..f8dbd2f1e3 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -5,13 +5,14 @@ from __python__ import bound_methods, hash_literals from dom import set_css, add_extra_css, build_rule, svgicon from elementmaker import E from gettext import gettext as _ +from utils import html_escape -from modals import error_dialog +from modals import error_dialog, warning_dialog from book_list.globals import get_session_data, get_boss from read_book.globals import messenger, iframe_id, current_book, set_current_spine_item from read_book.resources import load_resources from read_book.overlay import Overlay -from read_book.search import SearchOverlay +from read_book.search import SearchOverlay, find_in_spine from read_book.prefs.colors import resolve_color_scheme from read_book.prefs.font_size import change_font_size_by from read_book.touch import set_left_margin_handler, set_right_margin_handler @@ -121,10 +122,29 @@ class View: self.send_message('gesture_from_margin', gesture=gesture) def find(self, text, backwards): - self.send_message('find', text=text, backwards=backwards) + self.send_message('find', text=text, backwards=backwards, searched_in_spine=False) def on_find_in_spine(self, data): - pass + if data.searched_in_spine: + warning_dialog(_('Not found'), _('The text: {} was not found in this book').format(html_escape(data.text))) + return + spine = self.book.manifest.spine + idx = spine.indexOf(self.currently_showing.name) + if idx < 0: + error_dialog(_('Missing file'), _( + 'Could not search as the spine item {} is missing from the book'.format(self.currently_showing.name))) + return + names = v'[]' + item_groups = [range(idx-1, -1, -1), range(spine.length-1, idx, -1)] if data.backwards else [range(idx + 1, spine.length), range(idx)] + for items in item_groups: + for i in items: + names.push(spine[i]) + find_in_spine(names, self.book, self.ui.db, data.text, def(found_in): + if found_in: + self.show_name(found_in, initial_position={'type':'search', 'search_data':data, 'replace_history':True}) + else: + self.send_message('find', text=data.text, backwards=data.backwards, searched_in_spine=True) + ) def bump_font_size(self, data): delta = 2 if data.increase else -2 diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj index 837f189736..a40ac2de47 100644 --- a/src/pyj/utils.pyj +++ b/src/pyj/utils.pyj @@ -141,6 +141,10 @@ def viewport_to_document(x, y, doc): def username_key(username): return ('u' if username else 'n') + username +def html_escape(text): + repl = { '&': "&", '"': """, '<': "<", '>': ">" } + return String.prototype.replace.call(text, /[&"<>]/g, def (c): return repl[c];) + def uniq(vals): # Remove all duplicates from vals, while preserving order ans = v'[]'