From 7852aa3eac6bd6646f7e0113bfe8d001c470fb2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 Aug 2016 10:14:18 +0530 Subject: [PATCH] Add history to the search bar widget --- src/pyj/book_list/search.pyj | 20 +++++--------------- src/pyj/complete.pyj | 34 +++++++++++++++++++++++++++------- src/pyj/popups.pyj | 12 ++++++------ src/pyj/session.pyj | 12 +++++++++--- src/pyj/utils.pyj | 10 ++++++++++ 5 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/pyj/book_list/search.pyj b/src/pyj/book_list/search.pyj index d44f6753cf..180ba57004 100644 --- a/src/pyj/book_list/search.pyj +++ b/src/pyj/book_list/search.pyj @@ -3,6 +3,7 @@ from __python__ import hash_literals from ajax import ajax +from complete import create_search_bar from dom import clear, set_css, build_rule, svgicon, add_extra_css from elementmaker import E from gettext import gettext as _ @@ -56,15 +57,10 @@ class SearchPanel: # Build search input search_container = div.firstChild - search_button = create_button(_('Search'), icon='search', action=self.execute_search.bind(self), tooltip=_('Do the search')) - search_container.appendChild(E.div(style="display: flex; width: 100%;", - E.input( - type='search', name='search-books', - title=_('Search for books'), placeholder=_('Enter the search query'), - style="flex-grow: 10; margin-right: 0.5em", onkeydown=self.on_input_keydown.bind(self), - ), - search_button - )) + search_button = create_button(_('Search'), icon='search', tooltip=_('Do the search')) + search_bar = create_search_bar(self.execute_search.bind(self), 'search-books', tooltip=_('Search for books'), placeholder=_('Enter the search query'), button=search_button) + set_css(search_bar, flex_grow='10', margin_right='0.5em') + search_container.appendChild(E.div(style="display: flex; width: 100%;", search_bar, search_button)) search_container.appendChild(E.ul(class_='search-items')) # Build loading panel @@ -80,12 +76,6 @@ class SearchPanel: self.node_id_map = {} self.active_nodes = {} - def on_input_keydown(self, event): - if event.keyCode is 13: # Enter - event.preventDefault(), event.stopPropagation() - event.target.nextSibling.focus() - self.execute_search() - def init(self): tb = self.search_control # We dont focus the search box because on mobile that will cause the diff --git a/src/pyj/complete.pyj b/src/pyj/complete.pyj index d45015b63a..5bfc245961 100644 --- a/src/pyj/complete.pyj +++ b/src/pyj/complete.pyj @@ -5,7 +5,9 @@ from __python__ import hash_literals, bound_methods from dom import ensure_id from elementmaker import E from keycodes import get_key +from session import local_storage from popups import CompletionPopup +from utils import uniq class EditWithComplete: @@ -63,14 +65,30 @@ class EditWithComplete: self.completion_popup.set_all_items(items) -def create_search_bar(action, name, tooltip=None, placeholder=None, button=None): - - parent = E.div() +def create_search_bar(action, name, tooltip=None, placeholder=None, button=None, history_size=100): + parent = E.div(style="display:flex") ewc = EditWithComplete(name, parent=parent, tooltip=tooltip, placeholder=placeholder, input_type='search') + parent.lastChild.style.width = '100%' + history_name = 'search-bar-history-' + name + + def update_completion_items(): + items = local_storage().get(history_name) + if items?.length: + ewc.set_all_items(items) + update_completion_items() def trigger(): + text = ewc.text_input.value ewc.hide_completion_popup() - action() + action(text) + if text and text.strip(): + items = local_storage().get(history_name) or v'[]' + idx = items.indexOf(text) + if idx > -1: + items = items.splice(idx, 1) + items.unshift(text) + local_storage().set(history_name, uniq(items[:history_size])) + update_completion_items() ewc.onenterkey = trigger if button: @@ -81,6 +99,8 @@ def create_search_bar(action, name, tooltip=None, placeholder=None, button=None) # }}} def main(container): - ewc = EditWithComplete(parent=container, placeholder='Testing edit with complete') - ewc.set_all_items('a a1 a11 a12 a13 b b1 b2 b3'.split(' ')) - ewc.text_input.focus() + container.appendChild(create_search_bar(print, 'test-search-bar', placeholder='Testing search bar')) + container.firstChild.lastChild.focus() + # ewc = EditWithComplete(parent=container, placeholder='Testing edit with complete') + # ewc.set_all_items('a a1 a11 a12 a13 b b1 b2 b3'.split(' ')) + # ewc.text_input.focus() diff --git a/src/pyj/popups.pyj b/src/pyj/popups.pyj index 7c8c683c7f..af2095e21c 100644 --- a/src/pyj/popups.pyj +++ b/src/pyj/popups.pyj @@ -48,7 +48,7 @@ def create_popup(parent, idprefix): div = E.div(id=pid, style='display: none; position: absolute; z-index: {}'.format(POPUP_Z_INDEX)) parent = parent or document.body parent.appendChild(div) - return pid + return div def show_popup(popup_id, associated_widget_ids=None): elem = document.getElementById(popup_id) @@ -68,12 +68,12 @@ class CompletionPopup: def __init__(self, parent=None, max_items=25, onselect=None): self.max_items = max_items - self.container_id = create_popup(parent) + c = create_popup(parent) + set_css(c, user_select='none') + self.container_id = c.getAttribute('id') self.onselect = onselect self.items = [] self.matches = [] - c = self.container - set_css(c, user_select='none') c.appendChild(E.div(class_=self.CLASS)) self.associated_widget_ids = set() self.current_query, self.is_upwards = '', False @@ -88,7 +88,7 @@ class CompletionPopup: return self.container.style.display is not 'none' def set_all_items(self, items): - self.items = items + self.items = list(items) self.matches = [] self.applied_query = '' @@ -210,7 +210,7 @@ class CompletionPopup: add_extra_css(def(): sel = 'div.' + CompletionPopup.CLASS - style = build_rule(sel, overflow='hidden', background_color=get_color('window-background'), border='solid 1px ' + get_color('window-foreground')) + style = build_rule(sel, overflow='hidden', text_align='left', background_color=get_color('window-background'), border='solid 1px ' + get_color('window-foreground')) sel += ' > div' style += build_rule(sel, cursor='pointer', padding='1ex 1rem', white_space='nowrap', text_overflow='ellipsis', overflow='hidden') sel += '.' + CompletionPopup.CURRENT_ITEM_CLASS diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index cc318cbb40..4f5373b20e 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -72,9 +72,8 @@ def get_session_storage(): class SessionData: - global_prefix = 'calibre-' - - def __init__(self): + def __init__(self, global_prefix=None): + self.global_prefix = global_prefix or 'calibre-session-' self.storage = get_session_storage() self.overflow_storage = {} self.has_overflow = False @@ -115,6 +114,13 @@ class SessionData: self.overflow_storage = {} self.has_overflow = False +_local_storage = None +def local_storage(): + nonlocal _local_storage + if not _local_storage: + _local_storage = SessionData('calibre-local-') + return _local_storage + class UserSessionData(SessionData): def __init__(self, username, saved_data): diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj index 2e895796c7..f6905cdcf2 100644 --- a/src/pyj/utils.pyj +++ b/src/pyj/utils.pyj @@ -131,6 +131,16 @@ def viewport_to_document(x, y, doc): def username_key(username): return ('u' if username else 'n') + username +def uniq(vals): + # Remove all duplicates from vals, while preserving order + ans = v'[]' + seen = {} + for x in vals: + if not seen[x]: + seen[x] = True + ans.push(x) + return ans + if __name__ is '__main__': print(fmt_sidx(10), fmt_sidx(1.2)) print(list(map(human_readable, [1, 1024.0, 1025, 1024*1024*2.3])))