diff --git a/src/calibre/gui2/fts/search.py b/src/calibre/gui2/fts/search.py
index cec94d1808..5b5160a3b6 100644
--- a/src/calibre/gui2/fts/search.py
+++ b/src/calibre/gui2/fts/search.py
@@ -500,7 +500,8 @@ class SearchInputPanel(QWidget):
self.related = rw = QCheckBox(_('&Match on related words'))
rw.setToolTip('
' + _(
'With this option searching for words will also match on any related words (supported in several languages). For'
- ' example, in the English language: correction matches correcting and corrected as well'))
+ ' example, in the English language: {0} matches {1} and {2} as well').format(
+ 'correction', 'correcting', 'corrected'))
rw.setChecked(gprefs['fts_library_use_stemmer'])
rw.stateChanged.connect(lambda state: gprefs.set('fts_library_use_stemmer', state != Qt.CheckState.Unchecked.value))
self.summary = s = QLabel(self)
diff --git a/src/pyj/book_list/fts.pyj b/src/pyj/book_list/fts.pyj
index 5e5a0db2ff..b3bb016a6e 100644
--- a/src/pyj/book_list/fts.pyj
+++ b/src/pyj/book_list/fts.pyj
@@ -2,13 +2,102 @@
# License: GPL v3 Copyright: 2022, Kovid Goyal
from __python__ import bound_methods, hash_literals
+from elementmaker import E
+from book_list.globals import get_session_data
+from book_list.router import back
+from book_list.top_bar import create_top_bar
from book_list.ui import set_panel_handler
+from complete import create_search_bar
+from dom import add_extra_css, clear, set_css
+from gettext import gettext as _
+from widgets import create_button
+
+overall_container_id = ''
+
+add_extra_css(def():
+ sel = '.fts-help-display '
+ style = f'{sel} ' + '{ margin-left: 1em; padding-top: 0.5ex }\n'
+ style += f'{sel} div' + ' { margin-top: 0.5ex }\n'
+ style += f'{sel} .h' + ' { font-weight: bold; padding-bottom: 0.25ex }\n'
+ style += f'{sel} .bq' + ' { margin-left: 1em; margin-top: 0.5ex; margin-bottom: 0.5ex; font-style: italic }\n'
+ style += f'{sel} p' + ' { margin: 0}\n'
+ return style
+)
+
+def component(name):
+ return document.getElementById(overall_container_id).querySelector(f'[data-component="{name}"]')
+
+
+def execute_search_interactive():
+ pass
+
+
+def build_search_page():
+ # search input container
+ container = component('search')
+ clear(container)
+ search_button = create_button(_('Search'), icon='search', tooltip=_('Do the search'))
+ search_bar = create_search_bar(execute_search_interactive, 'search-books-fts', tooltip=_('Search for books'), placeholder=_('Enter words to search for'), button=search_button)
+ set_css(search_bar, flex_grow='10', margin_right='0.5em')
+ container.appendChild(E.div(style="display: flex; width: 100%;", search_bar, search_button))
+ sd = get_session_data()
+ related_words = E.label(E.input(
+ type="checkbox", data_component="related_words", checked=bool(sd.get('fts_related_words'))),
+ onchange=def():
+ get_session_data().set('fts_related_words', bool(component('related_words').checked))
+ , ' ' + _('Match on related words'),
+ title=_(
+ 'With this option searching for words will also match on any related words (supported in several languages). For'
+ ' example, in the English language: {0} matches {1} and {2} as well').format(
+ 'correction', 'correcting', 'corrected')
+ )
+ container.appendChild(E.div(style="text-align:left; padding-top: 1ex", related_words))
+
+
+def show_search_help():
+ container = component('results')
+ clear(container)
+ container.appendChild(E.div(class_='fts-help-display'))
+ container = container.firstChild
+ fts_url = 'https://www.sqlite.org/fts5.html#full_text_query_syntax'
+ html = _('''
+Search for single words
+Simply type the word:
+awesome
calibre
+
+Search for phrases
+Enclose the phrase in quotes:
+"early run"
"song of love"
+
+Boolean searches
+(calibre AND ebook) NOT gun
simple NOT ("high bar" OR hard)
+
+Phrases near each other
+NEAR("people" "in Asia" "try")
NEAR("Kovid" "calibre", 30)
+Here, 30 is the most words allowed between near groups. Defaults to 10 when unspecified.
+
+
+''' + '').format(fts_url=fts_url)
+ container.innerHTML = html
+ a = container.querySelector('a[href]')
+ a.setAttribute('target', '_new')
+ a.classList.add('blue-link')
def init(container_id):
- c = document.getElementById(container_id)
- c.innerText = 'TODO: Implement me'
+ nonlocal overall_container_id
+ overall_container_id = container_id
+ container = document.getElementById(container_id)
+ create_top_bar(container, title=_('Search text of books'), action=back, icon='close')
+ container.appendChild(E.div(data_component='indexing'))
+ container.appendChild(E.div(
+ data_component='search',
+ style="text-align:center; padding:1ex 1em; border-bottom: solid 1px currentColor; margin-top: 0.5ex; padding-bottom: 1.5ex; margin-bottom: 0.5ex"
+ ))
+ container.appendChild(E.div(data_component='results'))
+ build_search_page()
+ show_search_help()
set_panel_handler('fts', init)
diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj
index 4249060164..0c1d9cd29e 100644
--- a/src/pyj/session.pyj
+++ b/src/pyj/session.pyj
@@ -14,6 +14,7 @@ defaults = {
'show_all_metadata': False, # show all metadata fields in the book details panel
'sort': 'timestamp.desc', # comma separated list of items of the form: field.order
'view_mode': 'cover_grid',
+ 'fts_related_words': True,
# Tag Browser settings
'and_search_terms': False, # how to add search terms to the search expression from the Tag Browser