From 2639df5a9795b1a4ae78ead61ea11398215e9ba4 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Sat, 28 Jul 2018 12:15:30 +0200 Subject: [PATCH] Search: loading indicator, no result screen, extended search on enter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - open extended search on enter, solves #560 - show searching indicator, requested in https://community.zammad.org/t/delay-after-querying-a-search/405 - show no result screen, solves #788 - fix bug: when on ‘#search’, ‘show search results’ in the search sidebar was highlighted as active too because it links to #search - clear search when entering a search result --- .../app/controllers/navigation.coffee | 69 +++++++------- .../javascripts/app/controllers/search.coffee | 3 +- .../app/lib/app_post/global_search.coffee | 6 +- .../app/views/navigation/no_result.jst.eco | 4 + .../navigation/search_placeholder.jst.eco | 24 +++++ app/assets/stylesheets/zammad.scss | 95 ++++++++++++++++--- 6 files changed, 152 insertions(+), 49 deletions(-) create mode 100644 app/assets/javascripts/app/views/navigation/no_result.jst.eco create mode 100644 app/assets/javascripts/app/views/navigation/search_placeholder.jst.eco diff --git a/app/assets/javascripts/app/controllers/navigation.coffee b/app/assets/javascripts/app/controllers/navigation.coffee index 55ad1234c..4464bf1e7 100644 --- a/app/assets/javascripts/app/controllers/navigation.coffee +++ b/app/assets/javascripts/app/controllers/navigation.coffee @@ -15,7 +15,7 @@ class App.Navigation extends App.ControllerWidgetPermanent 'focus #global-search': 'searchFocus' 'blur #global-search': 'searchBlur' 'keyup #global-search': 'listNavigate' - 'click .js-global-search-result': 'andClose' + 'click .js-global-search-result': 'emptyAndClose' 'click .js-details-link': 'openExtendedSearch' 'change .js-menu .js-switch input': 'switch' @@ -156,31 +156,31 @@ class App.Navigation extends App.ControllerWidgetPermanent type: 'personal' ) - renderResult: (result = []) => - - # remove result if not result exists - if _.isEmpty(result) - @searchContainer.removeClass('open') - @globalSearch.close() - @searchResult.html('') + renderResult: (result = [], noChange) => + if noChange return + # show no results placeholder if no result exists + if _.isEmpty(result) + @searchContainer.addClass('no-match') + @searchResult.html(App.view('navigation/no_result')()) + return + + @searchContainer.removeClass('no-match') + # build markup html = App.view('navigation/result')( result: result ) @searchResult.html(html) - # show result list - @searchContainer.addClass('open') - # start ticket popups @ticketPopups() # start user popups @userPopups() - # start oorganization popups + # start organization popups @organizationPopups() render: -> @@ -205,26 +205,22 @@ class App.Navigation extends App.ControllerWidgetPermanent searchFocus: (e) => @query = '' # reset query cache - @searchContainer.addClass('focused') @anyPopoversDestroy() - @search() + @searchContainer.addClass('focused') searchBlur: (e) => - # delay to be able to click x update = => query = @searchInput.val().trim() if !query @emptyAndClose() return - @searchContainer.removeClass('focused') @delay(update, 100, 'removeFocused') listNavigate: (e) => if e.keyCode is 27 # close on esc @emptyAndClose() - @searchInput.blur() return else if e.keyCode is 38 # up @nudge(e, -1) @@ -233,14 +229,13 @@ class App.Navigation extends App.ControllerWidgetPermanent @nudge(e, 1) return else if e.keyCode is 13 # enter - if @$('.global-search-menu .js-details-link.is-hover').get(0) - @openExtendedSearch() - return - href = @$('.global-search-result .nav-tab.is-hover').attr('href') - return if !href - @navigate(href) - @emptyAndClose() @searchInput.blur() + href = @$('.global-search-result .nav-tab.is-hover').attr('href') + if href + @navigate(href) + else + @openExtendedSearch() + @emptyAndClose() return # on other keys, show result @@ -284,25 +279,33 @@ class App.Navigation extends App.ControllerWidgetPermanent @scrollToIfNeeded(prev, false) emptyAndClose: => + @query = '' @searchInput.val('') - @searchContainer.removeClass('filled').removeClass('open').removeClass('focused') + @searchContainer.removeClass('focused filled open no-match') @globalSearch.close() - - # remove not needed popovers @delay(@anyPopoversDestroy, 100, 'removePopovers') andClose: => + @query = '' @searchInput.blur() - @searchContainer.removeClass('open') + @searchContainer.removeClass('open no-match') @globalSearch.close() @delay(@anyPopoversDestroy, 100, 'removePopovers') search: => query = @searchInput.val().trim() - return if !query - return if query is @query + @searchContainer.toggleClass('filled', !!query) + # if we started a new search and already typed something in + if @query == '' and query != '' + @searchContainer.addClass('open no-match') + @searchResult.html(App.view('navigation/search_placeholder')()) + @query = query - @searchContainer.toggleClass('filled', !!@query) + + if @query == '' + @searchContainer.removeClass('open') + return + @globalSearch.search(query: @query) filterNavbar: (values, user, parent = null) -> @@ -402,11 +405,11 @@ class App.Navigation extends App.ControllerWidgetPermanent url = params.url type = params.type if type is 'menu' - @$('.js-menu .is-active, .js-details-link.is-active').removeClass('is-active') + @$('.js-menu .is-active').removeClass('is-active') else @$('.is-active').removeClass('is-active') return if !url || url is '#' - @$("[href=\"#{url}\"]").addClass('is-active') + @$(".js-menu [href=\"#{url}\"], .tasks [href=\"#{url}\"]").addClass('is-active') recentViewNavbarItemsRebuild: => diff --git a/app/assets/javascripts/app/controllers/search.coffee b/app/assets/javascripts/app/controllers/search.coffee index 4c66dcf2f..1e252214c 100644 --- a/app/assets/javascripts/app/controllers/search.coffee +++ b/app/assets/javascripts/app/controllers/search.coffee @@ -125,7 +125,8 @@ class App.Search extends App.Controller @globalSearch.search(query: @query) - renderResult: (result = []) => + renderResult: (result = [], noChange) => + return if noChange @result = result for tab in @tabs count = 0 diff --git a/app/assets/javascripts/app/lib/app_post/global_search.coffee b/app/assets/javascripts/app/lib/app_post/global_search.coffee index 2724624fe..1e115fdf1 100644 --- a/app/assets/javascripts/app/lib/app_post/global_search.coffee +++ b/app/assets/javascripts/app/lib/app_post/global_search.coffee @@ -45,10 +45,12 @@ class App.GlobalSearch extends App.Controller renderTry: (result, query) => # if result hasn't changed, do not rerender - diff = false if @lastQuery is query && @searchResultCache[query] diff = difference(@searchResultCache[query].result, result) - return if diff isnt false && _.isEmpty(diff) + if _.isEmpty(diff) + @render(result, true) + return + @lastQuery = query # cache search result diff --git a/app/assets/javascripts/app/views/navigation/no_result.jst.eco b/app/assets/javascripts/app/views/navigation/no_result.jst.eco new file mode 100644 index 000000000..b335db81a --- /dev/null +++ b/app/assets/javascripts/app/views/navigation/no_result.jst.eco @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/app/views/navigation/search_placeholder.jst.eco b/app/assets/javascripts/app/views/navigation/search_placeholder.jst.eco new file mode 100644 index 000000000..b86b855d1 --- /dev/null +++ b/app/assets/javascripts/app/views/navigation/search_placeholder.jst.eco @@ -0,0 +1,24 @@ +
  • + + + + +
  • +
  • + + + + +
  • +
  • + + + + +
  • \ No newline at end of file diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss index 557224449..0fe6ea671 100644 --- a/app/assets/stylesheets/zammad.scss +++ b/app/assets/stylesheets/zammad.scss @@ -1962,7 +1962,6 @@ input.has-error { border-radius: 19px; padding: 0 17px 0 42px; @include rtl(padding, 0 42px 0 17px); - will-change: transform; &.is-empty + .empty-search { visibility: hidden; @@ -3279,10 +3278,6 @@ footer { .nav-tab.nav-tab--search.is-hover { background: #389ed9; color: white; - - .nav-tab-icon .icon { - fill: white; - } } .nav-tab.ui-sortable-helper { @@ -3306,7 +3301,7 @@ footer { .nav-tab-icon .icon { max-width: 18px; max-height: 18px; - fill: #808080; + fill: currentColor; } .nav-tab-icon .icon-diagonal-cross { @@ -3432,7 +3427,8 @@ footer { flex: 1; border-radius: 15px; position: relative; - transition: 240ms; + transition: margin-right 120ms; + will-change: margin-right; } .empty-search { @@ -3442,16 +3438,23 @@ footer { height: 30px; width: 40px; z-index: 1; - visibility: hidden; display: flex; align-items: center; justify-content: center; @extend %clickable; - } + visibility: hidden; + cursor: pointer; + + &:hover { + .icon { + opacity: 1; + } + } - .search .empty-search .icon-diagonal-cross { - fill: white; - opacity: 0.5; + .icon { + fill: white; + opacity: 0.5; + } } .filled.search .empty-search { @@ -3489,7 +3492,8 @@ footer { } .search.focused .search-holder { - @include bidi-style(margin-right, -46px, margin-left, 0); + transition: margin-right 240ms; + @include bidi-style(margin-right, -59px, margin-left, 0); } .search.focused .logo { @@ -3555,6 +3559,10 @@ footer { padding: 9px 15px 8px 0; margin-bottom: 7px; height: auto !important; + + .no-match & { + display: none; + } .nav-tab-icon { width: 18px; @@ -3582,6 +3590,62 @@ footer { list-style: none; } + .global-search-detail-no-result { + margin: 0 10px; + + .icon { + width: 30px; + height: 29px; + } + } + + .global-search-detail-placeholder { + pointer-events: none; + color: inherit; + + .nav-tab { + animation: fade-in 4s forwards; + } + + .nav-tab-name { + background: linear-gradient(to right, hsl(0,0%,50%) 50%, hsl(0,0%,70%)); + background-size: 200% 100%; + height: 10px; + width: 77%; + animation: placeholder-background 1.4s infinite; + } + + &:nth-child(2) { + .nav-tab-name { + width: 54%; + } + } + + &:nth-child(3) { + .nav-tab-name { + width: 68%; + } + } + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @keyframes placeholder-background { + from { + background-position: 0% 0%; + } + to { + background-position: -200% 0%; + } + } + .user-menu { padding: 0; margin: 0; @@ -5843,6 +5907,11 @@ footer { border-radius: 3px; color: white; border: none; + + .icon { + margin-right: 10px; + fill: currentColor; + } &.alert--info { background: hsl(203,65%,55%);