From 6a7f4a0767f059a2a3e4e4a4999046cffa860561 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 8 Mar 2016 19:39:14 -0500 Subject: [PATCH] Apply styling and tweaks to autocomplete dropdown --- .../lib/category_autocomplete.js.coffee | 32 +++++++++ .../javascripts/search_autocomplete.js.coffee | 35 ++++++++-- app/assets/stylesheets/framework/forms.scss | 34 --------- app/assets/stylesheets/framework/header.scss | 20 ------ app/assets/stylesheets/framework/jquery.scss | 34 ++++++++- app/assets/stylesheets/pages/search.scss | 70 +++++++++++++++++++ app/helpers/search_helper.rb | 21 +++--- app/views/layouts/_search.html.haml | 13 ++-- app/views/shared/_location_badge.html.haml | 13 ++-- 9 files changed, 186 insertions(+), 86 deletions(-) diff --git a/app/assets/javascripts/lib/category_autocomplete.js.coffee b/app/assets/javascripts/lib/category_autocomplete.js.coffee index 490032dc782..c85fabbcd5b 100644 --- a/app/assets/javascripts/lib/category_autocomplete.js.coffee +++ b/app/assets/javascripts/lib/category_autocomplete.js.coffee @@ -14,4 +14,36 @@ $.widget( "custom.catcomplete", $.ui.autocomplete, if item.category? li.attr('aria-label', item.category + " : " + item.label) + + _renderItem: (ul, item) -> + # Highlight occurrences + item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + + return $( "
  • " ) + .data( "item.autocomplete", item ) + .append( "#{item.label}" ) + .appendTo( ul ); + + _resizeMenu: -> + if (isNaN(this.options.maxShowItems)) + return + + ul = this.menu.element.css(overflowX: '', overflowY: '', width: '', maxHeight: '') + + lis = ul.children('li').css('whiteSpace', 'nowrap'); + + if (lis.length > this.options.maxShowItems) + ulW = ul.prop('clientWidth') + + ul.css( + overflowX: 'hidden' + overflowY: 'auto' + maxHeight: lis.eq(0).outerHeight() * this.options.maxShowItems + 1 + ) + + barW = ulW - ul.prop('clientWidth'); + ul.width('+=' + barW); + + # Original code from jquery.ui.autocomplete.js _resizeMenu() + ul.outerWidth(Math.max(ul.outerWidth() + 1, this.element.outerWidth())); ) diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index df31b07910c..a6d5ab65239 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -24,7 +24,10 @@ class @SearchAutocomplete @scopeInputEl = @$('#scope') @saveOriginalState() - @createAutocomplete() + + if @locationBadgeEl.is(':empty') + @createAutocomplete() + @bindEvents() $: (selector) -> @@ -66,6 +69,12 @@ class @SearchAutocomplete appendTo: 'form.navbar-form' source: @autocompletePath + @query minLength: 1 + maxShowItems: 15 + position: + # { my: "left top", at: "left bottom", collision: "none" } + my: "left-10 top+9" + at: "left bottom" + collision: "none" close: (e) -> e.preventDefault() @@ -89,7 +98,9 @@ class @SearchAutocomplete bindEvents: -> - @searchInput.on 'keydown', @onSearchKeyDown + @searchInput.on 'keydown', @onSearchInputKeyDown + @searchInput.on 'focus', @onSearchInputFocus + @searchInput.on 'blur', @onSearchInputBlur @wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick onRemoveLocationBadgeClick: (e) => @@ -97,7 +108,7 @@ class @SearchAutocomplete @removeLocationBadge() @searchInput.focus() - onSearchKeyDown: (e) => + onSearchInputKeyDown: (e) => # Remove tag when pressing backspace and input search is empty if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is '' @removeLocationBadge() @@ -106,14 +117,24 @@ class @SearchAutocomplete else if e.keyCode is @keyCode.ESCAPE @restoreOriginalState() else - # Create new autocomplete instance if it's not created - @createAutocomplete() unless @catcomplete? + # Create new autocomplete if hasn't been created yet and there's no badge + if !@catComplete? and @locationBadgeEl.is(':empty') + @createAutocomplete() + + onSearchInputFocus: => + @wrap.addClass('search-active') + + onSearchInputBlur: => + @wrap.removeClass('search-active') + + # If input is blank then restore state + @restoreOriginalState() if @searchInput.val() is '' addLocationBadge: (item) -> category = if item.category? then "#{item.category}: " else '' value = if item.value? then item.value else '' - html = " + html = " #{category}#{value} x " @@ -160,5 +181,5 @@ class @SearchAutocomplete location.href = result.url destroyAutocomplete: -> - @catComplete.destroy() if @catcomplete? + @catComplete.destroy() if @catComplete? @catComplete = null diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 4cb4129b71b..91b6451e68a 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -6,40 +6,6 @@ input { border-radius: $border-radius-base; } -input[type='search'] { - background-color: white; - padding-left: 10px; -} - -input[type='search'].search-input { - background-repeat: no-repeat; - background-position: 10px; - background-size: 16px; - background-position-x: 30%; - padding-left: 10px; - background-color: $gray-light; - - &.search-input[value=""] { - background-image: url('data:image/png;base64,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'); - } - - &.search-input::-webkit-input-placeholder { - text-align: center; - } - - &.search-input:-moz-placeholder { /* Firefox 18- */ - text-align: center; - } - - &.search-input::-moz-placeholder { /* Firefox 19+ */ - text-align: center; - } - - &.search-input:-ms-input-placeholder { - text-align: center; - } -} - input[type='text'].danger { background: #f2dede!important; border-color: #d66; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 71a7ecab8ef..a6c9fce5b89 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -112,26 +112,6 @@ header { } } - .search { - margin-right: 10px; - margin-left: 10px; - margin-top: ($header-height - 36) / 2; - - form { - margin: 0; - padding: 0; - } - - .search-input { - width: 220px; - - &:focus { - @include box-shadow(none); - outline: none; - } - } - } - .impersonation i { color: $red-normal; } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 525ed81b059..e0d655d3054 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -23,9 +23,39 @@ padding: 0; margin-top: 2px; z-index: 1001; + width: 240px; + margin-bottom: 0; + padding: 10px 10px; + font-size: 14px; + font-weight: normal; + background-color: $dropdown-bg; + border: 1px solid $dropdown-border-color; + border-radius: $border-radius-base; + box-shadow: 0 2px 4px $dropdown-shadow-color; - .ui-menu-item a { - padding: 4px 10px; + .ui-menu-item { + display: block; + position: relative; + padding: 0 10px; + color: $dropdown-link-color; + line-height: 34px; + text-overflow: ellipsis; + border-radius: 2px; + white-space: nowrap; + overflow: hidden; + border: none; + + &.ui-state-focus { + background-color: $dropdown-link-hover-bg; + text-decoration: none; + margin: 0; + } + } + + .ui-autocomplete-category { + text-transform: uppercase; + font-size: 11px; + color: #7f8fa4; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index b6e45024644..57b88268c03 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -21,3 +21,73 @@ } } + +.search { + margin-right: 10px; + margin-left: 10px; + margin-top: ($header-height - 35) / 2; + + &.search-active { + form { + @extend .form-control:focus; + } + + .location-badge { + @include transition(all .15s); + background-color: $input-border-focus; + color: $white-light; + } + } + + form { + @extend .form-control; + margin: 0; + padding: 4px; + width: 350px; + line-height: 24px; + overflow: hidden; + } + + .location-text { + font-style: normal; + } + + .remove-badge { + display: none; + } + + .search-input { + border: none; + font-size: 14px; + outline: none; + padding: 0; + margin-left: 2px; + line-height: 25px; + width: 100%; + } + + .location-badge { + line-height: 25px; + padding: 0 5px; + border-radius: 2px; + font-size: 14px; + font-style: normal; + color: #AAAAAA; + display: inline-block; + background-color: #F5F5F5; + vertical-align: top; + } + + .search-input-container { + display: flex; + } + + .search-location-badge, .search-input-wrap { + // Fallback if flex is not supported + display: inline-block; + } + + .search-input-wrap { + width: 100%; + } +} diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 9102fd6d501..cbead1b8b74 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -48,20 +48,19 @@ module SearchHelper # Autocomplete results for the current project, if it's defined def project_autocomplete if @project && @project.repository.exists? && @project.repository.root_ref - prefix = "Project - " + search_result_sanitize(@project.name_with_namespace) ref = @ref || @project.repository.root_ref [ - { category: prefix, label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, - { category: prefix, label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, - { category: prefix, label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, - { category: prefix, label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, - { category: prefix, label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, - { category: prefix, label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) }, - { category: prefix, label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, + { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, + { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) }, + { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, + { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, + { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, + { category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) }, + { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, ] else [] diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index c5002893831..843c833b4fe 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,9 +1,12 @@ -.search - = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - = render 'shared/location_badge' - = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1" - = hidden_field_tag :group_id, @group.try(:id) +.search.search-form + = form_tag search_path, method: :get, class: 'navbar-form' do |f| + .search-input-container + .search-location-badge + = render 'shared/location_badge' + .search-input-wrap + = search_field_tag "search", nil, placeholder: 'Search', class: "search-input", spellcheck: false, tabindex: "1", autocomplete: 'off' + = hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '' - if @project && @project.persisted? diff --git a/app/views/shared/_location_badge.html.haml b/app/views/shared/_location_badge.html.haml index dfe8bc010d6..f1ecc060cf1 100644 --- a/app/views/shared/_location_badge.html.haml +++ b/app/views/shared/_location_badge.html.haml @@ -3,11 +3,10 @@ - if controller.controller_path =~ /^projects/ - label = 'This project' -.search-location-badge - - if label.present? - %span.label.label-primary - %i.location-text - = label +- if label.present? + %span.location-badge + %i.location-text + = label - %a.remove-badge{href: '#'} - x + %a.remove-badge{href: '#'} + x