summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee7
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee82
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee207
-rw-r--r--app/assets/stylesheets/framework/forms.scss34
-rw-r--r--app/assets/stylesheets/framework/header.scss20
-rw-r--r--app/assets/stylesheets/framework/jquery.scss30
-rw-r--r--app/assets/stylesheets/pages/search.scss98
-rw-r--r--app/helpers/search_helper.rb58
-rw-r--r--app/views/layouts/_search.html.haml27
-rw-r--r--app/views/shared/_location_badge.html.haml12
10 files changed, 449 insertions, 126 deletions
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index f5e1ca9860d..a022c207d08 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -152,9 +152,4 @@ class Dispatcher
new Shortcuts()
initSearch: ->
- opts = $('.search-autocomplete-opts')
- path = opts.data('autocomplete-path')
- project_id = opts.data('autocomplete-project-id')
- project_ref = opts.data('autocomplete-project-ref')
-
- new SearchAutocomplete(path, project_id, project_ref)
+ new SearchAutocomplete()
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index c81e8bf760a..d9a4cb1771b 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -2,7 +2,10 @@ class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
constructor: (@dropdown, @options) ->
- @input = @dropdown.find(".dropdown-input .dropdown-input-field")
+ {
+ @input
+ @filterInputBlur = true
+ } = @options
# Key events
timeout = ""
@@ -17,7 +20,7 @@ class GitLabDropdownFilter
blur_field = @shouldBlur e.keyCode
search_text = @input.val()
- if blur_field
+ if blur_field and @filterInputBlur
@input.blur()
if @options.remote
@@ -77,25 +80,48 @@ class GitLabDropdown
PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active"
+ FILTER_INPUT = '.dropdown-input .dropdown-input-field'
+
constructor: (@el, @options) ->
- self = @
@dropdown = $(@el).parent()
+
+ # Set Defaults
+ {
+ # If no input is passed create a default one
+ @filterInput = @getElement(FILTER_INPUT)
+ @highlight = false
+ @filterInputBlur = true
+ } = @options
+
+ self = @
+
+ # If selector was passed
+ if _.isString(@filterInput)
+ @filterInput = @getElement(@filterInput)
+
search_fields = if @options.search then @options.search.fields else [];
if @options.data
- # Remote data
- @remote = new GitLabDropdownRemote @options.data, {
- dataType: @options.dataType,
- beforeSend: @toggleLoading.bind(@)
- success: (data) =>
- @fullData = data
+ # If data is an array
+ if _.isArray @options.data
+ @fullData = @options.data
+ @parseData @options.data
+ else
+ # Remote data
+ @remote = new GitLabDropdownRemote @options.data, {
+ dataType: @options.dataType,
+ beforeSend: @toggleLoading.bind(@)
+ success: (data) =>
+ @fullData = data
- @parseData @fullData
- }
+ @parseData @fullData
+ }
# Init filiterable
if @options.filterable
@filter = new GitLabDropdownFilter @dropdown,
+ filterInputBlur: @filterInputBlur
+ input: @filterInput
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
@@ -129,6 +155,10 @@ class GitLabDropdown
if self.options.clicked
self.options.clicked()
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @dropdown.find selector
+
toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
@@ -163,7 +193,7 @@ class GitLabDropdown
@remote.execute()
if @options.filterable
- @dropdown.find(".dropdown-input-field").focus()
+ @filterInput.focus()
hidden: =>
if @options.filterable
@@ -196,20 +226,38 @@ class GitLabDropdown
renderItem: (data) ->
html = ""
+ # Separator
return "<li class='divider'></li>" if data is "divider"
+ # Header
+ return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
+
if @options.renderRow
# Call the render function
html = @options.renderRow(data)
else
selected = if @options.isSelected then @options.isSelected(data) else false
- url = if @options.url then @options.url(data) else "#"
- text = if @options.text then @options.text(data) else ""
+
+ # Set URL
+ if @options.url?
+ url = @options.url(data)
+ else
+ url = if data.url? then data.url else '#'
+
+ # Set Text
+ if @options.text?
+ text = @options.text(data)
+ else
+ text = if data.text? then data.text else ''
+
cssClass = "";
if selected
cssClass = "is-active"
+ if @highlight
+ text = @highlightTextMatches(text, @filterInput.val())
+
html = "<li>"
html += "<a href='#{url}' class='#{cssClass}'>"
html += text
@@ -218,6 +266,12 @@ class GitLabDropdown
return html
+ highlightTextMatches: (text, term) ->
+ occurrences = fuzzaldrinPlus.match(text, term)
+ text.split('').map((character, i) ->
+ if i in occurrences then "<b>#{character}</b>" else character
+ ).join('')
+
noResults: ->
html = "<li>"
html += "<a href='#' class='is-focused'>"
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index c1801365266..18fa3b86d16 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,11 +1,198 @@
class @SearchAutocomplete
- constructor: (search_autocomplete_path, project_id, project_ref) ->
- project_id = '' unless project_id
- project_ref = '' unless project_ref
- query = "?project_id=" + project_id + "&project_ref=" + project_ref
-
- $("#search").autocomplete
- source: search_autocomplete_path + query
- minLength: 1
- select: (event, ui) ->
- location.href = ui.item.url
+
+ KEYCODE =
+ ESCAPE: 27
+ BACKSPACE: 8
+ TAB: 9
+ ENTER: 13
+
+ constructor: (opts = {}) ->
+ {
+ @wrap = $('.search')
+
+ @optsEl = @wrap.find('.search-autocomplete-opts')
+ @autocompletePath = @optsEl.data('autocomplete-path')
+ @projectId = @optsEl.data('autocomplete-project-id') || ''
+ @projectRef = @optsEl.data('autocomplete-project-ref') || ''
+
+ } = opts
+
+ # Dropdown Element
+ @dropdown = @wrap.find('.dropdown')
+
+ @locationBadgeEl = @getElement('.search-location-badge')
+ @locationText = @getElement('.location-text')
+ @scopeInputEl = @getElement('#scope')
+ @searchInput = @getElement('.search-input')
+ @projectInputEl = @getElement('#search_project_id')
+ @groupInputEl = @getElement('#group_id')
+ @searchCodeInputEl = @getElement('#search_code')
+ @repositoryInputEl = @getElement('#repository_ref')
+ @scopeInputEl = @getElement('#scope')
+
+ @saveOriginalState()
+
+ @searchInput.addClass('disabled')
+
+ @bindEvents()
+
+ # Finds an element inside wrapper element
+ getElement: (selector) ->
+ @wrap.find(selector)
+
+ saveOriginalState: ->
+ @originalState = @serializeState()
+
+ serializeState: ->
+ {
+ # Search Criteria
+ project_id: @projectInputEl.val()
+ group_id: @groupInputEl.val()
+ search_code: @searchCodeInputEl.val()
+ repository_ref: @repositoryInputEl.val()
+
+ # Location badge
+ _location: $.trim(@locationText.text())
+ }
+
+ bindEvents: ->
+ @searchInput.on 'keydown', @onSearchInputKeyDown
+ @searchInput.on 'focus', @onSearchInputFocus
+ @searchInput.on 'blur', @onSearchInputBlur
+
+ enableAutocomplete: ->
+ dropdownMenu = @dropdown.find('.dropdown-menu')
+ _this = @
+ @searchInput.glDropdown
+ filterInputBlur: false
+ filterable: true
+ filterRemote: true
+ highlight: true
+ filterInput: 'input#search'
+ search:
+ fields: ['text']
+ data: (term, callback) ->
+ $.get(_this.autocompletePath, {
+ project_id: _this.projectId
+ project_ref: _this.projectRef
+ term: term
+ }, (response) ->
+ data = []
+
+ # Save groups ordering according to server response
+ groupNames = _.unique(_.pluck(response, 'category'))
+
+ # Group results by category name
+ groups = _.groupBy response, (item) ->
+ item.category
+
+ # List results
+ for groupName in groupNames
+
+ # Add group header before list each group
+ data.push
+ header: groupName
+
+ # List group
+ for item in groups[groupName]
+ data.push
+ text: item.label
+ url: item.url
+ callback(data)
+ )
+
+ @dropdown.addClass('open')
+ @searchInput.removeClass('disabled')
+ @autocomplete = true
+
+ onDropdownOpen: (e) =>
+ @dropdown.dropdown('toggle')
+
+ onSearchInputKeyDown: (e) =>
+ # Remove tag when pressing backspace and input search is empty
+ if e.keyCode is KEYCODE.BACKSPACE and e.currentTarget.value is ''
+ @removeLocationBadge()
+ @searchInput.focus()
+
+ else if e.keyCode is KEYCODE.ESCAPE
+ @searchInput.val('')
+ @restoreOriginalState()
+ else
+ # Create new autocomplete if it hasn't been created yet and there's no badge
+ if @autocomplete is undefined
+ if !@badgePresent()
+ @enableAutocomplete()
+ else
+ # There's a badge
+ if @badgePresent()
+ @disableAutocomplete()
+
+ onSearchInputFocus: =>
+ @wrap.addClass('search-active')
+
+ onSearchInputBlur: =>
+ @wrap.removeClass('search-active')
+
+ # If input is blank then restore state
+ if @searchInput.val() is ''
+ @restoreOriginalState()
+
+ addLocationBadge: (item) ->
+ category = if item.category? then "#{item.category}: " else ''
+ value = if item.value? then item.value else ''
+
+ html = "<span class='location-badge'>
+ <i class='location-text'>#{category}#{value}</i>
+ <a class='remove-badge' href='#'>x</a>
+ </span>"
+ @locationBadgeEl.html(html)
+
+ restoreOriginalState: ->
+ inputs = Object.keys @originalState
+
+ for input in inputs
+ @getElement("##{input}").val(@originalState[input])
+
+
+ if @originalState._location is ''
+ @locationBadgeEl.html('')
+ else
+ @addLocationBadge(
+ value: @originalState._location
+ )
+
+ @dropdown.removeClass 'open'
+
+ # Only add class if there's a badge
+ if @badgePresent()
+ @searchInput.addClass 'disabled'
+
+ badgePresent: ->
+ @locationBadgeEl.children().length
+
+ resetSearchState: ->
+ # Remove scope
+ @scopeInputEl.val('')
+
+ # Remove group
+ @groupInputEl.val('')
+
+ # Remove project id
+ @projectInputEl.val('')
+
+ # Remove code search
+ @searchCodeInputEl.val('')
+
+ # Remove repository ref
+ @repositoryInputEl.val('')
+
+ removeLocationBadge: ->
+ @locationBadgeEl.empty()
+
+ # Reset state
+ @resetSearchState()
+
+ disableAutocomplete: ->
+ if @autocomplete?
+ @searchInput.addClass('disabled')
+ @autocomplete = 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..eb3fbd9155b 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -19,13 +19,35 @@
}
&.ui-autocomplete {
- border-color: #ddd;
- 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;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index b6e45024644..4a02f75719b 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -21,3 +21,101 @@
}
}
+.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;
+ }
+
+ .search-input-wrap {
+ i {
+ color: $input-border-focus;
+ }
+ }
+ }
+
+ form {
+ @extend .form-control;
+ margin: 0;
+ padding: 4px;
+ width: 350px;
+ line-height: 24px;
+ }
+
+ .location-text {
+ font-style: normal;
+ }
+
+ .remove-badge {
+ display: none;
+ }
+
+ .search-input {
+ border: none;
+ font-size: 14px;
+ outline: none;
+ padding: 0;
+ margin-left: 5px;
+ line-height: 25px;
+ width: 98%;
+ }
+
+ .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 flexbox is not supported
+ display: inline-block;
+ }
+
+ .search-input-wrap {
+ width: 100%;
+ position: relative;
+
+ .search-icon {
+ @extend .fa-search;
+ @include transition(color .15s);
+ position: absolute;
+ right: 5px;
+ color: #E7E9ED;
+ top: 0;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+
+ &:before {
+ font-family: FontAwesome;
+ font-weight: normal;
+ font-style: normal;
+ }
+ }
+
+ .dropdown-header {
+ text-transform: uppercase;
+ font-size: 11px;
+ }
+ }
+}
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 494dad0b41e..de164547396 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -23,45 +23,44 @@ module SearchHelper
# Autocomplete results for various settings pages
def default_autocomplete
[
- { label: "Profile settings", url: profile_path },
- { label: "SSH Keys", url: profile_keys_path },
- { label: "Dashboard", url: root_path },
- { label: "Admin Section", url: admin_root_path },
+ { category: "Settings", label: "Profile settings", url: profile_path },
+ { category: "Settings", label: "SSH Keys", url: profile_keys_path },
+ { category: "Settings", label: "Dashboard", url: root_path },
+ { category: "Settings", label: "Admin Section", url: admin_root_path },
]
end
# Autocomplete results for internal help pages
def help_autocomplete
[
- { label: "help: API Help", url: help_page_path("api", "README") },
- { label: "help: Markdown Help", url: help_page_path("markdown", "markdown") },
- { label: "help: Permissions Help", url: help_page_path("permissions", "permissions") },
- { label: "help: Public Access Help", url: help_page_path("public_access", "public_access") },
- { label: "help: Rake Tasks Help", url: help_page_path("raketasks", "README") },
- { label: "help: SSH Keys Help", url: help_page_path("ssh", "README") },
- { label: "help: System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
- { label: "help: Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
- { label: "help: Workflow Help", url: help_page_path("workflow", "README") },
+ { category: "Help", label: "API Help", url: help_page_path("api", "README") },
+ { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") },
+ { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") },
+ { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") },
+ { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") },
+ { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") },
+ { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
+ { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
+ { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") },
]
end
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
- { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
- { label: "#{prefix} - 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
[]
@@ -72,7 +71,10 @@ module SearchHelper
def groups_autocomplete(term, limit = 5)
current_user.authorized_groups.search(term).limit(limit).map do |group|
{
- label: "group: #{search_result_sanitize(group.name)}",
+ category: "Groups",
+ location: "groups",
+ id: group.id,
+ label: "#{search_result_sanitize(group.name)}",
url: group_path(group)
}
end
@@ -83,7 +85,11 @@ module SearchHelper
current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{
- label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ category: "Projects",
+ location: "projects",
+ id: p.id,
+ value: "#{search_result_sanitize(p.name)}",
+ label: "#{search_result_sanitize(p.name_with_namespace)}",
url: namespace_project_path(p.namespace, p)
}
end
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 54af2c3063c..f051e7a1867 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,10 +1,20 @@
-.search
- = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
+.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
+ .dropdown{ data: {url: search_autocomplete_path } }
+ = search_field_tag "search", nil, placeholder: 'Search', class: "search-input dropdown-menu-toggle", spellcheck: false, tabindex: "1", autocomplete: 'off', data: { toggle: 'dropdown' }
+ .dropdown-menu.dropdown-select
+ = dropdown_content
+ = dropdown_loading
+ %i.search-icon
+
= hidden_field_tag :group_id, @group.try(:id)
- - if @project && @project.persisted?
- = hidden_field_tag :project_id, @project.id
+ = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
+ - if @project && @project.persisted?
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
@@ -21,10 +31,3 @@
= hidden_field_tag :repository_ref, @ref
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
-
-:javascript
- $('.search-input').on('keyup', function(e) {
- if (e.keyCode == 27) {
- $('.search-input').blur();
- }
- });
diff --git a/app/views/shared/_location_badge.html.haml b/app/views/shared/_location_badge.html.haml
new file mode 100644
index 00000000000..f1ecc060cf1
--- /dev/null
+++ b/app/views/shared/_location_badge.html.haml
@@ -0,0 +1,12 @@
+- if controller.controller_path =~ /^groups/
+ - label = 'This group'
+- if controller.controller_path =~ /^projects/
+ - label = 'This project'
+
+- if label.present?
+ %span.location-badge
+ %i.location-text
+ = label
+
+ %a.remove-badge{href: '#'}
+ x