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'[]'