Working version of autocomplete with categorized results
This commit is contained in:
parent
eba0032552
commit
c9e202c1d7
6 changed files with 229 additions and 49 deletions
|
@ -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()
|
||||
|
|
17
app/assets/javascripts/lib/category_autocomplete.js.coffee
Normal file
17
app/assets/javascripts/lib/category_autocomplete.js.coffee
Normal file
|
@ -0,0 +1,17 @@
|
|||
$.widget( "custom.catcomplete", $.ui.autocomplete,
|
||||
_create: ->
|
||||
@_super();
|
||||
@widget().menu("option", "items", "> :not(.ui-autocomplete-category)")
|
||||
|
||||
_renderMenu: (ul, items) ->
|
||||
currentCategory = ''
|
||||
$.each items, (index, item) =>
|
||||
if item.category isnt currentCategory
|
||||
ul.append("<li class='ui-autocomplete-category'>#{item.category}</li>")
|
||||
currentCategory = item.category
|
||||
|
||||
li = @_renderItemData(ul, item)
|
||||
|
||||
if item.category?
|
||||
li.attr('aria-label', item.category + " : " + item.label)
|
||||
)
|
|
@ -1,11 +1,164 @@
|
|||
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
|
||||
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
|
||||
|
||||
$("#search").autocomplete
|
||||
source: search_autocomplete_path + query
|
||||
@keyCode =
|
||||
ESCAPE: 27
|
||||
BACKSPACE: 8
|
||||
TAB: 9
|
||||
ENTER: 13
|
||||
|
||||
@locationBadgeEl = @$('.search-location-badge')
|
||||
@locationText = @$('.location-text')
|
||||
@searchInput = @$('.search-input')
|
||||
@projectInputEl = @$('#project_id')
|
||||
@groupInputEl = @$('#group_id')
|
||||
@searchCodeInputEl = @$('#search_code')
|
||||
@repositoryInputEl = @$('#repository_ref')
|
||||
@scopeInputEl = @$('#scope')
|
||||
|
||||
@saveOriginalState()
|
||||
@createAutocomplete()
|
||||
@bindEvents()
|
||||
|
||||
$: (selector) ->
|
||||
@wrap.find(selector)
|
||||
|
||||
saveOriginalState: ->
|
||||
@originalState = @serializeState()
|
||||
|
||||
restoreOriginalState: ->
|
||||
inputs = Object.keys @originalState
|
||||
|
||||
for input in inputs
|
||||
@$("##{input}").val(@originalState[input])
|
||||
|
||||
|
||||
if @originalState._location is ''
|
||||
@locationBadgeEl.html('')
|
||||
else
|
||||
@addLocationBadge(
|
||||
value: @originalState._location
|
||||
)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
createAutocomplete: ->
|
||||
@query = "?project_id=" + @projectId + "&project_ref=" + @projectRef
|
||||
|
||||
@catComplete = @searchInput.catcomplete
|
||||
appendTo: 'form.navbar-form'
|
||||
source: @autocompletePath + @query
|
||||
minLength: 1
|
||||
select: (event, ui) ->
|
||||
location.href = ui.item.url
|
||||
close: (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
select: (event, ui) =>
|
||||
# Pressing enter choses an alternative
|
||||
if event.keyCode is @keyCode.ENTER
|
||||
@goToResult(ui.item)
|
||||
else
|
||||
# Pressing tab sets the scope
|
||||
if event.keyCode is @keyCode.TAB and ui.item.scope?
|
||||
@setLocationBadge(ui.item)
|
||||
@searchInput
|
||||
.val('') # remove selected value from input
|
||||
.focus()
|
||||
else
|
||||
# If option is not a scope go to page
|
||||
@goToResult(ui.item)
|
||||
|
||||
# Return false to avoid focus on the next element
|
||||
return false
|
||||
|
||||
|
||||
bindEvents: ->
|
||||
@searchInput.on 'keydown', @onSearchKeyDown
|
||||
@wrap.on 'click', '.remove-badge', @onRemoveLocationBadgeClick
|
||||
|
||||
onRemoveLocationBadgeClick: (e) =>
|
||||
e.preventDefault()
|
||||
@removeLocationBadge()
|
||||
@searchInput.focus()
|
||||
|
||||
onSearchKeyDown: (e) =>
|
||||
# Remove tag when pressing backspace and input search is empty
|
||||
if e.keyCode is @keyCode.BACKSPACE and e.currentTarget.value is ''
|
||||
@removeLocationBadge()
|
||||
@destroyAutocomplete()
|
||||
@searchInput.focus()
|
||||
else if e.keyCode is @keyCode.ESCAPE
|
||||
@restoreOriginalState()
|
||||
else
|
||||
# Create new autocomplete instance if it's not created
|
||||
@createAutocomplete() unless @catcomplete?
|
||||
|
||||
addLocationBadge: (item) ->
|
||||
category = if item.category? then "#{item.category}: " else ''
|
||||
value = if item.value? then item.value else ''
|
||||
|
||||
html = "<span class='label label-primary'>
|
||||
<i class='location-text'>#{category}#{value}</i>
|
||||
<a class='remove-badge' href='#'>x</a>
|
||||
</span>"
|
||||
@locationBadgeEl.html(html)
|
||||
|
||||
setLocationBadge: (item) ->
|
||||
@addLocationBadge(item)
|
||||
|
||||
# Reset input states
|
||||
@resetSearchState()
|
||||
|
||||
switch item.scope
|
||||
when 'projects'
|
||||
@projectInputEl.val(item.id)
|
||||
# @searchCodeInputEl.val('true') # TODO: always true for projects?
|
||||
# @repositoryInputEl.val('master') # TODO: always master?
|
||||
|
||||
when 'groups'
|
||||
@groupInputEl.val(item.id)
|
||||
|
||||
removeLocationBadge: ->
|
||||
@locationBadgeEl.empty()
|
||||
|
||||
# Reset state
|
||||
@resetSearchState()
|
||||
|
||||
resetSearchState: ->
|
||||
# Remove scope
|
||||
@scopeInputEl.val('')
|
||||
|
||||
# Remove group
|
||||
@groupInputEl.val('')
|
||||
|
||||
# Remove project id
|
||||
@projectInputEl.val('')
|
||||
|
||||
# Remove code search
|
||||
@searchCodeInputEl.val('')
|
||||
|
||||
# Remove repository ref
|
||||
@repositoryInputEl.val('')
|
||||
|
||||
goToResult: (result) ->
|
||||
location.href = result.url
|
||||
|
||||
destroyAutocomplete: ->
|
||||
@catComplete.destroy() if @catcomplete?
|
||||
@catComplete = null
|
||||
|
|
|
@ -23,45 +23,45 @@ 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)
|
||||
prefix = "Project - " + 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: 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) },
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
@ -72,7 +72,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",
|
||||
scope: "groups",
|
||||
id: group.id,
|
||||
label: "#{search_result_sanitize(group.name)}",
|
||||
url: group_path(group)
|
||||
}
|
||||
end
|
||||
|
@ -83,7 +86,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",
|
||||
scope: "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
|
||||
|
|
|
@ -1,10 +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)
|
||||
- if @project && @project.persisted?
|
||||
= hidden_field_tag :project_id, @project.id
|
||||
|
||||
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : ''
|
||||
|
||||
- if @project && @project.persisted?
|
||||
- if current_controller?(:issues)
|
||||
= hidden_field_tag :scope, 'issues'
|
||||
- elsif current_controller?(:merge_requests)
|
||||
|
@ -21,10 +23,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();
|
||||
}
|
||||
});
|
||||
|
|
13
app/views/shared/_location_badge.html.haml
Normal file
13
app/views/shared/_location_badge.html.haml
Normal file
|
@ -0,0 +1,13 @@
|
|||
- if controller.controller_path =~ /^groups/
|
||||
- label = 'This group'
|
||||
- if controller.controller_path =~ /^projects/
|
||||
- label = 'This project'
|
||||
|
||||
.search-location-badge
|
||||
- if label.present?
|
||||
%span.label.label-primary
|
||||
%i.location-text
|
||||
= label
|
||||
|
||||
%a.remove-badge{href: '#'}
|
||||
x
|
Loading…
Reference in a new issue