From 71dc5af9ce5e25d8d3219b296e23c3ca6340451b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 4 Nov 2016 16:27:11 -0500 Subject: Add basic search --- app/assets/javascripts/dispatcher.js.es6 | 3 + .../filtered_search/filtered_search_bundle.js | 13 +++ .../filtered_search/filtered_search_manager.js.es6 | 104 +++++++++++++++++++++ app/assets/stylesheets/framework/filters.scss | 24 +++++ app/views/projects/issues/index.html.haml | 6 +- app/views/shared/issuable/_search_bar.html.haml | 76 +++++++++++++++ 6 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/filtered_search_bundle.js create mode 100644 app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 create mode 100644 app/views/shared/issuable/_search_bar.html.haml (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 54f13e328bd..5a9ee5c7d78 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -84,6 +84,9 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': + if(gl.hasOwnProperty('FilteredSearchManager')) { + new gl.FilteredSearchManager(); + } Issuable.init(); new gl.IssuableBulkActions({ prefixId: page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_', diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js new file mode 100644 index 00000000000..656979ba82f --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -0,0 +1,13 @@ + /* eslint-disable */ + // This is a manifest file that'll be compiled into including all the files listed below. + // Add new JavaScript code in separate files in this directory and they'll automatically + // be included in the compiled file accessible from http://example.com/assets/application.js + // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the + // the compiled file. + // + /*= require_tree . */ + + (function() { + + }).call(this); + \ No newline at end of file diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 new file mode 100644 index 00000000000..797473f2044 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -0,0 +1,104 @@ +((global) => { + const TOKEN_TYPE_STRING = 'string'; + const TOKEN_TYPE_ARRAY = 'array'; + + const validTokenKeys = [{ + key: 'author', + type: 'string', + },{ + key: 'assignee', + type: 'string' + },{ + key: 'milestone', + type: 'string' + },{ + key: 'label', + type: 'array' + },]; + + class FilteredSearchManager { + constructor() { + this.bindEvents(); + this.clearTokens(); + } + + bindEvents() { + const input = document.querySelector('.filtered-search'); + + input.addEventListener('input', this.tokenize.bind(this)); + input.addEventListener('keydown', this.checkForEnter.bind(this)); + } + + clearTokens() { + this.tokens = []; + this.searchToken = ''; + } + + tokenize(event) { + // Re-calculate tokens + this.clearTokens(); + + // TODO: Current implementation does not support token values that have valid spaces in them + // Example/ label:community contribution + const input = event.target.value; + const inputs = input.split(' '); + let searchTerms = ''; + + inputs.forEach((i) => { + const colonIndex = i.indexOf(':'); + + // Check if text is a token + if (colonIndex !== -1) { + const tokenKey = i.slice(0, colonIndex).toLowerCase(); + const tokenValue = i.slice(colonIndex + 1); + + const match = validTokenKeys.filter((v) => { + return v.name === tokenKey; + })[0]; + + if (match) { + this.tokens.push = { + key: match.key, + value: tokenValue, + }; + } + } else { + searchTerms += i + ' '; + } + }, this); + + this.searchToken = searchTerms.trim(); + this.printTokens(); + } + + printTokens() { + console.log(this.tokens); + console.log(this.searchToken); + } + + checkForEnter(event) { + if (event.key === 'Enter') { + event.stopPropagation(); + event.preventDefault(); + this.search(); + } + } + + search() { + console.log('search'); + let path = '?scope=all&state=opened&utf8=✓'; + + this.tokens.foreach((token) => { + + }); + + if (this.searchToken) { + path += '&search=' + this.searchToken; + } + + window.location = path; + } + } + + global.FilteredSearchManager = FilteredSearchManager; +})(window.gl || (window.gl = {})); \ No newline at end of file diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 19827943385..a565642ba38 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -23,3 +23,27 @@ } } +.filtered-search-container { + display: flex; +} + +.filtered-search-input-container { + display: flex; + position: relative; + width: 100%; + + .form-control { + padding-left: 25px; + + &:focus ~ .fa-filter { + color: #444; + } + } + + .fa-filter { + position: absolute; + left: 10px; + top: 10px; + color: $gray-darkest; + } +} diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 26f3f0ac292..18e8372ecab 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -6,6 +6,9 @@ = content_for :sub_nav do = render "projects/issues/head" +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('filtered_search/filtered_search_bundle.js') + = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@project.name} issues") @@ -20,7 +23,6 @@ = icon('rss') %span.icon-label Subscribe - = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, @@ -30,7 +32,7 @@ title: "New Issue", id: "new_issue_link" do New Issue - = render 'shared/issuable/filter', type: :issues + = render 'shared/issuable/search_bar', type: :issues .issues-holder = render 'issues' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml new file mode 100644 index 00000000000..40c1bd3ef98 --- /dev/null +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -0,0 +1,76 @@ +- finder = controller.controller_name == 'issues' || controller.controller_name == 'boards' ? issues_finder : merge_requests_finder +- boards_page = controller.controller_name == 'boards' + +.issues-filters + .issues-details-filters.row-content-block.second-block + = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do + - if params[:search].present? + = hidden_field_tag :search, params[:search] + - if @bulk_edit + .check-all-holder + = check_box_tag "check_all_issues", nil, false, + class: "check_all_issues left" + .issues-other-filters.filtered-search-container + .filtered-search-input-container + %input.form-control.filtered-search{ placeholder: 'Search or filter results...' } + = icon('filter') + .pull-right + - if boards_page + #js-boards-seach.issue-boards-search + %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } + - if can?(current_user, :admin_list, @project) + .dropdown.pull-right + %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } + Create new list + .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable + = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } + - if can?(current_user, :admin_label, @project) + = render partial: "shared/issuable/label_page_create" + = dropdown_loading + - else + = render 'shared/sort_dropdown' + + - if @bulk_edit + .issues_bulk_update.hide + = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do + .filter-item.inline + = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do + %ul + %li + %a{href: "#", data: {id: "reopen"}} Open + %li + %a{href: "#", data: {id: "close"}} Closed + .filter-item.inline + = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", + placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } }) + .filter-item.inline + = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } }) + .filter-item.inline.labels-filter + = render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true } + .filter-item.inline + = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do + %ul + %li + %a{href: "#", data: {id: "subscribe"}} Subscribe + %li + %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe + + = hidden_field_tag 'update[issuable_ids]', [] + = hidden_field_tag :state_event, params[:state_event] + .filter-item.inline + = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" + - has_labels = @labels && @labels.any? + .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } + - if has_labels + = render 'shared/labels_row', labels: @labels + +:javascript + new UsersSelect(); + new LabelsSelect(); + new MilestoneSelect(); + new IssueStatusSelect(); + new SubscriptionSelect(); + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); -- cgit v1.2.3 From cf8ae790d13f69d15f3d279565abbba3b065fba4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 7 Nov 2016 16:18:50 -0600 Subject: Add filter params to search --- .../filtered_search/filtered_search_manager.js.es6 | 36 ++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 797473f2044..c26a46a8558 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -5,15 +5,19 @@ const validTokenKeys = [{ key: 'author', type: 'string', + param: 'id', },{ key: 'assignee', - type: 'string' + type: 'string', + param: 'id', },{ key: 'milestone', - type: 'string' + type: 'string', + param: 'title', },{ key: 'label', - type: 'array' + type: 'array', + param: 'name%5B%5D', },]; class FilteredSearchManager { @@ -53,14 +57,14 @@ const tokenValue = i.slice(colonIndex + 1); const match = validTokenKeys.filter((v) => { - return v.name === tokenKey; + return v.key === tokenKey; })[0]; - if (match) { - this.tokens.push = { - key: match.key, - value: tokenValue, - }; + if (match && tokenValue.length > 0) { + this.tokens.push({ + key: match.key, + value: tokenValue, + }); } } else { searchTerms += i + ' '; @@ -72,8 +76,11 @@ } printTokens() { - console.log(this.tokens); - console.log(this.searchToken); + console.log('tokens:') + this.tokens.forEach((token) => { + console.log(token); + }) + console.log('search: ' + this.searchToken); } checkForEnter(event) { @@ -88,8 +95,13 @@ console.log('search'); let path = '?scope=all&state=opened&utf8=✓'; - this.tokens.foreach((token) => { + this.tokens.forEach((token) => { + const param = validTokenKeys.find((t) => { + return t.key === token.key; + }).param; + + path += `&${token.key}_${param}=${token.value}`; }); if (this.searchToken) { -- cgit v1.2.3 From 9e8f0e63b46ad4540eb2cf8e6206ebc22200f670 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 7 Nov 2016 16:19:15 -0600 Subject: Load searched params into input field --- .../filtered_search/filtered_search_manager.js.es6 | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c26a46a8558..44718e8306c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -23,6 +23,7 @@ class FilteredSearchManager { constructor() { this.bindEvents(); + this.loadSearchParamsFromURL(); this.clearTokens(); } @@ -38,6 +39,31 @@ this.searchToken = ''; } + loadSearchParamsFromURL() { + const params = window.location.search.split('&'); + let inputValue = ''; + + params.forEach((p) => { + const split = p.split('='); + const key = split[0]; + const value = split[1]; + + const match = validTokenKeys.find((t) => { + return key === `${t.key}_${t.param}`; + }); + + if (match) { + const sanitizedKey = key.slice(0, key.indexOf('_')); + inputValue += `${sanitizedKey}:${value} `; + } else if (!match && key === 'search') { + inputValue += `${value} `; + } + }); + + // Trim the last space value + document.querySelector('.filtered-search').value = inputValue.trim(); + } + tokenize(event) { // Re-calculate tokens this.clearTokens(); -- cgit v1.2.3 From fc6eab6919e5cc2426328061df22e9c8985f201b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 7 Nov 2016 16:19:30 -0600 Subject: Remove shared/labels_row --- app/views/shared/issuable/_search_bar.html.haml | 4 ---- 1 file changed, 4 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 40c1bd3ef98..db9011d5d57 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -59,10 +59,6 @@ = hidden_field_tag :state_event, params[:state_event] .filter-item.inline = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" - - has_labels = @labels && @labels.any? - .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } - - if has_labels - = render 'shared/labels_row', labels: @labels :javascript new UsersSelect(); -- cgit v1.2.3 From d0165c82877cbc0ddd939713e7365337e0e5478f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 7 Nov 2016 16:33:51 -0600 Subject: Add author_username and assignee_username --- .../filtered_search/filtered_search_manager.js.es6 | 4 ++-- app/finders/issuable_finder.rb | 24 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 44718e8306c..94c0b99a1e1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -5,11 +5,11 @@ const validTokenKeys = [{ key: 'author', type: 'string', - param: 'id', + param: 'username', },{ key: 'assignee', type: 'string', - param: 'id', + param: 'username', },{ key: 'milestone', type: 'string', diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index b4c14d05eaf..2afde8ece65 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -165,31 +165,43 @@ class IssuableFinder end end - def assignee? + def assignee_id? params[:assignee_id].present? end + def assignee_username? + params[:assignee_username].present? + end + def assignee return @assignee if defined?(@assignee) @assignee = - if assignee? && params[:assignee_id] != NONE + if assignee_id? && params[:assignee_id] != NONE User.find(params[:assignee_id]) + elsif assignee_username? && params[:assignee_username] != NONE + User.find_by(username: params[:assignee_username]) else nil end end - def author? + def author_id? params[:author_id].present? end + def author_username? + params[:author_username].present? + end + def author return @author if defined?(@author) @author = - if author? && params[:author_id] != NONE + if author_id? && params[:author_id] != NONE User.find(params[:author_id]) + elsif author_username? && params[:author_username] != NONE + User.find_by(username: params[:author_username]) else nil end @@ -263,7 +275,7 @@ class IssuableFinder end def by_assignee(items) - if assignee? + if assignee_id? || assignee_username? items = items.where(assignee_id: assignee.try(:id)) end @@ -271,7 +283,7 @@ class IssuableFinder end def by_author(items) - if author? + if author_id? || author_username? items = items.where(author_id: author.try(:id)) end -- cgit v1.2.3 From 339c5d43262d0061a70b0b485f5fe75f49a6cd0b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 11:35:28 -0600 Subject: Sanitize spaces in search term --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 94c0b99a1e1..c9d7a99ae44 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -56,7 +56,9 @@ const sanitizedKey = key.slice(0, key.indexOf('_')); inputValue += `${sanitizedKey}:${value} `; } else if (!match && key === 'search') { - inputValue += `${value} `; + // Sanitize value as URL converts spaces into %20 + const sanitizedValue = value.replace('%20', ' '); + inputValue += `${sanitizedValue} `; } }); @@ -139,4 +141,4 @@ } global.FilteredSearchManager = FilteredSearchManager; -})(window.gl || (window.gl = {})); \ No newline at end of file +})(window.gl || (window.gl = {})); -- cgit v1.2.3 From 823185eca1dd3483f0c178991870c9727aad6470 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 11:36:03 -0600 Subject: Fixed bug where search terms with colons were not searchable --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c9d7a99ae44..7acdabe3ef2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -76,10 +76,13 @@ const inputs = input.split(' '); let searchTerms = ''; + const addSearchTerm = function addSearchTerm(term) { + searchTerms += term + ' '; + } + inputs.forEach((i) => { const colonIndex = i.indexOf(':'); - // Check if text is a token if (colonIndex !== -1) { const tokenKey = i.slice(0, colonIndex).toLowerCase(); const tokenValue = i.slice(colonIndex + 1); @@ -93,9 +96,11 @@ key: match.key, value: tokenValue, }); + } else { + addSearchTerm(i); } } else { - searchTerms += i + ' '; + addSearchTerm(i); } }, this); -- cgit v1.2.3 From a257f48946d9d002d829e116cc4acb6349240318 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 12:47:53 -0600 Subject: Add clear search button --- .../filtered_search/filtered_search_manager.js.es6 | 21 +++++++++++++++++++++ app/assets/stylesheets/framework/filters.scss | 22 ++++++++++++++++++++-- app/views/shared/issuable/_search_bar.html.haml | 2 ++ 3 files changed, 43 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 7acdabe3ef2..ad988fe2072 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -29,9 +29,23 @@ bindEvents() { const input = document.querySelector('.filtered-search'); + const clearSearch = document.querySelector('.clear-search'); input.addEventListener('input', this.tokenize.bind(this)); input.addEventListener('keydown', this.checkForEnter.bind(this)); + + clearSearch.addEventListener('click', this.clearSearch.bind(this)); + } + + clearSearch(event) { + event.stopPropagation(); + event.preventDefault(); + + this.clearTokens(); + const input = document.querySelector('.filtered-search'); + input.value = ''; + + event.target.classList.add('hidden'); } clearTokens() { @@ -64,12 +78,19 @@ // Trim the last space value document.querySelector('.filtered-search').value = inputValue.trim(); + + if (inputValue.trim()) { + document.querySelector('.clear-search').classList.remove('hidden'); + } } tokenize(event) { // Re-calculate tokens this.clearTokens(); + // Enable clear button + document.querySelector('.clear-search').classList.remove('hidden'); + // TODO: Current implementation does not support token values that have valid spaces in them // Example/ label:community contribution const input = event.target.value; diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index a565642ba38..b192455f5f0 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -39,11 +39,29 @@ color: #444; } } - .fa-filter { position: absolute; - left: 10px; top: 10px; + left: 10px; + color: $gray-darkest; + } + + .fa-times { + right: 10px; color: $gray-darkest; } + + .clear-search { + width: 35px; + background-color: transparent; + border: none; + position: absolute; + right: 0px; + height: 100%; + outline: none; + + &:hover .fa-times { + color: #444; + } + } } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index db9011d5d57..5e759301a04 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -14,6 +14,8 @@ .filtered-search-input-container %input.form-control.filtered-search{ placeholder: 'Search or filter results...' } = icon('filter') + %button.clear-search.hidden + = icon('times') .pull-right - if boards_page #js-boards-seach.issue-boards-search -- cgit v1.2.3 From 7564c5713319517d0b61bc421275197ae7c79113 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 12:59:30 -0600 Subject: Use + instead of %20 --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index ad988fe2072..fccc0de050f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -70,8 +70,8 @@ const sanitizedKey = key.slice(0, key.indexOf('_')); inputValue += `${sanitizedKey}:${value} `; } else if (!match && key === 'search') { - // Sanitize value as URL converts spaces into %20 - const sanitizedValue = value.replace('%20', ' '); + // Sanitize value as URL converts spaces into + + const sanitizedValue = value.replace(/[+]/g, ' '); inputValue += `${sanitizedValue} `; } }); @@ -149,7 +149,6 @@ console.log('search'); let path = '?scope=all&state=opened&utf8=✓'; - this.tokens.forEach((token) => { const param = validTokenKeys.find((t) => { return t.key === token.key; @@ -159,7 +158,7 @@ }); if (this.searchToken) { - path += '&search=' + this.searchToken; + path += '&search=' + this.searchToken.replace(/ /g, '+'); } window.location = path; -- cgit v1.2.3 From 6b4358eaf70afdd79e441501a5b41690ef70b845 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 13:20:37 -0600 Subject: Add search based on state --- .../filtered_search/filtered_search_manager.js.es6 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index fccc0de050f..63cdcecdf49 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -147,7 +147,22 @@ search() { console.log('search'); - let path = '?scope=all&state=opened&utf8=✓'; + let path = '?scope=all&utf8=✓'; + + // Check current state + const currentPath = window.location.search; + const stateIndex = currentPath.indexOf('state='); + const defaultState = 'opened'; + let currentState = defaultState; + + if (stateIndex !== -1) { + const remaining = currentPath.slice(stateIndex + 6); + const separatorIndex = remaining.indexOf('&'); + + currentState = separatorIndex === -1 ? remaining : remaining.slice(0, separatorIndex); + } + + path += `&state=${currentState}` this.tokens.forEach((token) => { const param = validTokenKeys.find((t) => { -- cgit v1.2.3 From 7b382af73956518b73872c9638754e86da15d915 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 14:18:46 -0600 Subject: Add support for quotations --- .../filtered_search/filtered_search_manager.js.es6 | 41 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 63cdcecdf49..f5e53d075b0 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -68,7 +68,13 @@ if (match) { const sanitizedKey = key.slice(0, key.indexOf('_')); - inputValue += `${sanitizedKey}:${value} `; + let sanitizedValue = value; + + if (match && sanitizedKey === 'label') { + sanitizedValue = sanitizedValue.replace(/%20/g, ' '); + } + + inputValue += `${sanitizedKey}:${sanitizedValue} `; } else if (!match && key === 'search') { // Sanitize value as URL converts spaces into + const sanitizedValue = value.replace(/[+]/g, ' '); @@ -91,26 +97,51 @@ // Enable clear button document.querySelector('.clear-search').classList.remove('hidden'); - // TODO: Current implementation does not support token values that have valid spaces in them - // Example/ label:community contribution const input = event.target.value; const inputs = input.split(' '); let searchTerms = ''; + let lastQuotation = ''; + let incompleteToken = false; const addSearchTerm = function addSearchTerm(term) { searchTerms += term + ' '; } inputs.forEach((i) => { + if (incompleteToken) { + const prevToken = this.tokens[this.tokens.length - 1]; + prevToken.value += ` ${i}`; + + // Remove last quotation + const lastQuotationRegex = new RegExp(lastQuotation, 'g'); + prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); + this.tokens[this.tokens.length - 1] = prevToken; + + // Check to see if this quotation completes the token value + if (i.indexOf(lastQuotation)) { + incompleteToken = !incompleteToken; + } + + return; + } + const colonIndex = i.indexOf(':'); if (colonIndex !== -1) { const tokenKey = i.slice(0, colonIndex).toLowerCase(); const tokenValue = i.slice(colonIndex + 1); - const match = validTokenKeys.filter((v) => { + const match = validTokenKeys.find((v) => { return v.key === tokenKey; - })[0]; + }); + + if (tokenValue.indexOf('"') !== -1) { + lastQuotation = '"'; + incompleteToken = true; + } else if (tokenValue.indexOf('\'') !== -1) { + lastQuotation = '\''; + incompleteToken = true; + } if (match && tokenValue.length > 0) { this.tokens.push({ -- cgit v1.2.3 From 53b4d1b3a76f2aa80070699f623c90e4f7766506 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 15:42:15 -0600 Subject: Add special character encoding --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index f5e53d075b0..393e0b8a4b2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -17,7 +17,7 @@ },{ key: 'label', type: 'array', - param: 'name%5B%5D', + param: 'name[]', },]; class FilteredSearchManager { @@ -54,13 +54,14 @@ } loadSearchParamsFromURL() { + // We can trust that each param has one & since values containing & will be encoded const params = window.location.search.split('&'); let inputValue = ''; params.forEach((p) => { const split = p.split('='); const key = split[0]; - const value = split[1]; + const value = decodeURIComponent(split[1]); const match = validTokenKeys.find((t) => { return key === `${t.key}_${t.param}`; @@ -200,11 +201,11 @@ return t.key === token.key; }).param; - path += `&${token.key}_${param}=${token.value}`; + path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`; }); if (this.searchToken) { - path += '&search=' + this.searchToken.replace(/ /g, '+'); + path += '&search=' + encodeURIComponent(this.searchToken.replace(/ /g, '+')); } window.location = path; -- cgit v1.2.3 From 3ce7f23a855e9061e8f702ae9c9c07ce91e24738 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 16:20:47 -0600 Subject: Fix bug where search terms would not work after switching to another state tab --- .../filtered_search/filtered_search_manager.js.es6 | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 393e0b8a4b2..3528d9da88c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -55,12 +55,13 @@ loadSearchParamsFromURL() { // We can trust that each param has one & since values containing & will be encoded - const params = window.location.search.split('&'); + // Remove the first character of search as it is always ? + const params = window.location.search.slice(1).split('&'); let inputValue = ''; params.forEach((p) => { const split = p.split('='); - const key = split[0]; + const key = decodeURIComponent(split[0]); const value = decodeURIComponent(split[1]); const match = validTokenKeys.find((t) => { @@ -69,17 +70,24 @@ if (match) { const sanitizedKey = key.slice(0, key.indexOf('_')); - let sanitizedValue = value; + const valueHasSpace = value.indexOf(' ') !== -1; - if (match && sanitizedKey === 'label') { - sanitizedValue = sanitizedValue.replace(/%20/g, ' '); + const preferredQuotations = '"'; + let quotationsToUse = preferredQuotations; + + if (valueHasSpace) { + // Prefer ", but use ' if required + quotationsToUse = value.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; } - inputValue += `${sanitizedKey}:${sanitizedValue} `; + inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${value}${quotationsToUse}` : `${sanitizedKey}:${value}`; + inputValue += ' '; + } else if (!match && key === 'search') { // Sanitize value as URL converts spaces into + const sanitizedValue = value.replace(/[+]/g, ' '); - inputValue += `${sanitizedValue} `; + inputValue += sanitizedValue; + inputValue += ' '; } }); -- cgit v1.2.3 From d797b03b98e9eccc5d2c7ba4de2d46b0aff1ff67 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 16:30:06 -0600 Subject: Fix bug where spaces would conver into + for all values --- .../filtered_search/filtered_search_manager.js.es6 | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 3528d9da88c..9fe70bbf3a7 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -62,7 +62,11 @@ params.forEach((p) => { const split = p.split('='); const key = decodeURIComponent(split[0]); - const value = decodeURIComponent(split[1]); + const value = split[1]; + + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; const match = validTokenKeys.find((t) => { return key === `${t.key}_${t.param}`; @@ -70,22 +74,20 @@ if (match) { const sanitizedKey = key.slice(0, key.indexOf('_')); - const valueHasSpace = value.indexOf(' ') !== -1; + const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; const preferredQuotations = '"'; let quotationsToUse = preferredQuotations; if (valueHasSpace) { // Prefer ", but use ' if required - quotationsToUse = value.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; + quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; } - inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${value}${quotationsToUse}` : `${sanitizedKey}:${value}`; + inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${sanitizedValue}`; inputValue += ' '; } else if (!match && key === 'search') { - // Sanitize value as URL converts spaces into + - const sanitizedValue = value.replace(/[+]/g, ' '); inputValue += sanitizedValue; inputValue += ' '; } @@ -213,7 +215,7 @@ }); if (this.searchToken) { - path += '&search=' + encodeURIComponent(this.searchToken.replace(/ /g, '+')); + path += '&search=' + encodeURIComponent(this.searchToken); } window.location = path; -- cgit v1.2.3 From ad02257c3ca25806c7104673566bc99c3f6867ed Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 8 Nov 2016 17:54:19 -0600 Subject: Fix bug where clear search button would not toggle visible --- .../filtered_search/filtered_search_manager.js.es6 | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 9fe70bbf3a7..42fe0cace10 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -32,6 +32,7 @@ const clearSearch = document.querySelector('.clear-search'); input.addEventListener('input', this.tokenize.bind(this)); + input.addEventListener('input', this.toggleClearSearchButton); input.addEventListener('keydown', this.checkForEnter.bind(this)); clearSearch.addEventListener('click', this.clearSearch.bind(this)); @@ -42,10 +43,8 @@ event.preventDefault(); this.clearTokens(); - const input = document.querySelector('.filtered-search'); - input.value = ''; - - event.target.classList.add('hidden'); + document.querySelector('.filtered-search').value = ''; + document.querySelector('.clear-search').classList.add('hidden'); } clearTokens() { @@ -101,13 +100,20 @@ } } + toggleClearSearchButton(event) { + const clearSearch = document.querySelector('.clear-search'); + + if (event.target.value) { + clearSearch.classList.remove('hidden'); + } else { + clearSearch.classList.add('hidden'); + } + } + tokenize(event) { // Re-calculate tokens this.clearTokens(); - // Enable clear button - document.querySelector('.clear-search').classList.remove('hidden'); - const input = event.target.value; const inputs = input.split(' '); let searchTerms = ''; -- cgit v1.2.3 From fe4d33cf15b877e8ba22f518f068088db8a3e36d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 9 Nov 2016 13:51:43 -0600 Subject: Fix scss lint --- app/assets/stylesheets/framework/filters.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index b192455f5f0..90b9394b207 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -39,6 +39,7 @@ color: #444; } } + .fa-filter { position: absolute; top: 10px; @@ -56,7 +57,7 @@ background-color: transparent; border: none; position: absolute; - right: 0px; + right: 0; height: 100%; outline: none; -- cgit v1.2.3 From f1574e45b268e9f1dd488a3962327a4c40f26ae9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 9 Nov 2016 14:31:58 -0600 Subject: Fix ESLint errors --- .../filtered_search/filtered_search_manager.js.es6 | 156 ++++++++++----------- 1 file changed, 71 insertions(+), 85 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 42fe0cace10..1b58fc01608 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,29 +1,81 @@ +/* eslint-disable no-param-reassign */ ((global) => { - const TOKEN_TYPE_STRING = 'string'; - const TOKEN_TYPE_ARRAY = 'array'; - const validTokenKeys = [{ key: 'author', type: 'string', param: 'username', - },{ + }, { key: 'assignee', type: 'string', param: 'username', - },{ + }, { key: 'milestone', type: 'string', param: 'title', - },{ + }, { key: 'label', type: 'array', param: 'name[]', - },]; + }]; + + function toggleClearSearchButton(event) { + const clearSearch = document.querySelector('.clear-search'); + + if (event.target.value) { + clearSearch.classList.remove('hidden'); + } else { + clearSearch.classList.add('hidden'); + } + } + + function loadSearchParamsFromURL() { + // We can trust that each param has one & since values containing & will be encoded + // Remove the first character of search as it is always ? + const params = window.location.search.slice(1).split('&'); + let inputValue = ''; + + params.forEach((p) => { + const split = p.split('='); + const key = decodeURIComponent(split[0]); + const value = split[1]; + + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; + const match = validTokenKeys.find(t => key === `${t.key}_${t.param}`); + + if (match) { + const sanitizedKey = key.slice(0, key.indexOf('_')); + const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; + + const preferredQuotations = '"'; + let quotationsToUse = preferredQuotations; + + if (valueHasSpace) { + // Prefer ", but use ' if required + quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; + } + + inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${sanitizedValue}`; + inputValue += ' '; + } else if (!match && key === 'search') { + inputValue += sanitizedValue; + inputValue += ' '; + } + }); + + // Trim the last space value + document.querySelector('.filtered-search').value = inputValue.trim(); + + if (inputValue.trim()) { + document.querySelector('.clear-search').classList.remove('hidden'); + } + } class FilteredSearchManager { constructor() { this.bindEvents(); - this.loadSearchParamsFromURL(); + loadSearchParamsFromURL(); this.clearTokens(); } @@ -32,7 +84,7 @@ const clearSearch = document.querySelector('.clear-search'); input.addEventListener('input', this.tokenize.bind(this)); - input.addEventListener('input', this.toggleClearSearchButton); + input.addEventListener('input', toggleClearSearchButton); input.addEventListener('keydown', this.checkForEnter.bind(this)); clearSearch.addEventListener('click', this.clearSearch.bind(this)); @@ -52,64 +104,6 @@ this.searchToken = ''; } - loadSearchParamsFromURL() { - // We can trust that each param has one & since values containing & will be encoded - // Remove the first character of search as it is always ? - const params = window.location.search.slice(1).split('&'); - let inputValue = ''; - - params.forEach((p) => { - const split = p.split('='); - const key = decodeURIComponent(split[0]); - const value = split[1]; - - // Sanitize value since URL converts spaces into + - // Replace before decode so that we know what was originally + versus the encoded + - const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; - - const match = validTokenKeys.find((t) => { - return key === `${t.key}_${t.param}`; - }); - - if (match) { - const sanitizedKey = key.slice(0, key.indexOf('_')); - const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; - - const preferredQuotations = '"'; - let quotationsToUse = preferredQuotations; - - if (valueHasSpace) { - // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; - } - - inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${sanitizedValue}`; - inputValue += ' '; - - } else if (!match && key === 'search') { - inputValue += sanitizedValue; - inputValue += ' '; - } - }); - - // Trim the last space value - document.querySelector('.filtered-search').value = inputValue.trim(); - - if (inputValue.trim()) { - document.querySelector('.clear-search').classList.remove('hidden'); - } - } - - toggleClearSearchButton(event) { - const clearSearch = document.querySelector('.clear-search'); - - if (event.target.value) { - clearSearch.classList.remove('hidden'); - } else { - clearSearch.classList.add('hidden'); - } - } - tokenize(event) { // Re-calculate tokens this.clearTokens(); @@ -121,8 +115,9 @@ let incompleteToken = false; const addSearchTerm = function addSearchTerm(term) { - searchTerms += term + ' '; - } + // Add space for next term + searchTerms += `${term} `; + }; inputs.forEach((i) => { if (incompleteToken) { @@ -147,10 +142,7 @@ if (colonIndex !== -1) { const tokenKey = i.slice(0, colonIndex).toLowerCase(); const tokenValue = i.slice(colonIndex + 1); - - const match = validTokenKeys.find((v) => { - return v.key === tokenKey; - }); + const match = validTokenKeys.find(v => v.key === tokenKey); if (tokenValue.indexOf('"') !== -1) { lastQuotation = '"'; @@ -178,11 +170,9 @@ } printTokens() { - console.log('tokens:') - this.tokens.forEach((token) => { - console.log(token); - }) - console.log('search: ' + this.searchToken); + console.log('tokens:'); + this.tokens.forEach(token => console.log(token)); + console.log(`search: ${this.searchToken}`); } checkForEnter(event) { @@ -210,18 +200,14 @@ currentState = separatorIndex === -1 ? remaining : remaining.slice(0, separatorIndex); } - path += `&state=${currentState}` - + path += `&state=${currentState}`; this.tokens.forEach((token) => { - const param = validTokenKeys.find((t) => { - return t.key === token.key; - }).param; - + const param = validTokenKeys.find(t => t.key === token.key).param; path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`; }); if (this.searchToken) { - path += '&search=' + encodeURIComponent(this.searchToken); + path += `&search=${encodeURIComponent(this.searchToken)}`; } window.location = path; -- cgit v1.2.3 From 3845bf377296f58e1604d44e4db529099e14888e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 9 Nov 2016 14:44:11 -0600 Subject: Add droplab --- app/assets/javascripts/droplab/droplab.js | 515 +++++++++++++++++++++++ app/assets/javascripts/droplab/droplab_ajax.js | 45 ++ app/assets/javascripts/droplab/droplab_filter.js | 28 ++ 3 files changed, 588 insertions(+) create mode 100644 app/assets/javascripts/droplab/droplab.js create mode 100644 app/assets/javascripts/droplab/droplab_ajax.js create mode 100644 app/assets/javascripts/droplab/droplab_filter.js (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js new file mode 100644 index 00000000000..18ca8be7203 --- /dev/null +++ b/app/assets/javascripts/droplab/droplab.js @@ -0,0 +1,515 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.droplab = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0){ + if(!listItems[currentIndex-1]){ + currentIndex = currentIndex-1; + } + listItems[currentIndex-1].classList.add('dropdown-active'); + } + }; + + var mousedown = function mousedown(e) { + var list = e.detail.hook.list; + removeHighlight(list); + list.show(); + currentIndex = 0; + isUpArrow = false; + isDownArrow = false; + }; + var selectItem = function selectItem(list) { + var listItems = removeHighlight(list); + var currentItem = listItems[currentIndex-1]; + var listEvent = new CustomEvent('click.dl', { + detail: { + list: list, + selected: currentItem, + data: currentItem.dataset, + }, + }); + list.list.dispatchEvent(listEvent); + list.hide(); + } + + var keydown = function keydown(e){ + var typedOn = e.target; + isUpArrow = false; + isDownArrow = false; + + if(e.detail.which){ + currentKey = e.detail.which; + if(currentKey === 13){ + selectItem(e.detail.hook.list); + return; + } + if(currentKey === 38) { + isUpArrow = true; + } + if(currentKey === 40) { + isDownArrow = true; + } + } else if(e.detail.key) { + currentKey = e.detail.key; + if(currentKey === 'Enter'){ + selectItem(e.detail.hook.list); + return; + } + if(currentKey === 'ArrowUp') { + isUpArrow = true; + } + if(currentKey === 'ArrowDown') { + isDownArrow = true; + } + } + if(isUpArrow){ currentIndex--; } + if(isDownArrow){ currentIndex++; } + if(currentIndex < 0){ currentIndex = 0; } + setMenuForArrows(e.detail.hook.list); + }; + + w.addEventListener('mousedown.dl', mousedown); + w.addEventListener('keydown.dl', keydown); + }; +}); +},{"./window":11}],10:[function(require,module,exports){ +var DATA_TRIGGER = require('./constants').DATA_TRIGGER; +var DATA_DROPDOWN = require('./constants').DATA_DROPDOWN; + +var toDataCamelCase = function(attr){ + return this.camelize(attr.split('-').slice(1).join(' ')); +}; + +// the tiniest damn templating I can do +var t = function(s,d){ + for(var p in d) + s=s.replace(new RegExp('{{'+p+'}}','g'), d[p]); + return s; +}; + +var camelize = function(str) { + return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { + return index == 0 ? letter.toLowerCase() : letter.toUpperCase(); + }).replace(/\s+/g, ''); +}; + +var closest = function(thisTag, stopTag) { + while(thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ + thisTag = thisTag.parentNode; + } + return thisTag; +}; + +var isDropDownParts = function(target) { + if(target.tagName === 'HTML') { return false; } + return ( + target.hasAttribute(DATA_TRIGGER) || + target.hasAttribute(DATA_DROPDOWN) + ); +}; + +module.exports = { + toDataCamelCase: toDataCamelCase, + t: t, + camelize: camelize, + closest: closest, + isDropDownParts: isDropDownParts, +}; + +},{"./constants":1}],11:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[8])(8) +}); diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js new file mode 100644 index 00000000000..23e43b352d6 --- /dev/null +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -0,0 +1,45 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Date: Wed, 9 Nov 2016 17:07:30 -0600 Subject: Refactor tokenizer --- .../filtered_search/filtered_search_manager.js.es6 | 120 +++++---------------- .../filtered_search/filtered_search_tokenizer.es6 | 90 ++++++++++++++++ 2 files changed, 115 insertions(+), 95 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 1b58fc01608..58c64ea078d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -18,13 +18,21 @@ param: 'name[]', }]; + function clearSearch(event) { + event.stopPropagation(); + event.preventDefault(); + + document.querySelector('.filtered-search').value = ''; + document.querySelector('.clear-search').classList.add('hidden'); + } + function toggleClearSearchButton(event) { - const clearSearch = document.querySelector('.clear-search'); + const clearSearchButton = document.querySelector('.clear-search'); if (event.target.value) { - clearSearch.classList.remove('hidden'); + clearSearchButton.classList.remove('hidden'); } else { - clearSearch.classList.add('hidden'); + clearSearchButton.classList.add('hidden'); } } @@ -74,105 +82,24 @@ class FilteredSearchManager { constructor() { + this.tokenizer = new gl.FilteredSearchTokenizer(validTokenKeys); this.bindEvents(); loadSearchParamsFromURL(); - this.clearTokens(); } bindEvents() { - const input = document.querySelector('.filtered-search'); - const clearSearch = document.querySelector('.clear-search'); - - input.addEventListener('input', this.tokenize.bind(this)); - input.addEventListener('input', toggleClearSearchButton); - input.addEventListener('keydown', this.checkForEnter.bind(this)); + const filteredSearchInput = document.querySelector('.filtered-search'); - clearSearch.addEventListener('click', this.clearSearch.bind(this)); - } - - clearSearch(event) { - event.stopPropagation(); - event.preventDefault(); - - this.clearTokens(); - document.querySelector('.filtered-search').value = ''; - document.querySelector('.clear-search').classList.add('hidden'); - } + filteredSearchInput.addEventListener('input', this.processInput.bind(this)); + filteredSearchInput.addEventListener('input', toggleClearSearchButton); + filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); - clearTokens() { - this.tokens = []; - this.searchToken = ''; + document.querySelector('.clear-search').addEventListener('click', clearSearch); } - tokenize(event) { - // Re-calculate tokens - this.clearTokens(); - + processInput(event) { const input = event.target.value; - const inputs = input.split(' '); - let searchTerms = ''; - let lastQuotation = ''; - let incompleteToken = false; - - const addSearchTerm = function addSearchTerm(term) { - // Add space for next term - searchTerms += `${term} `; - }; - - inputs.forEach((i) => { - if (incompleteToken) { - const prevToken = this.tokens[this.tokens.length - 1]; - prevToken.value += ` ${i}`; - - // Remove last quotation - const lastQuotationRegex = new RegExp(lastQuotation, 'g'); - prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); - this.tokens[this.tokens.length - 1] = prevToken; - - // Check to see if this quotation completes the token value - if (i.indexOf(lastQuotation)) { - incompleteToken = !incompleteToken; - } - - return; - } - - const colonIndex = i.indexOf(':'); - - if (colonIndex !== -1) { - const tokenKey = i.slice(0, colonIndex).toLowerCase(); - const tokenValue = i.slice(colonIndex + 1); - const match = validTokenKeys.find(v => v.key === tokenKey); - - if (tokenValue.indexOf('"') !== -1) { - lastQuotation = '"'; - incompleteToken = true; - } else if (tokenValue.indexOf('\'') !== -1) { - lastQuotation = '\''; - incompleteToken = true; - } - - if (match && tokenValue.length > 0) { - this.tokens.push({ - key: match.key, - value: tokenValue, - }); - } else { - addSearchTerm(i); - } - } else { - addSearchTerm(i); - } - }, this); - - this.searchToken = searchTerms.trim(); - this.printTokens(); - } - - printTokens() { - console.log('tokens:'); - this.tokens.forEach(token => console.log(token)); - console.log(`search: ${this.searchToken}`); + this.tokenizer.processTokens(input); } checkForEnter(event) { @@ -193,6 +120,9 @@ const defaultState = 'opened'; let currentState = defaultState; + const tokens = this.tokenizer.getTokens(); + const searchToken = this.tokenizer.getSearchToken(); + if (stateIndex !== -1) { const remaining = currentPath.slice(stateIndex + 6); const separatorIndex = remaining.indexOf('&'); @@ -201,13 +131,13 @@ } path += `&state=${currentState}`; - this.tokens.forEach((token) => { + tokens.forEach((token) => { const param = validTokenKeys.find(t => t.key === token.key).param; path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`; }); - if (this.searchToken) { - path += `&search=${encodeURIComponent(this.searchToken)}`; + if (searchToken) { + path += `&search=${encodeURIComponent(searchToken)}`; } window.location = path; diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 new file mode 100644 index 00000000000..f6cc1b8860d --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -0,0 +1,90 @@ +/* eslint-disable no-param-reassign */ +((global) => { + class FilteredSearchTokenizer { + constructor(validTokenKeys) { + this.validTokenKeys = validTokenKeys; + this.resetTokens(); + } + + getTokens() { + return this.tokens; + } + + getSearchToken() { + return this.searchToken; + } + + resetTokens() { + this.tokens = []; + this.searchToken = ''; + } + + printTokens() { + console.log('tokens:'); + this.tokens.forEach(token => console.log(token)); + console.log(`search: ${this.searchToken}`); + } + + processTokens(input) { + // Re-calculate tokens + this.resetTokens(); + + const inputs = input.split(' '); + let searchTerms = ''; + let lastQuotation = ''; + let incompleteToken = false; + + inputs.forEach((i) => { + if (incompleteToken) { + const prevToken = this.tokens[this.tokens.length - 1]; + prevToken.value += ` ${i}`; + + // Remove last quotation + const lastQuotationRegex = new RegExp(lastQuotation, 'g'); + prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); + this.tokens[this.tokens.length - 1] = prevToken; + + // Check to see if this quotation completes the token value + if (i.indexOf(lastQuotation)) { + incompleteToken = !incompleteToken; + } + + return; + } + + const colonIndex = i.indexOf(':'); + + if (colonIndex !== -1) { + const tokenKey = i.slice(0, colonIndex).toLowerCase(); + const tokenValue = i.slice(colonIndex + 1); + const match = this.validTokenKeys.find(v => v.key === tokenKey); + + if (tokenValue.indexOf('"') !== -1) { + lastQuotation = '"'; + incompleteToken = true; + } else if (tokenValue.indexOf('\'') !== -1) { + lastQuotation = '\''; + incompleteToken = true; + } + + if (match && tokenValue.length > 0) { + this.tokens.push({ + key: match.key, + value: tokenValue, + }); + + return; + } + } + + // Add space for next term + searchTerms += `${i} `; + }, this); + + this.searchToken = searchTerms.trim(); + this.printTokens(); + } + } + + global.FilteredSearchTokenizer = FilteredSearchTokenizer; +})(window.gl || (window.gl = {})); -- cgit v1.2.3 From 8b4e4e333db0cf47080aa8577b4351b9e00525ea Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 9 Nov 2016 19:10:15 -0600 Subject: Fix JS for tests --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 7 ++++--- .../javascripts/filtered_search/filtered_search_tokenizer.es6 | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 58c64ea078d..db414b9755d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -50,7 +50,7 @@ // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; - const match = validTokenKeys.find(t => key === `${t.key}_${t.param}`); + const match = validTokenKeys.filter(t => key === `${t.key}_${t.param}`)[0]; if (match) { const sanitizedKey = key.slice(0, key.indexOf('_')); @@ -103,7 +103,8 @@ } checkForEnter(event) { - if (event.key === 'Enter') { + // Enter KeyCode + if (event.keyCode === 13) { event.stopPropagation(); event.preventDefault(); this.search(); @@ -132,7 +133,7 @@ path += `&state=${currentState}`; tokens.forEach((token) => { - const param = validTokenKeys.find(t => t.key === token.key).param; + const param = validTokenKeys.filter(t => t.key === token.key)[0].param; path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`; }); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index f6cc1b8860d..ddb173b2d98 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -57,7 +57,7 @@ if (colonIndex !== -1) { const tokenKey = i.slice(0, colonIndex).toLowerCase(); const tokenValue = i.slice(colonIndex + 1); - const match = this.validTokenKeys.find(v => v.key === tokenKey); + const match = this.validTokenKeys.filter(v => v.key === tokenKey)[0]; if (tokenValue.indexOf('"') !== -1) { lastQuotation = '"'; -- cgit v1.2.3 From 9c8a86f60d2d36b628c5275004e4c17aa07aeeeb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 10 Nov 2016 16:49:12 -0600 Subject: Update filter issue specs --- app/assets/stylesheets/framework/filters.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 90b9394b207..c679a3833e9 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -24,10 +24,12 @@ } .filtered-search-container { + display: -webkit-flex; display: flex; } .filtered-search-input-container { + display: -webkit-flex; display: flex; position: relative; width: 100%; -- cgit v1.2.3 From f20875ec4557b23d6df810bd49e1955f5fbbd6e0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 11 Nov 2016 11:56:47 -0600 Subject: Add username to gon --- app/assets/javascripts/search_autocomplete.js.es6 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 437f5dbbf7d..cec8856d4e7 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -142,8 +142,9 @@ } getCategoryContents() { - var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; + var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils; userId = gon.current_user_id; + userName = gon.current_username; utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; if (utils.isInGroupsPage() && groupOptions) { options = groupOptions[utils.getGroupSlug()]; @@ -158,10 +159,10 @@ header: "" + name }, { text: 'Issues assigned to me', - url: issuesPath + "/?assignee_id=" + userId + url: issuesPath + "/?assignee_username=" + userName }, { text: "Issues I've created", - url: issuesPath + "/?author_id=" + userId + url: issuesPath + "/?author_username=" + userName }, 'separator', { text: 'Merge requests assigned to me', url: mrPath + "/?assignee_id=" + userId -- cgit v1.2.3 From 01eb0571f0498225c3d75df419b8a50a47739dc8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 14 Nov 2016 10:37:55 -0600 Subject: Resolve MR review suggestions --- .../filtered_search/filtered_search_bundle.js | 6 ----- .../filtered_search/filtered_search_manager.js.es6 | 28 +++++++++++----------- .../filtered_search/filtered_search_tokenizer.es6 | 2 +- 3 files changed, 15 insertions(+), 21 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js index 656979ba82f..d188718c5f3 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -1,4 +1,3 @@ - /* eslint-disable */ // This is a manifest file that'll be compiled into including all the files listed below. // Add new JavaScript code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js @@ -6,8 +5,3 @@ // the compiled file. // /*= require_tree . */ - - (function() { - - }).call(this); - \ No newline at end of file diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index db414b9755d..26b9d334545 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -18,22 +18,22 @@ param: 'name[]', }]; - function clearSearch(event) { - event.stopPropagation(); - event.preventDefault(); + function clearSearch(e) { + e.stopPropagation(); + e.preventDefault(); document.querySelector('.filtered-search').value = ''; document.querySelector('.clear-search').classList.add('hidden'); } - function toggleClearSearchButton(event) { + function toggleClearSearchButton(e) { const clearSearchButton = document.querySelector('.clear-search'); if (event.target.value) { - clearSearchButton.classList.remove('hidden'); - } else { - clearSearchButton.classList.add('hidden'); - } + clearSearchButton.classList.remove('hidden'); + } else { + clearSearchButton.classList.add('hidden'); + } } function loadSearchParamsFromURL() { @@ -97,16 +97,16 @@ document.querySelector('.clear-search').addEventListener('click', clearSearch); } - processInput(event) { - const input = event.target.value; + processInput(e) { + const input = e.target.value; this.tokenizer.processTokens(input); } - checkForEnter(event) { + checkForEnter(e) { // Enter KeyCode - if (event.keyCode === 13) { - event.stopPropagation(); - event.preventDefault(); + if (e.keyCode === 13) { + e.stopPropagation(); + e.preventDefault(); this.search(); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index ddb173b2d98..de91081edfa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -36,7 +36,7 @@ inputs.forEach((i) => { if (incompleteToken) { - const prevToken = this.tokens[this.tokens.length - 1]; + const prevToken = this.tokens.last(); prevToken.value += ` ${i}`; // Remove last quotation -- cgit v1.2.3 From 329b03b3c3fa51f365dee867cf4d8cef5ad23d4e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 14 Nov 2016 13:22:32 -0600 Subject: Add token symbol matching --- .../filtered_search/filtered_search_manager.js.es6 | 94 ++++++++++++++++------ .../filtered_search/filtered_search_tokenizer.es6 | 10 ++- 2 files changed, 78 insertions(+), 26 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 26b9d334545..31e570bd6b6 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -4,18 +4,37 @@ key: 'author', type: 'string', param: 'username', + symbol: '@', }, { key: 'assignee', type: 'string', param: 'username', + symbol: '@', + conditions: [{ + keyword: 'none', + url: 'assignee_id=0', + }] }, { key: 'milestone', type: 'string', param: 'title', + symbol: '%', + conditions: [{ + keyword: 'none', + url: 'milestone_title=No+Milestone', + }, { + keyword: 'upcoming', + url: 'milestone_title=%23upcoming', + }] }, { key: 'label', type: 'array', param: 'name[]', + symbol: '~', + conditions: [{ + keyword: 'none', + url: 'label_name[]=No+Label', + }] }]; function clearSearch(e) { @@ -47,28 +66,42 @@ const key = decodeURIComponent(split[0]); const value = split[1]; - // Sanitize value since URL converts spaces into + - // Replace before decode so that we know what was originally + versus the encoded + - const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; - const match = validTokenKeys.filter(t => key === `${t.key}_${t.param}`)[0]; - - if (match) { - const sanitizedKey = key.slice(0, key.indexOf('_')); - const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; - - const preferredQuotations = '"'; - let quotationsToUse = preferredQuotations; - - if (valueHasSpace) { - // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; + // Check if it matches edge conditions listed in validTokenKeys + let conditionIndex = 0; + const validCondition = validTokenKeys.filter(v => v.conditions && v.conditions.filter((c, index) => { + if (c.url === p) { + conditionIndex = index; + } + return c.url === p; + })[0])[0]; + + if (validCondition) { + inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; + } else { + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; + const match = validTokenKeys.filter(t => key === `${t.key}_${t.param}`)[0]; + + if (match) { + const sanitizedKey = key.slice(0, key.indexOf('_')); + const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; + const symbol = match.symbol; + + const preferredQuotations = '"'; + let quotationsToUse = preferredQuotations; + + if (valueHasSpace) { + // Prefer ", but use ' if required + quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; + } + + inputValue += valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`; + inputValue += ' '; + } else if (!match && key === 'search') { + inputValue += sanitizedValue; + inputValue += ' '; } - - inputValue += valueHasSpace ? `${sanitizedKey}:${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${sanitizedValue}`; - inputValue += ' '; - } else if (!match && key === 'search') { - inputValue += sanitizedValue; - inputValue += ' '; } }); @@ -133,8 +166,23 @@ path += `&state=${currentState}`; tokens.forEach((token) => { - const param = validTokenKeys.filter(t => t.key === token.key)[0].param; - path += `&${token.key}_${param}=${encodeURIComponent(token.value)}`; + const match = validTokenKeys.filter(t => t.key === token.key)[0]; + let tokenPath = ''; + + if (token.wildcard && match.conditions) { + const condition = match.conditions.filter(c => c.keyword === token.value.toLowerCase())[0]; + + if (condition) { + tokenPath = `${condition.url}`; + } + } else if (!token.wildcard) { + // Remove the wildcard token + tokenPath = `${token.key}_${match.param}=${encodeURIComponent(token.value.slice(1))}`; + } else { + tokenPath = `${token.key}_${match.param}=${encodeURIComponent(token.value)}`; + } + + path += `&${tokenPath}`; }); if (searchToken) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index de91081edfa..c3e5e817c9e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -57,7 +57,10 @@ if (colonIndex !== -1) { const tokenKey = i.slice(0, colonIndex).toLowerCase(); const tokenValue = i.slice(colonIndex + 1); - const match = this.validTokenKeys.filter(v => v.key === tokenKey)[0]; + const tokenSymbol = tokenValue[0]; + console.log(tokenSymbol) + const keyMatch = this.validTokenKeys.filter(v => v.key === tokenKey)[0]; + const symbolMatch = this.validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; if (tokenValue.indexOf('"') !== -1) { lastQuotation = '"'; @@ -67,10 +70,11 @@ incompleteToken = true; } - if (match && tokenValue.length > 0) { + if (keyMatch && tokenValue.length > 0) { this.tokens.push({ - key: match.key, + key: keyMatch.key, value: tokenValue, + wildcard: symbolMatch ? false : true, }); return; -- cgit v1.2.3 From 8e3a52cfd68302ed75ffb89de3a08d1f70f876ad Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 14 Nov 2016 17:45:26 -0600 Subject: Fix eslint --- .../filtered_search/filtered_search_manager.js.es6 | 32 ++++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 31e570bd6b6..8568bf78416 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -13,7 +13,7 @@ conditions: [{ keyword: 'none', url: 'assignee_id=0', - }] + }], }, { key: 'milestone', type: 'string', @@ -25,7 +25,7 @@ }, { keyword: 'upcoming', url: 'milestone_title=%23upcoming', - }] + }], }, { key: 'label', type: 'array', @@ -34,7 +34,7 @@ conditions: [{ keyword: 'none', url: 'label_name[]=No+Label', - }] + }], }]; function clearSearch(e) { @@ -48,11 +48,11 @@ function toggleClearSearchButton(e) { const clearSearchButton = document.querySelector('.clear-search'); - if (event.target.value) { - clearSearchButton.classList.remove('hidden'); - } else { - clearSearchButton.classList.add('hidden'); - } + if (e.target.value) { + clearSearchButton.classList.remove('hidden'); + } else { + clearSearchButton.classList.add('hidden'); + } } function loadSearchParamsFromURL() { @@ -68,12 +68,13 @@ // Check if it matches edge conditions listed in validTokenKeys let conditionIndex = 0; - const validCondition = validTokenKeys.filter(v => v.conditions && v.conditions.filter((c, index) => { - if (c.url === p) { - conditionIndex = index; - } - return c.url === p; - })[0])[0]; + const validCondition = validTokenKeys + .filter(v => v.conditions && v.conditions.filter((c, index) => { + if (c.url === p) { + conditionIndex = index; + } + return c.url === p; + })[0])[0]; if (validCondition) { inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; @@ -170,7 +171,8 @@ let tokenPath = ''; if (token.wildcard && match.conditions) { - const condition = match.conditions.filter(c => c.keyword === token.value.toLowerCase())[0]; + const condition = match.conditions + .filter(c => c.keyword === token.value.toLowerCase())[0]; if (condition) { tokenPath = `${condition.url}`; -- cgit v1.2.3 From 976893ec2fa1e4289f5d923a41d296e170bdf3af Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 14 Nov 2016 22:14:29 -0600 Subject: Add support for labels containing single/double quote --- .../filtered_search/filtered_search_tokenizer.es6 | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index c3e5e817c9e..eab805c4714 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -62,10 +62,20 @@ const keyMatch = this.validTokenKeys.filter(v => v.key === tokenKey)[0]; const symbolMatch = this.validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; - if (tokenValue.indexOf('"') !== -1) { + const doubleQuoteIndex = tokenValue.indexOf('"'); + const singleQuoteIndex = tokenValue.indexOf('\''); + + const doubleQuoteExist = doubleQuoteIndex !== -1; + const singleQuoteExist = singleQuoteIndex !== -1; + + if ((doubleQuoteExist && !singleQuoteExist) || + (doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex)) { + // " is found and is in front of ' (if any) lastQuotation = '"'; incompleteToken = true; - } else if (tokenValue.indexOf('\'') !== -1) { + } else if ((singleQuoteExist && !doubleQuoteExist) || + (doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex)) { + // ' is found and is in front of " (if any) lastQuotation = '\''; incompleteToken = true; } -- cgit v1.2.3 From 8ecc2117db3a38961785fcaa4b49bd6de13371d4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 30 Nov 2016 12:30:52 -0600 Subject: Refactor validTokenKeys --- .../filtered_search/filtered_search_manager.js.es6 | 51 +++--------------- .../filtered_search_token_keys.js.es6 | 45 ++++++++++++++++ .../filtered_search/filtered_search_tokenizer.es6 | 60 +++++++++++----------- 3 files changed, 81 insertions(+), 75 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 8568bf78416..3899181a352 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,42 +1,5 @@ /* eslint-disable no-param-reassign */ ((global) => { - const validTokenKeys = [{ - key: 'author', - type: 'string', - param: 'username', - symbol: '@', - }, { - key: 'assignee', - type: 'string', - param: 'username', - symbol: '@', - conditions: [{ - keyword: 'none', - url: 'assignee_id=0', - }], - }, { - key: 'milestone', - type: 'string', - param: 'title', - symbol: '%', - conditions: [{ - keyword: 'none', - url: 'milestone_title=No+Milestone', - }, { - keyword: 'upcoming', - url: 'milestone_title=%23upcoming', - }], - }, { - key: 'label', - type: 'array', - param: 'name[]', - symbol: '~', - conditions: [{ - keyword: 'none', - url: 'label_name[]=No+Label', - }], - }]; - function clearSearch(e) { e.stopPropagation(); e.preventDefault(); @@ -66,9 +29,9 @@ const key = decodeURIComponent(split[0]); const value = split[1]; - // Check if it matches edge conditions listed in validTokenKeys + // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys.get() let conditionIndex = 0; - const validCondition = validTokenKeys + const validCondition = gl.FilteredSearchTokenKeys.get() .filter(v => v.conditions && v.conditions.filter((c, index) => { if (c.url === p) { conditionIndex = index; @@ -82,7 +45,7 @@ // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; - const match = validTokenKeys.filter(t => key === `${t.key}_${t.param}`)[0]; + const match = gl.FilteredSearchTokenKeys.get().filter(t => key === `${t.key}_${t.param}`)[0]; if (match) { const sanitizedKey = key.slice(0, key.indexOf('_')); @@ -116,7 +79,7 @@ class FilteredSearchManager { constructor() { - this.tokenizer = new gl.FilteredSearchTokenizer(validTokenKeys); + this.tokenizer = gl.FilteredSearchTokenizer; this.bindEvents(); loadSearchParamsFromURL(); } @@ -131,6 +94,7 @@ document.querySelector('.clear-search').addEventListener('click', clearSearch); } + // TODO: This is only used for testing, remove when going to PRO processInput(e) { const input = e.target.value; this.tokenizer.processTokens(input); @@ -155,8 +119,7 @@ const defaultState = 'opened'; let currentState = defaultState; - const tokens = this.tokenizer.getTokens(); - const searchToken = this.tokenizer.getSearchToken(); + const { tokens, searchToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); if (stateIndex !== -1) { const remaining = currentPath.slice(stateIndex + 6); @@ -167,7 +130,7 @@ path += `&state=${currentState}`; tokens.forEach((token) => { - const match = validTokenKeys.filter(t => t.key === token.key)[0]; + const match = gl.FilteredSearchTokenKeys.get().filter(t => t.key === token.key)[0]; let tokenPath = ''; if (token.wildcard && match.conditions) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 new file mode 100644 index 00000000000..8d38a29a354 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -0,0 +1,45 @@ +/* eslint-disable no-param-reassign */ +((global) => { + class FilteredSearchTokenKeys { + static get() { + return [{ + key: 'author', + type: 'string', + param: 'username', + symbol: '@', + }, { + key: 'assignee', + type: 'string', + param: 'username', + symbol: '@', + conditions: [{ + keyword: 'none', + url: 'assignee_id=0', + }], + }, { + key: 'milestone', + type: 'string', + param: 'title', + symbol: '%', + conditions: [{ + keyword: 'none', + url: 'milestone_title=No+Milestone', + }, { + keyword: 'upcoming', + url: 'milestone_title=%23upcoming', + }], + }, { + key: 'label', + type: 'array', + param: 'name[]', + symbol: '~', + conditions: [{ + keyword: 'none', + url: 'label_name[]=No+Label', + }], + }]; + } + } + + global.FilteredSearchTokenKeys = FilteredSearchTokenKeys; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index eab805c4714..b1f37443aa1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -1,33 +1,20 @@ /* eslint-disable no-param-reassign */ ((global) => { class FilteredSearchTokenizer { - constructor(validTokenKeys) { - this.validTokenKeys = validTokenKeys; - this.resetTokens(); - } - - getTokens() { - return this.tokens; - } - - getSearchToken() { - return this.searchToken; - } - - resetTokens() { - this.tokens = []; - this.searchToken = ''; - } - - printTokens() { + // TODO: Remove when going to pro + static printTokens(tokens, searchToken, lastToken) { console.log('tokens:'); - this.tokens.forEach(token => console.log(token)); - console.log(`search: ${this.searchToken}`); + tokens.forEach(token => console.log(token)); + console.log(`search: ${searchToken}`); + console.log('last token:'); + console.log(lastToken); } - processTokens(input) { - // Re-calculate tokens - this.resetTokens(); + static processTokens(input) { + let tokens = []; + let searchToken = ''; + let lastToken = ''; + const validTokenKeys = gl.FilteredSearchTokenKeys.get(); const inputs = input.split(' '); let searchTerms = ''; @@ -36,16 +23,17 @@ inputs.forEach((i) => { if (incompleteToken) { - const prevToken = this.tokens.last(); + const prevToken = tokens.last(); prevToken.value += ` ${i}`; // Remove last quotation const lastQuotationRegex = new RegExp(lastQuotation, 'g'); prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); - this.tokens[this.tokens.length - 1] = prevToken; + tokens[tokens.length - 1] = prevToken; // Check to see if this quotation completes the token value if (i.indexOf(lastQuotation)) { + lastToken = tokens.last(); incompleteToken = !incompleteToken; } @@ -59,8 +47,8 @@ const tokenValue = i.slice(colonIndex + 1); const tokenSymbol = tokenValue[0]; console.log(tokenSymbol) - const keyMatch = this.validTokenKeys.filter(v => v.key === tokenKey)[0]; - const symbolMatch = this.validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; + const keyMatch = validTokenKeys.filter(v => v.key === tokenKey)[0]; + const symbolMatch = validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; const doubleQuoteIndex = tokenValue.indexOf('"'); const singleQuoteIndex = tokenValue.indexOf('\''); @@ -81,11 +69,12 @@ } if (keyMatch && tokenValue.length > 0) { - this.tokens.push({ + tokens.push({ key: keyMatch.key, value: tokenValue, wildcard: symbolMatch ? false : true, }); + lastToken = tokens.last(); return; } @@ -93,10 +82,19 @@ // Add space for next term searchTerms += `${i} `; + lastToken = i; }, this); - this.searchToken = searchTerms.trim(); - this.printTokens(); + searchToken = searchTerms.trim(); + + // TODO: Remove when going to PRO + gl.FilteredSearchTokenizer.printTokens(tokens, searchToken, lastToken); + + return { + tokens, + searchToken, + lastToken, + }; } } -- cgit v1.2.3 From 3492ff6784ffdd72db2863aa982426b29245ed69 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 30 Nov 2016 12:32:10 -0600 Subject: Add static methods for dropdowns to interface with --- .../filtered_search/filtered_search_manager.js.es6 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 3899181a352..09a7779635f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -84,6 +84,21 @@ loadSearchParamsFromURL(); } + static fillInWord(word) { + const originalValue = document.querySelector('.filtered-search').value; + document.querySelector('.filtered-search').value = `${originalValue} ${word.trim()}`; + } + + static loadDropdown(dropdownName) { + dropdownName = dropdownName.toLowerCase(); + + const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; + + if (match) { + console.log(`🦄 load ${match.key} dropdown`); + } + } + bindEvents() { const filteredSearchInput = document.querySelector('.filtered-search'); -- cgit v1.2.3 From 44187782bfc7944b535e3feda05557831518806b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 30 Nov 2016 12:48:54 -0600 Subject: Add type button for accessibility --- app/views/shared/issuable/_search_bar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 5e759301a04..4c27c835bee 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -14,7 +14,7 @@ .filtered-search-input-container %input.form-control.filtered-search{ placeholder: 'Search or filter results...' } = icon('filter') - %button.clear-search.hidden + %button.clear-search.hidden{ type: 'button' } = icon('times') .pull-right - if boards_page -- cgit v1.2.3 From 64d46a3e80001c2dc13f6fd04e2abac40ee9d093 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 30 Nov 2016 15:18:17 -0600 Subject: Add logic for dynamically selecting which dropdown to load [skip ci] --- .../filtered_search/filtered_search_manager.js.es6 | 55 ++++++++++++++++++---- .../filtered_search/filtered_search_tokenizer.es6 | 35 ++++++++++---- app/views/shared/issuable/_search_bar.html.haml | 2 +- 3 files changed, 72 insertions(+), 20 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 09a7779635f..8903f382c18 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -77,42 +77,77 @@ } } + let dropdownHint; + class FilteredSearchManager { constructor() { this.tokenizer = gl.FilteredSearchTokenizer; this.bindEvents(); loadSearchParamsFromURL(); + this.setDropdown(); } - static fillInWord(word) { - const originalValue = document.querySelector('.filtered-search').value; - document.querySelector('.filtered-search').value = `${originalValue} ${word.trim()}`; + static addWordToInput(word, addSpace) { + const hasExistingValue = document.querySelector('.filtered-search').value.length !== 0; + document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word; } - static loadDropdown(dropdownName) { + loadDropdown(dropdownName = '') { dropdownName = dropdownName.toLowerCase(); const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; - if (match) { + if (match && this.currentDropdown !== match.key) { console.log(`🦄 load ${match.key} dropdown`); + this.currentDropdown = match.key; + } else if (!match && this.currentDropdown !== 'hint') { + console.log('🦄 load hint dropdown'); + this.currentDropdown = 'hint'; + + if (!dropdownHint) { + dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search')) + } + + dropdownHint.render(); + } + } + + setDropdown() { + const { lastToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); + + if (typeof lastToken === 'string') { + // Token is not fully initialized yet + // because it has no value + // Eg. token = 'label:' + const { tokenKey } = this.tokenizer.parseToken(lastToken); + this.loadDropdown(tokenKey); + } else if (lastToken.hasOwnProperty('key')) { + // Token has been initialized into an object + // because it has a value + this.loadDropdown(lastToken.key); + } else { + this.loadDropdown('hint'); } } bindEvents() { const filteredSearchInput = document.querySelector('.filtered-search'); - filteredSearchInput.addEventListener('input', this.processInput.bind(this)); + filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); filteredSearchInput.addEventListener('input', toggleClearSearchButton); filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); - document.querySelector('.clear-search').addEventListener('click', clearSearch); } - // TODO: This is only used for testing, remove when going to PRO - processInput(e) { + checkDropdownToken(e) { const input = e.target.value; - this.tokenizer.processTokens(input); + const { lastToken } = this.tokenizer.processTokens(input); + + // Check for dropdown token + if (lastToken[lastToken.length - 1] === ':') { + const token = lastToken.slice(0, -1); + + } } checkForEnter(e) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index b1f37443aa1..b686a43cf32 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -3,11 +3,30 @@ class FilteredSearchTokenizer { // TODO: Remove when going to pro static printTokens(tokens, searchToken, lastToken) { - console.log('tokens:'); - tokens.forEach(token => console.log(token)); - console.log(`search: ${searchToken}`); - console.log('last token:'); - console.log(lastToken); + // console.log('tokens:'); + // tokens.forEach(token => console.log(token)); + // console.log(`search: ${searchToken}`); + // console.log('last token:'); + // console.log(lastToken); + } + + static parseToken(input) { + const colonIndex = input.indexOf(':'); + let tokenKey; + let tokenValue; + let tokenSymbol; + + if (colonIndex !== -1) { + tokenKey = input.slice(0, colonIndex).toLowerCase(); + tokenValue = input.slice(colonIndex + 1); + tokenSymbol = tokenValue[0]; + } + + return { + tokenKey, + tokenValue, + tokenSymbol, + } } static processTokens(input) { @@ -43,10 +62,8 @@ const colonIndex = i.indexOf(':'); if (colonIndex !== -1) { - const tokenKey = i.slice(0, colonIndex).toLowerCase(); - const tokenValue = i.slice(colonIndex + 1); - const tokenSymbol = tokenValue[0]; - console.log(tokenSymbol) + const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i); + const keyMatch = validTokenKeys.filter(v => v.key === tokenKey)[0]; const symbolMatch = validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 4c27c835bee..a45af053f5c 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -12,7 +12,7 @@ class: "check_all_issues left" .issues-other-filters.filtered-search-container .filtered-search-input-container - %input.form-control.filtered-search{ placeholder: 'Search or filter results...' } + %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search' } = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') -- cgit v1.2.3 From 3c0755809f82b4eed5913f1994f57ccffffb4686 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 30 Nov 2016 15:25:10 -0600 Subject: Add dropdown hint --- .../filtered_search/dropdown_hint.js.es6 | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/assets/javascripts/filtered_search/dropdown_hint.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 new file mode 100644 index 00000000000..ebbd43ad8e0 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -0,0 +1,106 @@ +/* eslint-disable no-param-reassign */ +((global) => { + const dropdownData = [{ + icon: 'fa-search', + hint: 'Keep typing and press Enter', + tag: '', + },{ + icon: 'fa-pencil', + hint: 'author:', + tag: '<author>' + },{ + icon: 'fa-user', + hint: 'assignee:', + tag: '<assignee>', + },{ + icon: 'fa-clock-o', + hint: 'milestone:', + tag: '<milestone>', + },{ + icon: 'fa-tag', + hint: 'label:', + tag: '<label>', + }]; + + class DropdownHint { + constructor(dropdown, input) { + this.input = input; + this.dropdown = dropdown; + this.bindEvents(); + } + + bindEvents() { + this.dropdown.addEventListener('click.dl', this.itemClicked.bind(this)); + } + + unbindEvents() { + this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this)); + } + + // cleanup() { + // this.unbindEvents(); + // droplab.setConfig({'filtered-search': {}}); + // droplab.setData('filtered-search', []); + // this.dropdown.style.display = 'hidden'; + // } + + getSelectedText(selectedToken) { + // TODO: Get last word from FilteredSearchTokenizer + const lastWord = this.input.value.split(' ').last(); + const lastWordIndex = selectedToken.indexOf(lastWord); + + return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); + } + + itemClicked(e) { + const token = e.detail.selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = e.detail.selected.querySelector('.js-filter-tag').innerText.trim(); + + if (tag.length) { + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); + } + + this.input.focus(); + this.dismissDropdown(); + + // Propogate input change to FilteredSearchManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new Event('input')); + } + + dismissDropdown() { + this.input.removeAttribute('data-dropdown-trigger'); + droplab.setConfig({'filtered-search': {}}); + droplab.setData('filtered-search', []); + this.unbindEvents(); + } + + setAsDropdown() { + this.input.setAttribute('data-dropdown-trigger', '#js-dropdown-hint'); + // const hookId = 'filtered-search'; + // const listId = 'js-dropdown-hint'; + // const hook = droplab.hooks.filter((h) => { + // return h.id === hookId; + // })[0]; + + // if (hook.list.list.id !== listId) { + // droplab.changeHookList(hookId, `#${listId}`); + // } + } + + render() { + console.log('render dropdown hint'); + this.setAsDropdown(); + + droplab.setConfig({ + 'filtered-search': { + text: 'hint' + } + }); + + droplab.setData('filtered-search', dropdownData); + } + } + + global.DropdownHint = DropdownHint; +})(window.gl || (window.gl = {})); -- cgit v1.2.3 From a1ca5c76ab44e306fb4fb4adcfe5ea2214bd5abc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 2 Dec 2016 15:02:54 -0600 Subject: Add droplab updates --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/droplab/droplab.js | 98 +++++++++++++++++++----- app/assets/javascripts/droplab/droplab_filter.js | 12 ++- 3 files changed, 86 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e43afbb4cc9..f0615481ed2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -58,6 +58,7 @@ /*= require_directory ./extensions */ /*= require_directory ./lib/utils */ /*= require_directory ./u2f */ +/*= require_directory ./droplab */ /*= require_directory . */ /*= require fuzzaldrin-plus */ /*= require es6-promise.auto */ diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 18ca8be7203..56582e71b61 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -38,6 +38,7 @@ var DropDown = function(list, trigger) { this.items = []; this.getItems(); this.addEvents(); + this.initialState = list.innerHTML; }; Object.assign(DropDown.prototype, { @@ -50,7 +51,8 @@ Object.assign(DropDown.prototype, { var self = this; // event delegation. this.list.addEventListener('click', function(e) { - if(e.target.tagName === 'A') { + if(e.target.tagName === 'A' || e.target.tagName === 'button') { + e.preventDefault(); self.hide(); var listEvent = new CustomEvent('click.dl', { detail: { @@ -72,6 +74,11 @@ Object.assign(DropDown.prototype, { } }, + setData: function(data) { + this.data = data; + this.render(data); + }, + addData: function(data) { this.data = (this.data || []).concat(data); this.render(data); @@ -155,8 +162,17 @@ require('./window')(function(w){ addData: function () { var args = [].slice.apply(arguments); + this.applyArgs(args, '_addData'); + }, + + setData: function() { + var args = [].slice.apply(arguments); + this.applyArgs(args, '_setData'); + }, + + applyArgs: function(args, methodName) { if(this.ready) { - this._addData.apply(this, args); + this[methodName].apply(this, args); } else { this.queuedData = this.queuedData || []; this.queuedData.push(args); @@ -164,10 +180,18 @@ require('./window')(function(w){ }, _addData: function(trigger, data) { + this._processData(trigger, data, 'addData'); + }, + + _setData: function(trigger, data) { + this._processData(trigger, data, 'setData'); + }, + + _processData: function(trigger, data, methodName) { this.hooks.forEach(function(hook) { if(hook.trigger.dataset.hasOwnProperty('id')) { if(hook.trigger.dataset.id === trigger) { - hook.list.addData(data); + hook.list[methodName](data); } } }); @@ -189,21 +213,48 @@ require('./window')(function(w){ }); }, - addHook: function(hook) { + changeHookList: function(trigger, list) { + trigger = document.querySelector('[data-id="'+trigger+'"]'); + list = document.querySelector(list); + this.hooks.every(function(hook, i) { + if(hook.trigger === trigger) { + // Restore initial State + hook.list.list.innerHTML = hook.list.initialState; + hook.list.hide(); + hook.trigger.removeEventListener('mousedown', hook.events.mousedown); + hook.trigger.removeEventListener('input', hook.events.input); + hook.trigger.removeEventListener('keyup', hook.events.keyup); + hook.trigger.removeEventListener('keydown', hook.events.keydown); + this.hooks.splice(i, 1); + this.addHook(trigger, list); + return false; + } + return true + }.bind(this)); + }, + + addHook: function(hook, list) { if(!(hook instanceof HTMLElement) && typeof hook === 'string'){ hook = document.querySelector(hook); } - var list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]); - if(hook.tagName === 'A' || hook.tagName === 'BUTTON') { - this.hooks.push(new HookButton(hook, list)); - } else if(hook.tagName === 'INPUT') { - this.hooks.push(new HookInput(hook, list)); + if(!list){ + list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]); + } + + if(hook) { + if(hook.tagName === 'A' || hook.tagName === 'BUTTON') { + this.hooks.push(new HookButton(hook, list)); + } else if(hook.tagName === 'INPUT') { + this.hooks.push(new HookInput(hook, list)); + } } return this; }, addHooks: function(hooks) { - hooks.forEach(this.addHook.bind(this)); + hooks.forEach(function(hook) { + this.addHook(hook, null); + }.bind(this)); return this; }, @@ -302,7 +353,8 @@ var HookInput = function(trigger, list) { Object.assign(HookInput.prototype, { addEvents: function(){ var self = this; - this.trigger.addEventListener('mousedown', function(e){ + + function mousedown(e) { var mouseEvent = new CustomEvent('mousedown.dl', { detail: { hook: self, @@ -312,9 +364,9 @@ Object.assign(HookInput.prototype, { cancelable: true }); e.target.dispatchEvent(mouseEvent); - }); + } - this.trigger.addEventListener('input', function(e){ + function input(e) { var inputEvent = new CustomEvent('input.dl', { detail: { hook: self, @@ -325,15 +377,15 @@ Object.assign(HookInput.prototype, { }); e.target.dispatchEvent(inputEvent); self.list.show(); - }); + } - this.trigger.addEventListener('keyup', function(e){ + function keyup(e) { keyEvent(e, 'keyup.dl'); - }); + } - this.trigger.addEventListener('keydown', function(e){ + function keydown(e) { keyEvent(e, 'keydown.dl'); - }); + } function keyEvent(e, keyEventName){ var keyEvent = new CustomEvent(keyEventName, { @@ -349,6 +401,16 @@ Object.assign(HookInput.prototype, { e.target.dispatchEvent(keyEvent); self.list.show(); } + + this.events = this.events || {}; + this.events.mousedown = mousedown; + this.events.input = input; + this.events.keyup = keyup; + this.events.keydown = keydown; + this.trigger.addEventListener('mousedown', mousedown); + this.trigger.addEventListener('input', input); + this.trigger.addEventListener('keyup', keyup); + this.trigger.addEventListener('keydown', keydown); }, }); diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 4a7ae0cbdc1..88e69c02422 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -2,18 +2,17 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Date: Fri, 2 Dec 2016 15:04:10 -0600 Subject: Add dropdowns for assignee --- .../filtered_search/dropdown_assignee.js.es6 | 21 ++++++ .../filtered_search/dropdown_hint.js.es6 | 69 +++---------------- .../filtered_search_dropdown.js.es6 | 78 ++++++++++++++++++++++ .../filtered_search/filtered_search_manager.js.es6 | 22 +++++- app/views/shared/issuable/_search_bar.html.haml | 17 +++++ 5 files changed, 147 insertions(+), 60 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 create mode 100644 app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 new file mode 100644 index 00000000000..9e4d1018ac3 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -0,0 +1,21 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownAssignee extends gl.FilteredSearchDropdown { + constructor(dropdown, input) { + super(dropdown, input); + this.listId = 'js-dropdown-assignee'; + } + + itemClicked(e) { + console.log('assignee clicked'); + } + + renderContent() { + droplab.addData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); + } + } + + global.DropdownAssignee = DropdownAssignee; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index ebbd43ad8e0..0593561c8a1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -1,4 +1,6 @@ /* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + ((global) => { const dropdownData = [{ icon: 'fa-search', @@ -22,34 +24,11 @@ tag: '<label>', }]; - class DropdownHint { - constructor(dropdown, input) { - this.input = input; - this.dropdown = dropdown; - this.bindEvents(); - } - - bindEvents() { - this.dropdown.addEventListener('click.dl', this.itemClicked.bind(this)); - } - - unbindEvents() { - this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this)); - } - - // cleanup() { - // this.unbindEvents(); - // droplab.setConfig({'filtered-search': {}}); - // droplab.setData('filtered-search', []); - // this.dropdown.style.display = 'hidden'; - // } - - getSelectedText(selectedToken) { - // TODO: Get last word from FilteredSearchTokenizer - const lastWord = this.input.value.split(' ').last(); - const lastWordIndex = selectedToken.indexOf(lastWord); - - return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); + class DropdownHint extends gl.FilteredSearchDropdown { + constructor(dropdown, input, filterKeyword) { + super(dropdown, input); + this.listId = 'js-dropdown-hint'; + this.filterKeyword = filterKeyword; } itemClicked(e) { @@ -68,37 +47,9 @@ this.input.dispatchEvent(new Event('input')); } - dismissDropdown() { - this.input.removeAttribute('data-dropdown-trigger'); - droplab.setConfig({'filtered-search': {}}); - droplab.setData('filtered-search', []); - this.unbindEvents(); - } - - setAsDropdown() { - this.input.setAttribute('data-dropdown-trigger', '#js-dropdown-hint'); - // const hookId = 'filtered-search'; - // const listId = 'js-dropdown-hint'; - // const hook = droplab.hooks.filter((h) => { - // return h.id === hookId; - // })[0]; - - // if (hook.list.list.id !== listId) { - // droplab.changeHookList(hookId, `#${listId}`); - // } - } - - render() { - console.log('render dropdown hint'); - this.setAsDropdown(); - - droplab.setConfig({ - 'filtered-search': { - text: 'hint' - } - }); - - droplab.setData('filtered-search', dropdownData); + renderContent() { + super.renderContent(); + droplab.setData(this.hookId, dropdownData); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 new file mode 100644 index 00000000000..250d8236ea9 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -0,0 +1,78 @@ +/* eslint-disable no-param-reassign */ +((global) => { + const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; + + class FilteredSearchDropdown { + constructor(dropdown, input) { + this.hookId = 'filtered-search'; + this.input = input; + this.dropdown = dropdown; + this.bindEvents(); + } + + bindEvents() { + this.dropdown.addEventListener('click.dl', this.itemClicked.bind(this)); + } + + unbindEvents() { + this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this)); + } + + getSelectedText(selectedToken) { + // TODO: Get last word from FilteredSearchTokenizer + const lastWord = this.input.value.split(' ').last(); + const lastWordIndex = selectedToken.indexOf(lastWord); + + return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); + } + + itemClicked(e) { + // Overridden by dropdown sub class + } + + getFilterConfig(filterKeyword) { + const config = {}; + const filterConfig = { + text: filterKeyword, + }; + + config[this.hookId] = filterKeyword ? filterConfig : {}; + + return config; + } + + dismissDropdown() { + this.input.removeAttribute(DATA_DROPDOWN_TRIGGER); + droplab.setConfig(this.getFilterConfig()); + droplab.setData(this.hookId, []); + this.unbindEvents(); + } + + setAsDropdown() { + this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.listId}`); + } + + getCurrentHook() { + return droplab.hooks.filter(h => h.id === this.hookId)[0]; + } + + renderContent() { + droplab.setConfig(this.getFilterConfig(this.filterKeyword)); + } + + render() { + this.setAsDropdown(); + + const firstTimeInitialized = this.getCurrentHook() === undefined; + + if (firstTimeInitialized) { + this.renderContent(); + } else if(this.getCurrentHook().list.list.id !== this.listId) { + droplab.changeHookList(this.hookId, `#${this.listId}`); + this.renderContent(); + } + } + } + + global.FilteredSearchDropdown = FilteredSearchDropdown; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 8903f382c18..92f07024354 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -78,6 +78,7 @@ } let dropdownHint; + let dropdownAssignee; class FilteredSearchManager { constructor() { @@ -99,19 +100,38 @@ if (match && this.currentDropdown !== match.key) { console.log(`🦄 load ${match.key} dropdown`); + this.dismissCurrentDropdown(); this.currentDropdown = match.key; + + if (match.key === 'assignee') { + if (!dropdownAssignee) { + + // document.querySelector('.filtered-search').setAttribute('data-dropdown-trigger', '#js-dropdown-assignee'); + dropdownAssignee = new gl.DropdownAssignee(document.querySelector('#js-dropdown-assignee'), document.querySelector('.filtered-search')); + } + + dropdownAssignee.render(); + } + } else if (!match && this.currentDropdown !== 'hint') { console.log('🦄 load hint dropdown'); + this.dismissCurrentDropdown(); this.currentDropdown = 'hint'; if (!dropdownHint) { - dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search')) + dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search'), 'hint'); } dropdownHint.render(); } } + dismissCurrentDropdown() { + if (this.currentDropdown === 'hint') { + dropdownHint.dismissDropdown(); + } + } + setDropdown() { const { lastToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index a45af053f5c..04000a18dce 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -16,6 +16,23 @@ = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') + %ul#js-dropdown-hint.dropdown-menu{ 'data-dynamic' => true } + %li + %button.btn.btn-link + %i.fa{ 'class': '{{icon}}'} + %span.js-filter-hint + {{hint}} + %span.js-filter-tag + {{tag}} + #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } + %ul{ 'data-dynamic' => true } + %li + %button.btn.btn-link + %img.avatar.avatar-inline{ 'src': '{{avatar_url}}', width: '30' } + %strong + {{name}} + %span + {{username}} .pull-right - if boards_page #js-boards-seach.issue-boards-search -- cgit v1.2.3 From 9081d3efeec5b22fd92c76172ae92dad3cc94c58 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 2 Dec 2016 16:20:01 -0600 Subject: Update droplab --- app/assets/javascripts/droplab/droplab.js | 19 +++++++++++++++++-- app/assets/javascripts/droplab/droplab_ajax.js | 11 +++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 56582e71b61..aff47aa23cf 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -51,13 +51,15 @@ Object.assign(DropDown.prototype, { var self = this; // event delegation. this.list.addEventListener('click', function(e) { - if(e.target.tagName === 'A' || e.target.tagName === 'button') { + // climb up the tree to find the LI + var selected = utils.closest(e.target, 'LI'); + if(selected) { e.preventDefault(); self.hide(); var listEvent = new CustomEvent('click.dl', { detail: { list: self, - selected: e.target, + selected: selected, data: e.target.dataset, }, }); @@ -102,6 +104,15 @@ Object.assign(DropDown.prototype, { var html = utils.t(sampleItem.outerHTML, dat); var template = document.createElement('template'); template.innerHTML = html; + + // Help set the image src template + var imageTags = template.content.querySelectorAll('img[data-src]'); + for(var i = 0; i < imageTags.length; i++) { + var imageTag = imagetags[i]; + imageTag.src = imageTag.getAttribute('data-src'); + imageTag.removeAttribute('data-src'); + } + if(dat.hasOwnProperty('droplab_hidden') && dat.droplab_hidden){ template.content.firstChild.style.display = 'none' }else{ @@ -115,6 +126,9 @@ Object.assign(DropDown.prototype, { } else { this.list.innerHTML = newChildren.join(''); } + + // Show dropdown if there is data + data !== [] ? this.show() : this.hide(); }, show: function() { @@ -221,6 +235,7 @@ require('./window')(function(w){ // Restore initial State hook.list.list.innerHTML = hook.list.initialState; hook.list.hide(); + hook.trigger.removeEventListener('mousedown', hook.events.mousedown); hook.trigger.removeEventListener('input', hook.events.input); hook.trigger.removeEventListener('keyup', hook.events.keyup); diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index 23e43b352d6..2dff5b83fae 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -3,6 +3,7 @@ /* global droplab */ droplab.plugin(function init(DropLab) { var _addData = DropLab.prototype.addData; + var _setData = DropLab.prototype.setData; var _loadUrlData = function(url) { return new Promise(function(resolve, reject) { @@ -24,10 +25,16 @@ droplab.plugin(function init(DropLab) { Object.assign(DropLab.prototype, { addData: function(trigger, data) { + this.processData(trigger, data, _addData); + }, + setData: function(trigger, data) { + this.processData(trigger, data, _setData); + }, + processData: function(trigger, data, methodName) { var _this = this; if('string' === typeof data) { _loadUrlData(data).then(function(d) { - _addData.call(_this, trigger, d); + methodName.call(_this, trigger, d); }).catch(function(e) { if(e.message) console.error(e.message, e.stack); // eslint-disable-line no-console @@ -35,7 +42,7 @@ droplab.plugin(function init(DropLab) { console.error(e); // eslint-disable-line no-console }) } else { - _addData.apply(this, arguments); + methodName.apply(this, arguments); } }, }); -- cgit v1.2.3 From ce1247727d2a9f24994f602debe95fd2ebff90db Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 2 Dec 2016 16:20:21 -0600 Subject: Fix rendering of assignee dropdown after clicking hint dropdown --- app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 | 3 ++- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index 9e4d1018ac3..e3cbb4cb3a0 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -13,7 +13,8 @@ } renderContent() { - droplab.addData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); + super.renderContent(); + droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 92f07024354..fc7bfe121fb 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -121,7 +121,6 @@ if (!dropdownHint) { dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search'), 'hint'); } - dropdownHint.render(); } } -- cgit v1.2.3 From 78b9e7c6b500b87dd2ebac45331a012eb0cf6ca4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 2 Dec 2016 16:30:19 -0600 Subject: Add author dropdown --- .../filtered_search/dropdown_author.js.es6 | 22 ++++++++++++++++++++++ .../filtered_search/filtered_search_manager.js.es6 | 13 +++++++++---- app/views/shared/issuable/_search_bar.html.haml | 9 +++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/dropdown_author.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 new file mode 100644 index 00000000000..e16b313b743 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -0,0 +1,22 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownAuthor extends gl.FilteredSearchDropdown { + constructor(dropdown, input) { + super(dropdown, input); + this.listId = 'js-dropdown-author'; + } + + itemClicked(e) { + console.log('author clicked'); + } + + renderContent() { + super.renderContent(); + droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); + } + } + + global.DropdownAuthor = DropdownAuthor; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index fc7bfe121fb..237f4fc3fff 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -78,6 +78,7 @@ } let dropdownHint; + let dropdownAuthor; let dropdownAssignee; class FilteredSearchManager { @@ -103,10 +104,14 @@ this.dismissCurrentDropdown(); this.currentDropdown = match.key; - if (match.key === 'assignee') { - if (!dropdownAssignee) { + if (match.key === 'author') { + if (!dropdownAuthor) { + dropdownAuthor = new gl.DropdownAuthor(document.querySelector('#js-dropdown-author'), document.querySelector('.filtered-search')); + } - // document.querySelector('.filtered-search').setAttribute('data-dropdown-trigger', '#js-dropdown-assignee'); + dropdownAuthor.render(); + } else if (match.key === 'assignee') { + if (!dropdownAssignee) { dropdownAssignee = new gl.DropdownAssignee(document.querySelector('#js-dropdown-assignee'), document.querySelector('.filtered-search')); } @@ -119,7 +124,7 @@ this.currentDropdown = 'hint'; if (!dropdownHint) { - dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search'), 'hint'); + dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search'), this.currentDropdown); } dropdownHint.render(); } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 04000a18dce..3801b46a332 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -24,6 +24,15 @@ {{hint}} %span.js-filter-tag {{tag}} + #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } + %ul{ 'data-dynamic' => true } + %li + %button.btn.btn-link + %img.avatar.avatar-inline{ 'src': '{{avatar_url}}', width: '30' } + %strong + {{name}} + %span + {{username}} #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } %li -- cgit v1.2.3 From a510791bb2fb537fd2fbe4a9f6b94e38fe5a6094 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 2 Dec 2016 16:43:15 -0600 Subject: Add label and milestone dropdowns --- .../filtered_search/dropdown_label.js.es6 | 22 ++++++++++++++++++++++ .../filtered_search/dropdown_milestone.js.es6 | 22 ++++++++++++++++++++++ .../filtered_search/filtered_search_manager.js.es6 | 14 ++++++++++++++ app/views/shared/issuable/_search_bar.html.haml | 11 +++++++++++ 4 files changed, 69 insertions(+) create mode 100644 app/assets/javascripts/filtered_search/dropdown_label.js.es6 create mode 100644 app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 new file mode 100644 index 00000000000..9225dca13b0 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -0,0 +1,22 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownLabel extends gl.FilteredSearchDropdown { + constructor(dropdown, input) { + super(dropdown, input); + this.listId = 'js-dropdown-label'; + } + + itemClicked(e) { + console.log('label clicked'); + } + + renderContent() { + super.renderContent(); + droplab.setData(this.hookId, 'labels.json'); + } + } + + global.DropdownLabel = DropdownLabel; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 new file mode 100644 index 00000000000..ab97d709886 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -0,0 +1,22 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownMilestone extends gl.FilteredSearchDropdown { + constructor(dropdown, input) { + super(dropdown, input); + this.listId = 'js-dropdown-milestone'; + } + + itemClicked(e) { + console.log('milestone clicked'); + } + + renderContent() { + super.renderContent(); + droplab.setData(this.hookId, 'milestones.json'); + } + } + + global.DropdownMilestone = DropdownMilestone; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 237f4fc3fff..f06d5a646cf 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -80,6 +80,8 @@ let dropdownHint; let dropdownAuthor; let dropdownAssignee; + let dropdownMilestone; + let dropdownLabel; class FilteredSearchManager { constructor() { @@ -116,6 +118,18 @@ } dropdownAssignee.render(); + } else if (match.key === 'milestone') { + if (!dropdownMilestone) { + dropdownMilestone = new gl.DropdownMilestone(document.querySelector('#js-dropdown-milestone'), document.querySelector('.filtered-search')); + } + + dropdownMilestone.render(); + } else if (match.key === 'label') { + if (!dropdownLabel) { + dropdownLabel = new gl.DropdownLabel(document.querySelector('#js-dropdown-label'), document.querySelector('.filtered-search')); + } + + dropdownLabel.render(); } } else if (!match && this.currentDropdown !== 'hint') { diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 3801b46a332..cf5b1a52332 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -42,6 +42,17 @@ {{name}} %span {{username}} + #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } + %ul{ 'data-dynamic' => true } + %li + %button.btn.btn-link + {{title}} + #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } + %ul{ 'data-dynamic' => true } + %li + %button.btn.btn-link + %span.dropdown-label-box{ 'style': 'background: {{color}}'} + {{title}} .pull-right - if boards_page #js-boards-seach.issue-boards-search -- cgit v1.2.3 From 2edaabfe669f7e865a56eab321db41fe9cdcad89 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 12:18:02 -0600 Subject: Fix image data-src --- app/assets/javascripts/droplab/droplab.js | 2 +- app/views/shared/issuable/_search_bar.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index aff47aa23cf..0152eef793f 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -108,7 +108,7 @@ Object.assign(DropDown.prototype, { // Help set the image src template var imageTags = template.content.querySelectorAll('img[data-src]'); for(var i = 0; i < imageTags.length; i++) { - var imageTag = imagetags[i]; + var imageTag = imageTags[i]; imageTag.src = imageTag.getAttribute('data-src'); imageTag.removeAttribute('data-src'); } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index cf5b1a52332..c7841486ad1 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -28,7 +28,7 @@ %ul{ 'data-dynamic' => true } %li %button.btn.btn-link - %img.avatar.avatar-inline{ 'src': '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } %strong {{name}} %span @@ -37,7 +37,7 @@ %ul{ 'data-dynamic' => true } %li %button.btn.btn-link - %img.avatar.avatar-inline{ 'src': '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } %strong {{name}} %span -- cgit v1.2.3 From f19503b008e962a6eaf16ce4fa18bdb89ceb7442 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 12:26:36 -0600 Subject: Style hint dropdown --- .../filtered_search/dropdown_hint.js.es6 | 4 ++-- app/assets/stylesheets/framework/filters.scss | 22 ++++++++++++++++++++++ app/views/shared/issuable/_search_bar.html.haml | 6 +++--- 3 files changed, 27 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 0593561c8a1..dc28b97fea9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -32,8 +32,8 @@ } itemClicked(e) { - const token = e.detail.selected.querySelector('.js-filter-hint').innerText.trim(); - const tag = e.detail.selected.querySelector('.js-filter-tag').innerText.trim(); + const token = e.detail.selected.querySelector('.dropdown-filter-hint').innerText.trim(); + const tag = e.detail.selected.querySelector('.dropdown-filter-tag').innerText.trim(); if (tag.length) { gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index c679a3833e9..71b33646185 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -68,3 +68,25 @@ } } } + +.dropdown-menu .filter-dropdown { + padding: 0; +} + +.filter-dropdown { + .btn { + border: none; + width: 100%; + text-align: left; + padding: 8px 16px; + + &:hover { + text-decoration: none; + } + } + + .dropdown-filter-tag { + font-size: 14px; + font-weight: 400; + } +} diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index c7841486ad1..6df35b78194 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -17,12 +17,12 @@ %button.clear-search.hidden{ type: 'button' } = icon('times') %ul#js-dropdown-hint.dropdown-menu{ 'data-dynamic' => true } - %li + %li.filter-dropdown %button.btn.btn-link %i.fa{ 'class': '{{icon}}'} - %span.js-filter-hint + %span.dropdown-filter-hint {{hint}} - %span.js-filter-tag + %span.dropdown-filter-tag {{tag}} #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } -- cgit v1.2.3 From fdd1bac91a0554df9a3a25b500877d376fd6b2a0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 12:35:28 -0600 Subject: Style author dropdown --- .../filtered_search/dropdown_hint.js.es6 | 4 ++-- app/assets/stylesheets/framework/filters.scss | 11 ++++++++++- app/views/shared/issuable/_search_bar.html.haml | 23 +++++++++++----------- 3 files changed, 24 insertions(+), 14 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index dc28b97fea9..0593561c8a1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -32,8 +32,8 @@ } itemClicked(e) { - const token = e.detail.selected.querySelector('.dropdown-filter-hint').innerText.trim(); - const tag = e.detail.selected.querySelector('.dropdown-filter-tag').innerText.trim(); + const token = e.detail.selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = e.detail.selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 71b33646185..767803ac1d0 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -85,8 +85,17 @@ } } - .dropdown-filter-tag { + .dropdown-light-content { font-size: 14px; font-weight: 400; } + + .dropdown-user { + display: flex; + } + + .dropdown-user-details { + display: flex; + flex-direction: column; + } } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 6df35b78194..b83ea6c60d9 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -20,22 +20,23 @@ %li.filter-dropdown %button.btn.btn-link %i.fa{ 'class': '{{icon}}'} - %span.dropdown-filter-hint + %span.js-filter-hint {{hint}} - %span.dropdown-filter-tag + %span.js-filter-tag.dropdown-light-content {{tag}} #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } - %li - %button.btn.btn-link + %li.filter-dropdown + %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } - %strong - {{name}} - %span - {{username}} + .dropdown-user-details + %span + {{name}} + %span.dropdown-light-content + @{{username}} #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } - %li + %li.filter-dropdown %button.btn.btn-link %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } %strong @@ -44,12 +45,12 @@ {{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } - %li + %li.filter-dropdown %button.btn.btn-link {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dynamic' => true } - %li + %li.filter-dropdown %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} {{title}} -- cgit v1.2.3 From 7cf322f1d667b6e0d1c74e58a89aa3dbd437a850 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:00:18 -0600 Subject: Add blue hover for dropdowns --- app/assets/stylesheets/framework/filters.scss | 2 ++ app/assets/stylesheets/framework/variables.scss | 5 +++++ 2 files changed, 7 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 767803ac1d0..4fa826c1b76 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -81,6 +81,8 @@ padding: 8px 16px; &:hover { + background-color: $dropdown-hover-color; + color: white; text-decoration: none; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3e52c482ece..f3cb3d33d99 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -263,6 +263,11 @@ $dropdown-chevron-size: 10px; $dropdown-toggle-active-border-color: darken($border-color, 14%); +/* +* Filtered Search +*/ +$dropdown-hover-color: #3B86FF; + /* * Buttons */ -- cgit v1.2.3 From eb55cf5007f478a1fdffe029110c27c869f74356 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:00:54 -0600 Subject: Add static dropdown list items --- app/views/shared/issuable/_search_bar.html.haml | 26 ++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index b83ea6c60d9..a47140ed0aa 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -35,20 +35,36 @@ %span.dropdown-light-content @{{username}} #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } - %ul{ 'data-dynamic' => true } + %ul %li.filter-dropdown %button.btn.btn-link + No assignee + %li.divider + %ul{ 'data-dynamic' => true } + %li.filter-dropdown + %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } - %strong - {{name}} - %span - {{username}} + .dropdown-user-details + %span + {{name}} + %span.dropdown-light-content + @{{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } + %ul + %li.filter-dropdown + %button.btn.btn-link + No milestone + %li.divider %ul{ 'data-dynamic' => true } %li.filter-dropdown %button.btn.btn-link {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } + %ul + %li.filter-dropdown + %button.btn.btn-link + No label + %li.divider %ul{ 'data-dynamic' => true } %li.filter-dropdown %button.btn.btn-link -- cgit v1.2.3 From 9f1c19ce84046de2e85eff1aef7f693a2925bf11 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:09:29 -0600 Subject: Style label dropdowns --- app/assets/stylesheets/framework/filters.scss | 2 ++ app/views/shared/issuable/_search_bar.html.haml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 4fa826c1b76..c4b4a56a8b5 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -79,6 +79,8 @@ width: 100%; text-align: left; padding: 8px 16px; + text-overflow: ellipsis; + overflow-y: hidden; &:hover { background-color: $dropdown-hover-color; diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index a47140ed0aa..f076c9c1a75 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -69,7 +69,7 @@ %li.filter-dropdown %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} - {{title}} + {{title}} .pull-right - if boards_page #js-boards-seach.issue-boards-search -- cgit v1.2.3 From 52fd1b08caad773b332216c117bdecc25cbd6256 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:17:43 -0600 Subject: Add vertical scrolling for dropdowns --- app/assets/stylesheets/framework/filters.scss | 9 +++++++++ app/views/shared/issuable/_search_bar.html.haml | 26 ++++++++++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index c4b4a56a8b5..2efdb537cb3 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -74,6 +74,11 @@ } .filter-dropdown { + max-height: 215px; + overflow-x: scroll; +} + +.filter-dropdown-item { .btn { border: none; width: 100%; @@ -103,3 +108,7 @@ flex-direction: column; } } + +.hint-dropdown { + width: 250px; +} diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index f076c9c1a75..8dda6e99d2d 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -16,8 +16,8 @@ = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') - %ul#js-dropdown-hint.dropdown-menu{ 'data-dynamic' => true } - %li.filter-dropdown + %ul#js-dropdown-hint.dropdown-menu.hint-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item %button.btn.btn-link %i.fa{ 'class': '{{icon}}'} %span.js-filter-hint @@ -25,8 +25,8 @@ %span.js-filter-tag.dropdown-light-content {{tag}} #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } - %ul{ 'data-dynamic' => true } - %li.filter-dropdown + %ul.filter-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } .dropdown-user-details @@ -36,12 +36,12 @@ @{{username}} #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown + %li.filter-dropdown-item %button.btn.btn-link No assignee %li.divider - %ul{ 'data-dynamic' => true } - %li.filter-dropdown + %ul.filter-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } .dropdown-user-details @@ -51,22 +51,22 @@ @{{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown + %li.filter-dropdown-item %button.btn.btn-link No milestone %li.divider - %ul{ 'data-dynamic' => true } - %li.filter-dropdown + %ul.filter-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item %button.btn.btn-link {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown + %li.filter-dropdown-item %button.btn.btn-link No label %li.divider - %ul{ 'data-dynamic' => true } - %li.filter-dropdown + %ul.filter-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} {{title}} -- cgit v1.2.3 From c0ec94f5f0e0ba24655ab06f60a8da3ccd2930f8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:26:24 -0600 Subject: Fix css dropdown width --- app/assets/stylesheets/framework/filters.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 2efdb537cb3..1f980c3d618 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -69,7 +69,7 @@ } } -.dropdown-menu .filter-dropdown { +.dropdown-menu .filter-dropdown-item { padding: 0; } -- cgit v1.2.3 From 24042d882b544e8909170bdb49d080c5c9e153fd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:32:44 -0600 Subject: Add white background for dropdown label box color --- app/assets/stylesheets/framework/filters.scss | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 1f980c3d618..bcbf0e868e2 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -91,6 +91,12 @@ background-color: $dropdown-hover-color; color: white; text-decoration: none; + + .dropdown-label-box { + border-color: white; + border-style: solid; + border-width: 2px; + } } } -- cgit v1.2.3 From 0e7b8413329270379b6675d87fb5cc28a5b24a58 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 13:34:20 -0600 Subject: Add focus state style the same as hover state --- app/assets/stylesheets/framework/filters.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index bcbf0e868e2..130d06b601c 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -87,7 +87,8 @@ text-overflow: ellipsis; overflow-y: hidden; - &:hover { + &:hover, + &:focus { background-color: $dropdown-hover-color; color: white; text-decoration: none; -- cgit v1.2.3 From e36d32327185e9e8824b04f0cebcbfbffa42d7e8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 15:12:34 -0600 Subject: Add dropdown offset to match input cursor --- .../filtered_search_dropdown.js.es6 | 4 ++++ .../filtered_search/filtered_search_manager.js.es6 | 24 +++++++++++++++++----- app/assets/javascripts/lib/utils/text_utility.js | 16 +++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 250d8236ea9..251162f3fb1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -52,6 +52,10 @@ this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.listId}`); } + setOffset(offset = 0) { + this.dropdown.style.left = `${offset}px`; + } + getCurrentHook() { return droplab.hooks.filter(h => h.id === this.hookId)[0]; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index f06d5a646cf..c80c60d6d6e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -99,47 +99,61 @@ loadDropdown(dropdownName = '') { dropdownName = dropdownName.toLowerCase(); + const filterIconPadding = 27; const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; + const filteredSearch = document.querySelector('.filtered-search'); if (match && this.currentDropdown !== match.key) { console.log(`🦄 load ${match.key} dropdown`); + + const dynamicDropdownPadding = 12; + const dropdownOffset = gl.text.getTextWidth(filteredSearch.value) + filterIconPadding + dynamicDropdownPadding; + this.dismissCurrentDropdown(); this.currentDropdown = match.key; if (match.key === 'author') { if (!dropdownAuthor) { - dropdownAuthor = new gl.DropdownAuthor(document.querySelector('#js-dropdown-author'), document.querySelector('.filtered-search')); + dropdownAuthor = new gl.DropdownAuthor(document.querySelector('#js-dropdown-author'), filteredSearch); } + dropdownAuthor.setOffset(dropdownOffset); dropdownAuthor.render(); } else if (match.key === 'assignee') { if (!dropdownAssignee) { - dropdownAssignee = new gl.DropdownAssignee(document.querySelector('#js-dropdown-assignee'), document.querySelector('.filtered-search')); + dropdownAssignee = new gl.DropdownAssignee(document.querySelector('#js-dropdown-assignee'), filteredSearch); } + dropdownAssignee.setOffset(dropdownOffset); dropdownAssignee.render(); } else if (match.key === 'milestone') { if (!dropdownMilestone) { - dropdownMilestone = new gl.DropdownMilestone(document.querySelector('#js-dropdown-milestone'), document.querySelector('.filtered-search')); + dropdownMilestone = new gl.DropdownMilestone(document.querySelector('#js-dropdown-milestone'), filteredSearch); } + dropdownMilestone.setOffset(dropdownOffset); dropdownMilestone.render(); } else if (match.key === 'label') { if (!dropdownLabel) { - dropdownLabel = new gl.DropdownLabel(document.querySelector('#js-dropdown-label'), document.querySelector('.filtered-search')); + dropdownLabel = new gl.DropdownLabel(document.querySelector('#js-dropdown-label'), filteredSearch); } + dropdownLabel.setOffset(dropdownOffset); dropdownLabel.render(); } } else if (!match && this.currentDropdown !== 'hint') { console.log('🦄 load hint dropdown'); + + const dropdownOffset = gl.text.getTextWidth(filteredSearch.value) + filterIconPadding; + this.dismissCurrentDropdown(); this.currentDropdown = 'hint'; if (!dropdownHint) { - dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), document.querySelector('.filtered-search'), this.currentDropdown); + dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), filteredSearch, this.currentDropdown); } + dropdownHint.setOffset(dropdownOffset); dropdownHint.render(); } } diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 5066e3282d7..e47eccc3a33 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -17,6 +17,22 @@ gl.text.replaceRange = function(s, start, end, substitute) { return s.substring(0, start) + substitute + s.substring(end); }; + gl.text.getTextWidth = function(text, font) { + /** + * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. + * + * @param {String} text The text to be rendered. + * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). + * + * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 + */ + // re-use canvas object for better performance + var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement("canvas")); + var context = canvas.getContext("2d"); + context.font = font; + var metrics = context.measureText(text); + return metrics.width; + }; gl.text.selectedText = function(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); }; -- cgit v1.2.3 From dd90dd0e28839a0e83a2145aa13c6f517efbee89 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 15:40:55 -0600 Subject: Set data_dropdown_trigger to empty instead of removing --- app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 251162f3fb1..0a406bef985 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -42,7 +42,7 @@ } dismissDropdown() { - this.input.removeAttribute(DATA_DROPDOWN_TRIGGER); + this.input.setAttribute(DATA_DROPDOWN_TRIGGER, ''); droplab.setConfig(this.getFilterConfig()); droplab.setData(this.hookId, []); this.unbindEvents(); -- cgit v1.2.3 From d2ecba6edfea8ff1836943b6c683fdb36e4c92a3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 16:04:06 -0600 Subject: Remove bad droplab code --- app/assets/javascripts/droplab/droplab.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 0152eef793f..6befa0976d4 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -126,9 +126,6 @@ Object.assign(DropDown.prototype, { } else { this.list.innerHTML = newChildren.join(''); } - - // Show dropdown if there is data - data !== [] ? this.show() : this.hide(); }, show: function() { -- cgit v1.2.3 From f5719c2c48dd989ed5431a78e95e560f3c1d9335 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 16:05:30 -0600 Subject: Add ability to click on none as an option --- .../filtered_search/dropdown_assignee.js.es6 | 8 +++++++- .../javascripts/filtered_search/dropdown_hint.js.es6 | 5 ----- .../javascripts/filtered_search/dropdown_label.js.es6 | 9 ++++++++- .../filtered_search/dropdown_milestone.js.es6 | 8 +++++++- .../filtered_search/filtered_search_dropdown.js.es6 | 19 ++++++++++++++++++- .../filtered_search/filtered_search_manager.js.es6 | 2 +- app/views/shared/issuable/_search_bar.html.haml | 9 +++++---- 7 files changed, 46 insertions(+), 14 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index e3cbb4cb3a0..fcaacac1b50 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -9,7 +9,13 @@ } itemClicked(e) { - console.log('assignee clicked'); + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + console.log('set value'); + } + + this.dismissDropdown(); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 0593561c8a1..b7161d00eb9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -39,12 +39,7 @@ gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); } - this.input.focus(); this.dismissDropdown(); - - // Propogate input change to FilteredSearchManager - // so that it can determine which dropdowns to open - this.input.dispatchEvent(new Event('input')); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index 9225dca13b0..ef92ecd3bd1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -9,7 +9,14 @@ } itemClicked(e) { - console.log('label clicked'); + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + const labelName = `~${e.detail.selected.querySelector('.label-title').innerText.trim()}`; + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(labelName)); + } + + this.dismissDropdown(); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index ab97d709886..00df89ff063 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -9,7 +9,13 @@ } itemClicked(e) { - console.log('milestone clicked'); + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + console.log('set value'); + } + + this.dismissDropdown(); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 0a406bef985..a345b368238 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -41,13 +41,20 @@ return config; } - dismissDropdown() { + destroy() { this.input.setAttribute(DATA_DROPDOWN_TRIGGER, ''); droplab.setConfig(this.getFilterConfig()); droplab.setData(this.hookId, []); this.unbindEvents(); } + dismissDropdown() { + this.input.focus(); + // Propogate input change to FilteredSearchManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new Event('input')); + } + setAsDropdown() { this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.listId}`); } @@ -56,6 +63,16 @@ this.dropdown.style.left = `${offset}px`; } + setDataValueIfSelected(selected) { + const dataValue = selected.getAttribute('data-value'); + + if (dataValue) { + gl.FilteredSearchManager.addWordToInput(dataValue); + } + + return dataValue !== null; + } + getCurrentHook() { return droplab.hooks.filter(h => h.id === this.hookId)[0]; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c80c60d6d6e..53ab2135a09 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -160,7 +160,7 @@ dismissCurrentDropdown() { if (this.currentDropdown === 'hint') { - dropdownHint.dismissDropdown(); + dropdownHint.destroy(); } } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 8dda6e99d2d..39af0c2c288 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -36,7 +36,7 @@ @{{username}} #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown-item + %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No assignee %li.divider @@ -51,7 +51,7 @@ @{{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown-item + %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No milestone %li.divider @@ -61,7 +61,7 @@ {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } %ul - %li.filter-dropdown-item + %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No label %li.divider @@ -69,7 +69,8 @@ %li.filter-dropdown-item %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} - {{title}} + %span.label-title + {{title}} .pull-right - if boards_page #js-boards-seach.issue-boards-search -- cgit v1.2.3 From 0c1c26c0bcc5c62f5959d7fc1399d44e1a6617cd Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 16:29:55 -0600 Subject: Replace typed token with selected dropdown token --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 53ab2135a09..7e6144b571d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -92,7 +92,14 @@ } static addWordToInput(word, addSpace) { - const hasExistingValue = document.querySelector('.filtered-search').value.length !== 0; + const filteredSearchValue = document.querySelector('.filtered-search').value; + const hasExistingValue = filteredSearchValue.length !== 0; + + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); + if (lastToken.hasOwnProperty('key')) { + document.querySelector('.filtered-search').value = filteredSearchValue.slice(0, -1 * (lastToken.value.length)); + } + document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word; } -- cgit v1.2.3 From 71da8ffaef9220c10d2b95ca0ae06bc08fefa594 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 5 Dec 2016 16:36:05 -0600 Subject: Populate selected item in filtered search input --- app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 | 3 ++- app/assets/javascripts/filtered_search/dropdown_author.js.es6 | 5 ++++- app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index fcaacac1b50..e791de5ad41 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -12,7 +12,8 @@ const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - console.log('set value'); + const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); } this.dismissDropdown(); diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index e16b313b743..75eb1c06fbd 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -9,7 +9,10 @@ } itemClicked(e) { - console.log('author clicked'); + const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); + + this.dismissDropdown(); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 00df89ff063..8c75bd30e97 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -12,7 +12,8 @@ const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - console.log('set value'); + const milestoneName = `%${e.detail.selected.querySelector('.btn-link').innerText.trim()}`; + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(milestoneName)); } this.dismissDropdown(); -- cgit v1.2.3 From bcae21b1badaa3a7aedfc44f67908909b34afb7d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 11:34:00 -0600 Subject: Remove border radius of list item buttons --- app/assets/stylesheets/framework/filters.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 130d06b601c..0882af57482 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -86,6 +86,7 @@ padding: 8px 16px; text-overflow: ellipsis; overflow-y: hidden; + border-radius: 0; &:hover, &:focus { -- cgit v1.2.3 From ab2808531dcd768a03f4a9fd90ec0ca67d013278 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 11:37:54 -0600 Subject: Remove unnecessary dismissCurrentDropdown --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 7 ------- 1 file changed, 7 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 7e6144b571d..c509a3c3b62 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -154,7 +154,6 @@ const dropdownOffset = gl.text.getTextWidth(filteredSearch.value) + filterIconPadding; - this.dismissCurrentDropdown(); this.currentDropdown = 'hint'; if (!dropdownHint) { @@ -165,12 +164,6 @@ } } - dismissCurrentDropdown() { - if (this.currentDropdown === 'hint') { - dropdownHint.destroy(); - } - } - setDropdown() { const { lastToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); -- cgit v1.2.3 From 60c9240bc27ea1c5de1acb2a4cc686d9e2a85555 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 12:46:16 -0600 Subject: Fixed bug where labels with multiple spaces wouldn't get tokenized correctly --- .../javascripts/filtered_search/filtered_search_tokenizer.es6 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index b686a43cf32..17fdfe0f550 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -40,18 +40,21 @@ let lastQuotation = ''; let incompleteToken = false; + // Iterate through each word (broken up by spaces) inputs.forEach((i) => { if (incompleteToken) { + // Continue previous token as it had an escaped + // quote in the beginning const prevToken = tokens.last(); prevToken.value += ` ${i}`; - // Remove last quotation + // Remove last quotation from the value const lastQuotationRegex = new RegExp(lastQuotation, 'g'); prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); tokens[tokens.length - 1] = prevToken; // Check to see if this quotation completes the token value - if (i.indexOf(lastQuotation)) { + if (i.indexOf(lastQuotation) !== -1) { lastToken = tokens.last(); incompleteToken = !incompleteToken; } -- cgit v1.2.3 From da8ab2bc086b6b7c2775d44198a6b3bc04794c3d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 12:52:23 -0600 Subject: Add escape quotations for selected labels from dropdown --- .../javascripts/filtered_search/dropdown_label.js.es6 | 18 ++++++++++++++++-- .../filtered_search/filtered_search_manager.js.es6 | 7 ++++++- 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index ef92ecd3bd1..cd1ccb541e6 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -12,8 +12,22 @@ const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - const labelName = `~${e.detail.selected.querySelector('.label-title').innerText.trim()}`; - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(labelName)); + let labelTitle = e.detail.selected.querySelector('.label-title').innerText.trim(); + + // Encapsulate label with quotes if it has spaces + if (labelTitle.indexOf(' ') !== -1) { + if (labelTitle.indexOf('"') !== -1) { + // Use single quotes if label title contains double quotes + labelTitle = `'${labelTitle}'`; + } else { + // Known side effect: Label's with both single and double quotes + // won't escape properly + labelTitle = `"${labelTitle}"`; + } + } + + const labelName = `~${labelTitle}`; + gl.FilteredSearchManager.addWordToInput(labelName); } this.dismissDropdown(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c509a3c3b62..04374525d4c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -97,7 +97,12 @@ const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); if (lastToken.hasOwnProperty('key')) { - document.querySelector('.filtered-search').value = filteredSearchValue.slice(0, -1 * (lastToken.value.length)); + console.log(lastToken); + // Spaces inside the token means that the token value will be escaped by quotes + const hasQuotes = lastToken.value.indexOf(' ') !== -1; + + const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; + document.querySelector('.filtered-search').value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); } document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word; -- cgit v1.2.3 From 5cd90ef9489797c858bfb7eec7be0f5773d4d417 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 12:57:07 -0600 Subject: Add escaping for milestone values --- .../javascripts/filtered_search/dropdown_label.js.es6 | 17 ++--------------- .../filtered_search/dropdown_milestone.js.es6 | 3 ++- .../filtered_search/filtered_search_dropdown.js.es6 | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index cd1ccb541e6..d4a50422c3b 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -12,21 +12,8 @@ const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - let labelTitle = e.detail.selected.querySelector('.label-title').innerText.trim(); - - // Encapsulate label with quotes if it has spaces - if (labelTitle.indexOf(' ') !== -1) { - if (labelTitle.indexOf('"') !== -1) { - // Use single quotes if label title contains double quotes - labelTitle = `'${labelTitle}'`; - } else { - // Known side effect: Label's with both single and double quotes - // won't escape properly - labelTitle = `"${labelTitle}"`; - } - } - - const labelName = `~${labelTitle}`; + const labelTitle = e.detail.selected.querySelector('.label-title').innerText.trim(); + const labelName = `~${this.getEscapedText(labelTitle)}`; gl.FilteredSearchManager.addWordToInput(labelName); } diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 8c75bd30e97..965a8c8a58d 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -12,7 +12,8 @@ const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - const milestoneName = `%${e.detail.selected.querySelector('.btn-link').innerText.trim()}`; + const milestoneTitle = e.detail.selected.querySelector('.btn-link').innerText.trim(); + const milestoneName = `%${this.getEscapedText(milestoneTitle)}`; gl.FilteredSearchManager.addWordToInput(this.getSelectedText(milestoneName)); } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index a345b368238..cc7f61c23e5 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -18,6 +18,24 @@ this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this)); } + getEscapedText(text) { + let escapedText = text; + + // Encapsulate value with quotes if it has spaces + if (text.indexOf(' ') !== -1) { + if (text.indexOf('"') !== -1) { + // Use single quotes if value contains double quotes + escapedText = `'${text}'`; + } else { + // Known side effect: values's with both single and double quotes + // won't escape properly + escapedText = `"${text}"`; + } + } + + return escapedText; + } + getSelectedText(selectedToken) { // TODO: Get last word from FilteredSearchTokenizer const lastWord = this.input.value.split(' ').last(); -- cgit v1.2.3 From 00ed5aafd227a7d4f9be6448f1bcdacff6ee6dfa Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 15:13:31 -0600 Subject: Make keep typing dropdown item static --- .../filtered_search/dropdown_hint.js.es6 | 4 ---- app/views/shared/issuable/_search_bar.html.haml | 23 ++++++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index b7161d00eb9..b09136586c8 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -3,10 +3,6 @@ ((global) => { const dropdownData = [{ - icon: 'fa-search', - hint: 'Keep typing and press Enter', - tag: '', - },{ icon: 'fa-pencil', hint: 'author:', tag: '<author>' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 39af0c2c288..0a5de59cb63 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -16,14 +16,21 @@ = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') - %ul#js-dropdown-hint.dropdown-menu.hint-dropdown{ 'data-dynamic' => true } - %li.filter-dropdown-item - %button.btn.btn-link - %i.fa{ 'class': '{{icon}}'} - %span.js-filter-hint - {{hint}} - %span.js-filter-tag.dropdown-light-content - {{tag}} + #js-dropdown-hint.dropdown-menu.hint-dropdown + %ul + %li.filter-dropdown-item{ 'data-value': 'none' } + %button.btn.btn-link + = icon('search') + %span + Keep typing and press Enter + %ul.filter-dropdown{ 'data-dynamic' => true } + %li.filter-dropdown-item + %button.btn.btn-link + %i.fa{ 'class': '{{icon}}'} + %span.js-filter-hint + {{hint}} + %span.js-filter-tag.dropdown-light-content + {{tag}} #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } %ul.filter-dropdown{ 'data-dynamic' => true } %li.filter-dropdown-item -- cgit v1.2.3 From d91d586aa60c8194a675e936832667cfe12729be Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 16:12:00 -0600 Subject: Add filter by last token --- app/assets/javascripts/droplab/droplab_filter.js | 13 +++++- .../filtered_search/dropdown_author.js.es6 | 8 ++++ .../filtered_search_dropdown.js.es6 | 13 ++++-- .../filtered_search/filtered_search_tokenizer.es6 | 49 ++++++++++++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js index 88e69c02422..5ae81afaf89 100644 --- a/app/assets/javascripts/droplab/droplab_filter.js +++ b/app/assets/javascripts/droplab/droplab_filter.js @@ -10,13 +10,22 @@ droplab.plugin(function init(DropLab) { var matches = []; // will only work on dynamically set data // and if a config text property is set - if(!data || !config.hasOwnProperty('text')){ + if(!data || (!config.hasOwnProperty('text') && !config.hasOwnProperty('filter'))){ return; } - matches = data.map(function(o){ + + var filterFunction = function(o){ // cheap string search o.droplab_hidden = o[config.text].toLowerCase().indexOf(value) === -1; return o; + }; + + if (config.hasOwnProperty('filter') && config.filter !== undefined) { + filterFunction = config.filter; + } + + matches = data.map(function(o) { + return filterFunction(o, value); }); list.render(matches); } diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 75eb1c06fbd..64c310ba7ad 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -19,6 +19,14 @@ super.renderContent(); droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); } + + filterMethod(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutPrefix = value.slice(1); + + item.droplab_hidden = item['username'].indexOf(valueWithoutPrefix) === -1; + return item; + } } global.DropdownAuthor = DropdownAuthor; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index cc7f61c23e5..bbfe26e6a21 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -50,12 +50,17 @@ getFilterConfig(filterKeyword) { const config = {}; - const filterConfig = { - text: filterKeyword, - }; + const filterConfig = {}; - config[this.hookId] = filterKeyword ? filterConfig : {}; + if (filterKeyword) { + filterConfig.text = filterKeyword; + } + + if (this.filterMethod) { + filterConfig.filter = this.filterMethod; + } + config[this.hookId] = filterConfig; return config; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index 17fdfe0f550..d6df83a3fb9 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -29,6 +29,55 @@ } } + static getLastTokenObject(input) { + const token = FilteredSearchTokenizer.getLastToken(input); + const colonIndex = token.indexOf(':'); + + const key = colonIndex !== -1 ? token.slice(0, colonIndex) : ''; + const value = colonIndex !== -1 ? token.slice(colonIndex) : token; + + return { + key, + value, + } + } + + static getLastToken(input) { + let completeToken = false; + let completeQuotation = true; + let lastQuotation = ''; + let i = input.length; + + const doubleQuote = '"'; + const singleQuote = '\''; + while(!completeToken && i >= 0) { + const isDoubleQuote = input[i] === doubleQuote; + const isSingleQuote = input[i] === singleQuote; + + // If the second quotation is found + if ((lastQuotation === doubleQuote && input[i] === doubleQuote) || + (lastQuotation === singleQuote && input[i] === singleQuote)) { + completeQuotation = true; + } + + // Save the first quotation + if ((input[i] === doubleQuote && lastQuotation === '') || + (input[i] === singleQuote && lastQuotation === '')) { + lastQuotation = input[i]; + completeQuotation = false; + } + + if (completeQuotation && input[i] === ' ') { + completeToken = true; + } else { + i--; + } + } + + // Adjust by 1 because of empty space + return input.slice(i + 1); + } + static processTokens(input) { let tokens = []; let searchToken = ''; -- cgit v1.2.3 From 6d2d2b2bd1448bb46b586d5dbe0edc88f020967c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 16:20:14 -0600 Subject: Add filtering to the remaining dropdowns --- app/assets/javascripts/filtered_search/dropdown_label.js.es6 | 8 ++++++++ app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index d4a50422c3b..c5493f7a887 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -24,6 +24,14 @@ super.renderContent(); droplab.setData(this.hookId, 'labels.json'); } + + filterMethod(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutPrefix = value.slice(1); + + item.droplab_hidden = item['title'].indexOf(valueWithoutPrefix) === -1; + return item; + } } global.DropdownLabel = DropdownLabel; diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 965a8c8a58d..8317ce5824c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -24,6 +24,14 @@ super.renderContent(); droplab.setData(this.hookId, 'milestones.json'); } + + filterMethod(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutPrefix = value.slice(1); + + item.droplab_hidden = item['title'].indexOf(valueWithoutPrefix) === -1; + return item; + } } global.DropdownMilestone = DropdownMilestone; -- cgit v1.2.3 From b16a38c8a2c9b1090e3b28c47516e9161130ee10 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 6 Dec 2016 16:23:04 -0600 Subject: Add filterMethod to hint dropdown --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index b09136586c8..0bee2eb2986 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -42,6 +42,18 @@ super.renderContent(); droplab.setData(this.hookId, dropdownData); } + + filterMethod(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + + if (value === '') { + item.droplab_hidden = false; + } else { + item.droplab_hidden = item['hint'].indexOf(value) === -1; + } + + return item; + } } global.DropdownHint = DropdownHint; -- cgit v1.2.3 From 6d2a8b5b140a26f5e83ae7b6497cf14bc9a065ee Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 7 Dec 2016 12:33:02 -0600 Subject: Add font to dropdown offset calculation --- .../filtered_search/filtered_search_manager.js.es6 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 04374525d4c..7e399427cef 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -108,18 +108,22 @@ document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word; } - loadDropdown(dropdownName = '') { + loadDropdown(dropdownName = '', hideDropdown) { dropdownName = dropdownName.toLowerCase(); const filterIconPadding = 27; const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; const filteredSearch = document.querySelector('.filtered-search'); + if (!this.font) { + this.font = window.getComputedStyle(filteredSearch).font; + } + if (match && this.currentDropdown !== match.key) { console.log(`🦄 load ${match.key} dropdown`); const dynamicDropdownPadding = 12; - const dropdownOffset = gl.text.getTextWidth(filteredSearch.value) + filterIconPadding + dynamicDropdownPadding; + const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding + dynamicDropdownPadding; this.dismissCurrentDropdown(); this.currentDropdown = match.key; @@ -157,8 +161,9 @@ } else if (!match && this.currentDropdown !== 'hint') { console.log('🦄 load hint dropdown'); - const dropdownOffset = gl.text.getTextWidth(filteredSearch.value) + filterIconPadding; - + const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding; + console.log(dropdownOffset) + this.dismissCurrentDropdown(); this.currentDropdown = 'hint'; if (!dropdownHint) { -- cgit v1.2.3 From 158e90d13527897b2bdd49733c537898a387f2cb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 7 Dec 2016 12:33:33 -0600 Subject: Add padding for clear button --- app/assets/stylesheets/framework/filters.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 0882af57482..205cecb4906 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -36,6 +36,7 @@ .form-control { padding-left: 25px; + padding-right: 25px; &:focus ~ .fa-filter { color: #444; -- cgit v1.2.3 From 15454eb503bda8ffa9579e08ebe3f3f7114b7643 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 7 Dec 2016 12:51:25 -0600 Subject: Add ability to search for filter dropdowns without filter symbol --- .../javascripts/filtered_search/dropdown_assignee.js.es6 | 15 +++++++++++++++ .../javascripts/filtered_search/dropdown_author.js.es6 | 11 +++++++++-- .../javascripts/filtered_search/dropdown_label.js.es6 | 8 ++++++-- .../javascripts/filtered_search/dropdown_milestone.js.es6 | 9 +++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index e791de5ad41..63fbe30ee84 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -23,6 +23,21 @@ super.renderContent(); droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); } + + filterMethod(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutPrefix = valueWithoutColon.slice(1); + + const username = item.username.toLowerCase(); + const name = item.name.toLowerCase(); + + const noUsernameMatch = username.indexOf(valueWithoutPrefix) === -1 && username.indexOf(valueWithoutColon) === -1; + const noNameMatch = name.indexOf(valueWithoutColon) === -1; + + item.droplab_hidden = noUsernameMatch && noNameMatch; + return item; + } } global.DropdownAssignee = DropdownAssignee; diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 64c310ba7ad..37e2e80533b 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -22,9 +22,16 @@ filterMethod(item, query) { const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutPrefix = value.slice(1); + const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutPrefix = valueWithoutColon.slice(1); - item.droplab_hidden = item['username'].indexOf(valueWithoutPrefix) === -1; + const username = item.username.toLowerCase(); + const name = item.name.toLowerCase(); + + const noUsernameMatch = username.indexOf(valueWithoutPrefix) === -1 && username.indexOf(valueWithoutColon) === -1; + const noNameMatch = name.indexOf(valueWithoutColon) === -1; + + item.droplab_hidden = noUsernameMatch && noNameMatch; return item; } } diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index c5493f7a887..e2c1305597a 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -27,9 +27,13 @@ filterMethod(item, query) { const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutPrefix = value.slice(1); + const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutPrefix = valueWithoutColon.slice(1); - item.droplab_hidden = item['title'].indexOf(valueWithoutPrefix) === -1; + const title = item.title.toLowerCase(); + const noTitleMatch = title.indexOf(valueWithoutPrefix) === -1 && title.indexOf(valueWithoutColon) === -1; + + item.droplab_hidden = noTitleMatch; return item; } } diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 8317ce5824c..cd185d31917 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -27,9 +27,14 @@ filterMethod(item, query) { const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutPrefix = value.slice(1); + const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutPrefix = valueWithoutColon.slice(1); - item.droplab_hidden = item['title'].indexOf(valueWithoutPrefix) === -1; + const title = item.title.toLowerCase(); + + const noTitleMatch = title.indexOf(valueWithoutPrefix) === -1 && title.indexOf(valueWithoutColon) === -1; + + item.droplab_hidden = noTitleMatch; return item; } } -- cgit v1.2.3 From bbad61b97c483a2d4e2a153c2e1b10d21edaa1e0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 7 Dec 2016 12:54:29 -0600 Subject: Fix Droplab --- app/assets/javascripts/droplab/droplab.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 6befa0976d4..84cd89297ff 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -212,7 +212,8 @@ require('./window')(function(w){ var self = this; window.addEventListener('click', function(e){ var thisTag = e.target; - if(thisTag.tagName === 'LI' || thisTag.tagName === 'A'){ + if(thisTag.tagName === 'LI' || thisTag.tagName === 'A' + || thisTag.tagName === 'BUTTON'){ // climb up the tree to find the UL thisTag = utils.closest(thisTag, 'UL'); } @@ -556,7 +557,7 @@ var camelize = function(str) { }; var closest = function(thisTag, stopTag) { - while(thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ + while(thisTag !== null && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ thisTag = thisTag.parentNode; } return thisTag; -- cgit v1.2.3 From ceb79e3c3cb595ac851fb32c99264c1f43dd9c75 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 7 Dec 2016 12:55:03 -0600 Subject: Reset filters after clear search --- .../filtered_search_dropdown.js.es6 | 38 +++++++++++++++++++++- .../filtered_search/filtered_search_manager.js.es6 | 29 ++++++++++------- 2 files changed, 55 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index bbfe26e6a21..edffd7fb8e2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -71,6 +71,20 @@ this.unbindEvents(); } + show() { + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.show(); + } + } + + hide() { + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.hide(); + } + } + dismissDropdown() { this.input.focus(); // Propogate input change to FilteredSearchManager @@ -104,7 +118,7 @@ droplab.setConfig(this.getFilterConfig(this.filterKeyword)); } - render() { + render(hide) { this.setAsDropdown(); const firstTimeInitialized = this.getCurrentHook() === undefined; @@ -115,6 +129,28 @@ droplab.changeHookList(this.hookId, `#${this.listId}`); this.renderContent(); } + + if (hide) { + this.hide(); + } else { + this.show(); + } + } + + resetFilters() { + const currentHook = this.getCurrentHook(); + + if (currentHook) { + const list = currentHook.list; + + if (list.data) { + const data = list.data.map((item) => { + item.droplab_hidden = false; + }); + + list.render(data); + } + } } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 7e399427cef..841738ff627 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,13 +1,5 @@ /* eslint-disable no-param-reassign */ ((global) => { - function clearSearch(e) { - e.stopPropagation(); - e.preventDefault(); - - document.querySelector('.filtered-search').value = ''; - document.querySelector('.clear-search').classList.add('hidden'); - } - function toggleClearSearchButton(e) { const clearSearchButton = document.querySelector('.clear-search'); @@ -170,7 +162,13 @@ dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), filteredSearch, this.currentDropdown); } dropdownHint.setOffset(dropdownOffset); - dropdownHint.render(); + dropdownHint.render(hideDropdown); + } + } + + dismissCurrentDropdown() { + if (this.currentDropdown === 'hint') { + dropdownHint.destroy(); } } @@ -198,7 +196,17 @@ filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); filteredSearchInput.addEventListener('input', toggleClearSearchButton); filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); - document.querySelector('.clear-search').addEventListener('click', clearSearch); + document.querySelector('.clear-search').addEventListener('click', this.clearSearch.bind(this)); + } + + clearSearch(e) { + e.stopPropagation(); + e.preventDefault(); + + document.querySelector('.filtered-search').value = ''; + document.querySelector('.clear-search').classList.add('hidden'); + dropdownHint.resetFilters(); + this.loadDropdown('hint', true); } checkDropdownToken(e) { @@ -208,7 +216,6 @@ // Check for dropdown token if (lastToken[lastToken.length - 1] === ':') { const token = lastToken.slice(0, -1); - } } -- cgit v1.2.3 From 367a15882a07a2f48dd7887fea642baf3920b6b7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 8 Dec 2016 15:36:54 -0600 Subject: Update droplab --- app/assets/javascripts/droplab/droplab.js | 190 ++++++++++++++------- app/assets/javascripts/droplab/droplab_ajax.js | 89 +++++----- app/assets/javascripts/droplab/droplab_filter.js | 77 ++++++--- .../filtered_search/dropdown_assignee.js.es6 | 13 +- .../filtered_search/dropdown_author.js.es6 | 13 +- .../filtered_search/dropdown_hint.js.es6 | 22 ++- .../filtered_search/dropdown_label.js.es6 | 30 ++-- .../filtered_search/dropdown_milestone.js.es6 | 30 ++-- .../filtered_search_dropdown.js.es6 | 60 ++++--- .../filtered_search/filtered_search_manager.js.es6 | 42 +++-- 10 files changed, 354 insertions(+), 212 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 84cd89297ff..4d83b609a73 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -53,6 +53,7 @@ Object.assign(DropDown.prototype, { this.list.addEventListener('click', function(e) { // climb up the tree to find the LI var selected = utils.closest(e.target, 'LI'); + if(selected) { e.preventDefault(); self.hide(); @@ -158,17 +159,22 @@ require('./window')(function(w){ this.ready = false; this.hooks = []; this.queuedData = []; - this.plugins = []; this.config = {}; + this.loadWrapper; if(typeof hook !== 'undefined'){ this.addHook(hook); } - this.addEvents(); }; + Object.assign(DropLab.prototype, { - plugin: function (plugin) { - this.plugins.push(plugin) + load: function() { + this.loadWrapper(); + }, + + loadWrapper: function(){ + var dropdownTriggers = [].slice.apply(document.querySelectorAll('['+DATA_TRIGGER+']')); + this.addHooks(dropdownTriggers).init(); }, addData: function () { @@ -181,6 +187,14 @@ require('./window')(function(w){ this.applyArgs(args, '_setData'); }, + destroy: function() { + this.hooks.forEach(function(h){ + h.destroy(); + }); + this.hooks = []; + this.removeEvents(); + }, + applyArgs: function(args, methodName) { if(this.ready) { this[methodName].apply(this, args); @@ -210,7 +224,7 @@ require('./window')(function(w){ addEvents: function() { var self = this; - window.addEventListener('click', function(e){ + this.windowClickedWrapper = function(e){ var thisTag = e.target; if(thisTag.tagName === 'LI' || thisTag.tagName === 'A' || thisTag.tagName === 'BUTTON'){ @@ -222,10 +236,16 @@ require('./window')(function(w){ self.hooks.forEach(function(hook) { hook.list.hide(); }); - }); + }.bind(this); + w.addEventListener('click', this.windowClickedWrapper); + }, + + removeEvents: function(){ + w.removeEventListener('click', this.windowClickedWrapper); + w.removeEventListener('load', this.loadWrapper); }, - changeHookList: function(trigger, list) { + changeHookList: function(trigger, list, plugins, config) { trigger = document.querySelector('[data-id="'+trigger+'"]'); list = document.querySelector(list); this.hooks.every(function(hook, i) { @@ -234,19 +254,16 @@ require('./window')(function(w){ hook.list.list.innerHTML = hook.list.initialState; hook.list.hide(); - hook.trigger.removeEventListener('mousedown', hook.events.mousedown); - hook.trigger.removeEventListener('input', hook.events.input); - hook.trigger.removeEventListener('keyup', hook.events.keyup); - hook.trigger.removeEventListener('keydown', hook.events.keydown); + hook.destroy(); this.hooks.splice(i, 1); - this.addHook(trigger, list); + this.addHook(trigger, list, plugins, config); return false; } return true }.bind(this)); }, - addHook: function(hook, list) { + addHook: function(hook, list, plugins, config) { if(!(hook instanceof HTMLElement) && typeof hook === 'string'){ hook = document.querySelector(hook); } @@ -256,17 +273,17 @@ require('./window')(function(w){ if(hook) { if(hook.tagName === 'A' || hook.tagName === 'BUTTON') { - this.hooks.push(new HookButton(hook, list)); + this.hooks.push(new HookButton(hook, list, plugins, config)); } else if(hook.tagName === 'INPUT') { - this.hooks.push(new HookInput(hook, list)); + this.hooks.push(new HookInput(hook, list, plugins, config)); } } return this; }, - addHooks: function(hooks) { + addHooks: function(hooks, plugins, config) { hooks.forEach(function(hook) { - this.addHook(hook, null); + this.addHook(hook, null, plugins, config); }.bind(this)); return this; }, @@ -276,9 +293,7 @@ require('./window')(function(w){ }, init: function () { - this.plugins.forEach(function(plugin) { - plugin(DropLab); - }) + this.addEvents(); var readyEvent = new CustomEvent('ready.dl', { detail: { dropdown: this, @@ -301,15 +316,18 @@ require('./window')(function(w){ },{"./constants":1,"./custom_event_polyfill":2,"./hook_button":6,"./hook_input":7,"./utils":10,"./window":11}],5:[function(require,module,exports){ var DropDown = require('./dropdown'); -var Hook = function(trigger, list){ +var Hook = function(trigger, list, plugins, config){ this.trigger = trigger; this.list = new DropDown(list); this.type = 'Hook'; this.event = 'click'; + this.plugins = plugins || []; + this.config = config || {}; this.id = trigger.dataset.id; }; Object.assign(Hook.prototype, { + addEvents: function(){}, constructor: Hook, @@ -321,31 +339,61 @@ module.exports = Hook; var CustomEvent = require('./custom_event_polyfill'); var Hook = require('./hook'); -var HookButton = function(trigger, list) { - Hook.call(this, trigger, list); +var HookButton = function(trigger, list, plugins, config) { + Hook.call(this, trigger, list, plugins, config); this.type = 'button'; this.event = 'click'; this.addEvents(); + this.addPlugins(); }; HookButton.prototype = Object.create(Hook.prototype); Object.assign(HookButton.prototype, { + addPlugins: function() { + this.plugins.forEach(function(plugin) { + plugin.init(this); + }); + }, + + clicked: function(e){ + var buttonEvent = new CustomEvent('click.dl', { + detail: { + hook: this, + }, + bubbles: true, + cancelable: true + }); + this.list.show(); + e.target.dispatchEvent(buttonEvent); + }, + addEvents: function(){ - var self = this; - this.trigger.addEventListener('click', function(e){ - var buttonEvent = new CustomEvent('click.dl', { - detail: { - hook: self, - }, - bubbles: true, - cancelable: true - }); - self.list.show(); - e.target.dispatchEvent(buttonEvent); + this.clickedWrapper = this.clicked.bind(this); + this.trigger.addEventListener('click', this.clickedWrapper); + }, + + removeEvents: function(){ + this.trigger.removeEventListener('click', this.clickedWrapper); + }, + + restoreInitialState: function() { + this.list.list.innerHTML = this.list.initialState; + }, + + removePlugins: function() { + this.plugins.forEach(function(plugin) { + plugin.destroy(); }); }, + destroy: function() { + this.restoreInitialState(); + this.removeEvents(); + this.removePlugins(); + }, + + constructor: HookButton, }); @@ -356,18 +404,26 @@ module.exports = HookButton; var CustomEvent = require('./custom_event_polyfill'); var Hook = require('./hook'); -var HookInput = function(trigger, list) { - Hook.call(this, trigger, list); +var HookInput = function(trigger, list, plugins, config) { + Hook.call(this, trigger, list, plugins, config); this.type = 'input'; this.event = 'input'; + this.addPlugins(); this.addEvents(); }; Object.assign(HookInput.prototype, { + addPlugins: function() { + var self = this; + this.plugins.forEach(function(plugin) { + plugin.init(self); + }); + }, + addEvents: function(){ var self = this; - function mousedown(e) { + this.mousedown = function mousedown(e) { var mouseEvent = new CustomEvent('mousedown.dl', { detail: { hook: self, @@ -379,7 +435,7 @@ Object.assign(HookInput.prototype, { e.target.dispatchEvent(mouseEvent); } - function input(e) { + this.input = function input(e) { var inputEvent = new CustomEvent('input.dl', { detail: { hook: self, @@ -392,11 +448,11 @@ Object.assign(HookInput.prototype, { self.list.show(); } - function keyup(e) { + this.keyup = function keyup(e) { keyEvent(e, 'keyup.dl'); } - function keydown(e) { + this.keydown = function keydown(e) { keyEvent(e, 'keydown.dl'); } @@ -416,15 +472,38 @@ Object.assign(HookInput.prototype, { } this.events = this.events || {}; - this.events.mousedown = mousedown; - this.events.input = input; - this.events.keyup = keyup; - this.events.keydown = keydown; - this.trigger.addEventListener('mousedown', mousedown); - this.trigger.addEventListener('input', input); - this.trigger.addEventListener('keyup', keyup); - this.trigger.addEventListener('keydown', keydown); + this.events.mousedown = this.mousedown; + this.events.input = this.input; + this.events.keyup = this.keyup; + this.events.keydown = this.keydown; + this.trigger.addEventListener('mousedown', this.mousedown); + this.trigger.addEventListener('input', this.input); + this.trigger.addEventListener('keyup', this.keyup); + this.trigger.addEventListener('keydown', this.keydown); }, + + removeEvents: function(){ + this.trigger.removeEventListener('mousedown', this.mousedown); + this.trigger.removeEventListener('input', this.input); + this.trigger.removeEventListener('keyup', this.keyup); + this.trigger.removeEventListener('keydown', this.keydown); + }, + + restoreInitialState: function() { + this.list.list.innerHTML = this.list.initialState; + }, + + removePlugins: function() { + this.plugins.forEach(function(plugin) { + plugin.destroy(); + }); + }, + + destroy: function() { + this.restoreInitialState(); + this.removeEvents(); + this.removePlugins(); + } }); module.exports = HookInput; @@ -433,21 +512,14 @@ module.exports = HookInput; var DropLab = require('./droplab')(); var DATA_TRIGGER = require('./constants').DATA_TRIGGER; var keyboard = require('./keyboard')(); - var setup = function() { - var droplab = DropLab(); - require('./window')(function(w) { - w.addEventListener('load', function() { - var dropdownTriggers = [].slice.apply(document.querySelectorAll('['+DATA_TRIGGER+']')); - droplab.addHooks(dropdownTriggers).init(); - }); - }); - return droplab; + window.DropLab = DropLab; }; + module.exports = setup(); -},{"./constants":1,"./droplab":4,"./keyboard":9,"./window":11}],9:[function(require,module,exports){ +},{"./constants":1,"./droplab":4,"./keyboard":9}],9:[function(require,module,exports){ require('./window')(function(w){ module.exports = function(){ var currentKey; @@ -557,7 +629,7 @@ var camelize = function(str) { }; var closest = function(thisTag, stopTag) { - while(thisTag !== null && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ + while(thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){ thisTag = thisTag.parentNode; } return thisTag; diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index 2dff5b83fae..b81663c281d 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -1,52 +1,59 @@ /* eslint-disable */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { class DropdownAssignee extends gl.FilteredSearchDropdown { - constructor(dropdown, input) { - super(dropdown, input); + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); this.listId = 'js-dropdown-assignee'; } @@ -20,8 +20,13 @@ } renderContent() { - super.renderContent(); - droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); + // TODO: Pass elements instead of querySelectors + this.droplab.changeHookList(this.hookId, '#js-dropdown-assignee', [droplabAjax], { + droplabAjax: { + endpoint: '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users=', + method: 'setData', + } + }); } filterMethod(item, query) { diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 37e2e80533b..c02f1e25407 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -3,8 +3,8 @@ ((global) => { class DropdownAuthor extends gl.FilteredSearchDropdown { - constructor(dropdown, input) { - super(dropdown, input); + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); this.listId = 'js-dropdown-author'; } @@ -16,8 +16,13 @@ } renderContent() { - super.renderContent(); - droplab.setData(this.hookId, '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users='); + // TODO: Pass elements instead of querySelectors + this.droplab.changeHookList(this.hookId, '#js-dropdown-author', [droplabAjax], { + droplabAjax: { + endpoint: '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users=', + method: 'setData', + } + }); } filterMethod(item, query) { diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 0bee2eb2986..481faa7fd49 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -21,10 +21,9 @@ }]; class DropdownHint extends gl.FilteredSearchDropdown { - constructor(dropdown, input, filterKeyword) { - super(dropdown, input); + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); this.listId = 'js-dropdown-hint'; - this.filterKeyword = filterKeyword; } itemClicked(e) { @@ -39,8 +38,13 @@ } renderContent() { - super.renderContent(); - droplab.setData(this.hookId, dropdownData); + this.droplab.changeHookList(this.hookId, '#js-dropdown-hint', [droplabFilter], { + droplabFilter: { + template: 'hint', + filterFunction: this.filterMethod, + } + }); + this.droplab.setData(this.hookId, dropdownData); } filterMethod(item, query) { @@ -54,6 +58,14 @@ return item; } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabFilter], { + droplabFilter: { + template: 'hint', + } + }).init(); + } } global.DropdownHint = DropdownHint; diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index e2c1305597a..af47ad2a1f8 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -3,9 +3,10 @@ ((global) => { class DropdownLabel extends gl.FilteredSearchDropdown { - constructor(dropdown, input) { - super(dropdown, input); + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); this.listId = 'js-dropdown-label'; + this.filterSymbol = '~'; } itemClicked(e) { @@ -21,20 +22,17 @@ } renderContent() { - super.renderContent(); - droplab.setData(this.hookId, 'labels.json'); - } - - filterMethod(item, query) { - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); - const valueWithoutPrefix = valueWithoutColon.slice(1); - - const title = item.title.toLowerCase(); - const noTitleMatch = title.indexOf(valueWithoutPrefix) === -1 && title.indexOf(valueWithoutColon) === -1; - - item.droplab_hidden = noTitleMatch; - return item; + // TODO: Pass elements instead of querySelectors + // TODO: Don't bind filterWithSymbol to (this), just pass the symbol + this.droplab.changeHookList(this.hookId, '#js-dropdown-label', [droplabAjax, droplabFilter], { + droplabAjax: { + endpoint: 'labels.json', + method: 'setData', + }, + droplabFilter: { + filterFunction: this.filterWithSymbol.bind(this), + } + }); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index cd185d31917..9810767eb66 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -3,9 +3,10 @@ ((global) => { class DropdownMilestone extends gl.FilteredSearchDropdown { - constructor(dropdown, input) { - super(dropdown, input); + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); this.listId = 'js-dropdown-milestone'; + this.filterSymbol = '%'; } itemClicked(e) { @@ -21,21 +22,16 @@ } renderContent() { - super.renderContent(); - droplab.setData(this.hookId, 'milestones.json'); - } - - filterMethod(item, query) { - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); - const valueWithoutPrefix = valueWithoutColon.slice(1); - - const title = item.title.toLowerCase(); - - const noTitleMatch = title.indexOf(valueWithoutPrefix) === -1 && title.indexOf(valueWithoutColon) === -1; - - item.droplab_hidden = noTitleMatch; - return item; + // TODO: Pass elements instead of querySelectors + this.droplab.changeHookList(this.hookId, '#js-dropdown-milestone', [droplabAjax, droplabFilter], { + droplabAjax: { + endpoint: 'milestones.json', + method: 'setData', + }, + droplabFilter: { + filterFunction: this.filterWithSymbol.bind(this), + } + }); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index edffd7fb8e2..80c3407b7fa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -3,7 +3,8 @@ const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; class FilteredSearchDropdown { - constructor(dropdown, input) { + constructor(droplab, dropdown, input) { + this.droplab = droplab; this.hookId = 'filtered-search'; this.input = input; this.dropdown = dropdown; @@ -66,25 +67,11 @@ destroy() { this.input.setAttribute(DATA_DROPDOWN_TRIGGER, ''); - droplab.setConfig(this.getFilterConfig()); - droplab.setData(this.hookId, []); + this.droplab.setConfig(this.getFilterConfig()); + this.droplab.setData(this.hookId, []); this.unbindEvents(); } - show() { - const currentHook = this.getCurrentHook(); - if (currentHook) { - currentHook.list.show(); - } - } - - hide() { - const currentHook = this.getCurrentHook(); - if (currentHook) { - currentHook.list.hide(); - } - } - dismissDropdown() { this.input.focus(); // Propogate input change to FilteredSearchManager @@ -111,30 +98,24 @@ } getCurrentHook() { - return droplab.hooks.filter(h => h.id === this.hookId)[0]; + return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; } renderContent() { - droplab.setConfig(this.getFilterConfig(this.filterKeyword)); + // Overriden by dropdown sub class } - render(hide) { + render(forceRenderContent) { this.setAsDropdown(); const firstTimeInitialized = this.getCurrentHook() === undefined; - if (firstTimeInitialized) { + if (firstTimeInitialized || forceRenderContent) { this.renderContent(); } else if(this.getCurrentHook().list.list.id !== this.listId) { - droplab.changeHookList(this.hookId, `#${this.listId}`); + // this.droplab.changeHookList(this.hookId, `#${this.listId}`); this.renderContent(); } - - if (hide) { - this.hide(); - } else { - this.show(); - } } resetFilters() { @@ -152,6 +133,29 @@ } } } + + hide() { + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.hide(); + } + } + + filterWithSymbol(item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutColon = value.slice(1).toLowerCase(); + const prefix = valueWithoutColon[0]; + const valueWithoutPrefix = valueWithoutColon.slice(1); + + const title = item.title.toLowerCase(); + + // Eg. this.filterSymbol = ~ for labels + const matchWithoutPrefix = prefix === this.filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; + const match = title.indexOf(valueWithoutColon) !== -1; + + item.droplab_hidden = !match && !matchWithoutPrefix; + return item; + } } global.FilteredSearchDropdown = FilteredSearchDropdown; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 841738ff627..af8e145fa7f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -101,11 +101,18 @@ } loadDropdown(dropdownName = '', hideDropdown) { + let firstLoad = false; + const filteredSearch = document.querySelector('.filtered-search'); + + if(!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } + dropdownName = dropdownName.toLowerCase(); const filterIconPadding = 27; const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; - const filteredSearch = document.querySelector('.filtered-search'); if (!this.font) { this.font = window.getComputedStyle(filteredSearch).font; @@ -116,34 +123,38 @@ const dynamicDropdownPadding = 12; const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding + dynamicDropdownPadding; + const dropdownAuthorElement = document.querySelector('#js-dropdown-author'); + const dropdownAssigneeElement = document.querySelector('#js-dropdown-assignee'); + const dropdownMilestoneElement = document.querySelector('#js-dropdown-milestone'); + const dropdownLabelElemenet = document.querySelector('#js-dropdown-label'); this.dismissCurrentDropdown(); this.currentDropdown = match.key; if (match.key === 'author') { if (!dropdownAuthor) { - dropdownAuthor = new gl.DropdownAuthor(document.querySelector('#js-dropdown-author'), filteredSearch); + dropdownAuthor = new gl.DropdownAuthor(this.droplab, dropdownAuthorElement, filteredSearch); } dropdownAuthor.setOffset(dropdownOffset); dropdownAuthor.render(); } else if (match.key === 'assignee') { if (!dropdownAssignee) { - dropdownAssignee = new gl.DropdownAssignee(document.querySelector('#js-dropdown-assignee'), filteredSearch); + dropdownAssignee = new gl.DropdownAssignee(this.droplab, dropdownAssigneeElement, filteredSearch); } dropdownAssignee.setOffset(dropdownOffset); dropdownAssignee.render(); } else if (match.key === 'milestone') { if (!dropdownMilestone) { - dropdownMilestone = new gl.DropdownMilestone(document.querySelector('#js-dropdown-milestone'), filteredSearch); + dropdownMilestone = new gl.DropdownMilestone(this.droplab, dropdownMilestoneElement, filteredSearch); } dropdownMilestone.setOffset(dropdownOffset); dropdownMilestone.render(); } else if (match.key === 'label') { if (!dropdownLabel) { - dropdownLabel = new gl.DropdownLabel(document.querySelector('#js-dropdown-label'), filteredSearch); + dropdownLabel = new gl.DropdownLabel(this.droplab, dropdownLabelElemenet, filteredSearch); } dropdownLabel.setOffset(dropdownOffset); @@ -154,22 +165,29 @@ console.log('🦄 load hint dropdown'); const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding; - console.log(dropdownOffset) + const dropdownHintElement = document.querySelector('#js-dropdown-hint'); + this.dismissCurrentDropdown(); this.currentDropdown = 'hint'; - if (!dropdownHint) { - dropdownHint = new gl.DropdownHint(document.querySelector('#js-dropdown-hint'), filteredSearch, this.currentDropdown); + dropdownHint = new gl.DropdownHint(this.droplab, dropdownHintElement, filteredSearch); + } + + if (firstLoad) { + dropdownHint.configure(); } + dropdownHint.setOffset(dropdownOffset); - dropdownHint.render(hideDropdown); + dropdownHint.render(firstLoad); } } dismissCurrentDropdown() { - if (this.currentDropdown === 'hint') { - dropdownHint.destroy(); - } + // if (this.currentDropdown === 'hint') { + // dropdownHint.hide(); + // } else if (this.currentDropdown === 'author') { + // // dropdownAuthor.hide(); + // } } setDropdown() { -- cgit v1.2.3 From 3cf0ee6c1f13d111da933cdea98b46582c7c4306 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 8 Dec 2016 15:42:58 -0600 Subject: Fix turbolinks issue by cleaning up droplab on page:change --- .../filtered_search/filtered_search_manager.js.es6 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index af8e145fa7f..f28ce6b4366 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -81,6 +81,25 @@ this.bindEvents(); loadSearchParamsFromURL(); this.setDropdown(); + + document.addEventListener('page:change', this.cleanup); + } + + cleanup() { + console.log('cleanup') + + if (this.droplab) { + this.droplab.destroy(); + this.droplab = null; + } + + dropdownHint = null; + dropdownAuthor = null; + dropdownAssignee = null; + dropdownMilestone = null; + dropdownLabel = null; + + document.removeEventListener('page:change', this.cleanup); } static addWordToInput(word, addSpace) { -- cgit v1.2.3 From c5029c65450522359ec8b869c1433ed89db3c303 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 8 Dec 2016 16:40:46 -0600 Subject: Add basic ajax filtering for author --- .../javascripts/droplab/droplab_ajax_filter.js | 109 +++++++++++++++++++++ .../filtered_search/dropdown_author.js.es6 | 34 ++++--- 2 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 app/assets/javascripts/droplab/droplab_ajax_filter.js (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js new file mode 100644 index 00000000000..b346f22f1c2 --- /dev/null +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -0,0 +1,109 @@ +/* eslint-disable */ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1; + if (invalidKeyPressed || this.loading) { + return; + } + + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.timeout = setTimeout(this.trigger.bind(this), 200); + }, + + trigger: function trigger() { + var config = this.hook.config.droplabAjaxFilter; + var searchValue = this.trigger.value; + + if (!config || !config.endpoint || !config.searchKey) { + return; + } + + if (config.searchValueFunction) { + searchValue = config.searchValueFunction(); + } + + if (searchValue === config.searchKey) { + return this.list.show(); + } + + this.loading = true; + this.hook.list.setData([]); + + var params = config.params || {}; + params[config.searchKey] = searchValue; + var self = this; + this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { + self.hook.list.addData.call(self.hook.list, data[0]); + self.notLoading(); + }); + }, + + _loadUrlData: function _loadUrlData(url) { + return new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest; + xhr.open('GET', url, true); + xhr.onreadystatechange = function () { + if(xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + var data = JSON.parse(xhr.responseText); + return resolve([data, xhr]); + } else { + return reject([xhr.responseText, xhr.status]); + } + } + }; + xhr.send(); + }); + }, + + buildParams: function(params) { + if (!params) return ''; + var paramsArray = Object.keys(params).map(function(param) { + return param + '=' + (params[param] || ''); + }); + return '?' + paramsArray.join('&'); + }, + + destroy: function destroy() { + if (this.timeout) { + clearTimeout(this.timeout); + } + + this.hook.trigger.removeEventListener('keydown.dl', this.debounceTrigger); + this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper); + } + }; +}); +},{"../window":2}],2:[function(require,module,exports){ +module.exports = function(callback) { + return (function() { + callback(this); + }).call(null); +}; + +},{}]},{},[1])(1) +}); \ No newline at end of file diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index c02f1e25407..cb3a6b6ab6d 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -17,27 +17,33 @@ renderContent() { // TODO: Pass elements instead of querySelectors - this.droplab.changeHookList(this.hookId, '#js-dropdown-author', [droplabAjax], { - droplabAjax: { - endpoint: '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users=', - method: 'setData', + this.droplab.changeHookList(this.hookId, '#js-dropdown-author', [droplabAjaxFilter], { + droplabAjaxFilter: { + endpoint: '/autocomplete/users.json', + searchKey: 'search', + params: { + per_page: 20, + active: true, + project_id: 2, + current_user: true, + }, + searchValueFunction: this.getSearchInput, } }); } - filterMethod(item, query) { + getSearchInput() { + const query = document.querySelector('.filtered-search').value; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutColon = value.slice(1); + const hasPrefix = valueWithoutColon[0] === '@'; const valueWithoutPrefix = valueWithoutColon.slice(1); - const username = item.username.toLowerCase(); - const name = item.name.toLowerCase(); - - const noUsernameMatch = username.indexOf(valueWithoutPrefix) === -1 && username.indexOf(valueWithoutColon) === -1; - const noNameMatch = name.indexOf(valueWithoutColon) === -1; - - item.droplab_hidden = noUsernameMatch && noNameMatch; - return item; + if (hasPrefix) { + return valueWithoutPrefix; + } else { + return valueWithoutColon; + } } } -- cgit v1.2.3 From ed4e525a3bd05dfb32aa3c2baa9bf19688319b1a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 11:44:09 -0600 Subject: Code cleanup --- app/assets/javascripts/droplab/droplab.js | 11 +- .../javascripts/droplab/droplab_ajax_filter.js | 13 +- .../filtered_search/dropdown_assignee.js.es6 | 41 +++-- .../filtered_search/dropdown_author.js.es6 | 31 ++-- .../filtered_search/dropdown_hint.js.es6 | 30 ++-- .../filtered_search/dropdown_label.js.es6 | 26 +-- .../filtered_search/dropdown_milestone.js.es6 | 25 +-- .../filtered_search_dropdown.js.es6 | 65 ++----- .../filtered_search/filtered_search_manager.js.es6 | 194 +++++++++------------ app/views/shared/issuable/_search_bar.html.haml | 2 +- 10 files changed, 203 insertions(+), 235 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 4d83b609a73..b17f156acb4 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -39,6 +39,10 @@ var DropDown = function(list, trigger) { this.getItems(); this.addEvents(); this.initialState = list.innerHTML; + + if (this.initialState.indexOf('{{') == -1) { + debugger + } }; Object.assign(DropDown.prototype, { @@ -138,6 +142,10 @@ Object.assign(DropDown.prototype, { this.list.style.display = 'none'; this.hidden = true; }, + + destroy: function() { + this.hide(); + } }); module.exports = DropDown; @@ -247,7 +255,7 @@ require('./window')(function(w){ changeHookList: function(trigger, list, plugins, config) { trigger = document.querySelector('[data-id="'+trigger+'"]'); - list = document.querySelector(list); + // list = document.querySelector(list); this.hooks.every(function(hook, i) { if(hook.trigger === trigger) { // Restore initial State @@ -503,6 +511,7 @@ Object.assign(HookInput.prototype, { this.restoreInitialState(); this.removeEvents(); this.removePlugins(); + this.list.destroy(); } }); diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index b346f22f1c2..c345fda1075 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -8,14 +8,12 @@ require('../window')(function(w){ this.hook = hook; this.notLoading(); - this.hook.trigger.addEventListener('keydown.dl', this.debounceTrigger.bind(this)); + this.debounceTriggerWrapper = this.debounceTrigger.bind(this); + this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper); + this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper); this.trigger(); }, - debounceTriggerWrapper() { - return this.debounceTrigger.bind(this.hook); - }, - notLoading: function notLoading() { this.loading = false; }, @@ -57,7 +55,8 @@ require('../window')(function(w){ params[config.searchKey] = searchValue; var self = this; this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { - self.hook.list.addData.call(self.hook.list, data[0]); + self.hook.restoreInitialState.call(self.hook); + self.hook.list.setData.call(self.hook.list, data[0]); self.notLoading(); }); }, @@ -93,7 +92,7 @@ require('../window')(function(w){ clearTimeout(this.timeout); } - this.hook.trigger.removeEventListener('keydown.dl', this.debounceTrigger); + this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper); this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper); } }; diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index 7609546a3a6..b2b03b637e7 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -6,6 +6,19 @@ constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.listId = 'js-dropdown-assignee'; + this.config = { + droplabAjaxFilter: { + endpoint: '/autocomplete/users.json', + searchKey: 'search', + params: { + per_page: 20, + active: true, + project_id: 2, + current_user: true, + }, + searchValueFunction: this.getSearchInput, + } + }; } itemClicked(e) { @@ -21,27 +34,25 @@ renderContent() { // TODO: Pass elements instead of querySelectors - this.droplab.changeHookList(this.hookId, '#js-dropdown-assignee', [droplabAjax], { - droplabAjax: { - endpoint: '/autocomplete/users.json?search=&per_page=20&active=true&project_id=2&group_id=&skip_ldap=&todo_filter=&todo_state_filter=¤t_user=true&push_code_to_protected_branches=&author_id=&skip_users=', - method: 'setData', - } - }); + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); } - filterMethod(item, query) { + getSearchInput() { + const query = document.querySelector('.filtered-search').value; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); + const valueWithoutColon = value.slice(1); + const hasPrefix = valueWithoutColon[0] === '@'; const valueWithoutPrefix = valueWithoutColon.slice(1); - const username = item.username.toLowerCase(); - const name = item.name.toLowerCase(); - - const noUsernameMatch = username.indexOf(valueWithoutPrefix) === -1 && username.indexOf(valueWithoutColon) === -1; - const noNameMatch = name.indexOf(valueWithoutColon) === -1; + if (hasPrefix) { + return valueWithoutPrefix; + } else { + return valueWithoutColon; + } + } - item.droplab_hidden = noUsernameMatch && noNameMatch; - return item; + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index cb3a6b6ab6d..9bd49ab1a78 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -6,18 +6,7 @@ constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.listId = 'js-dropdown-author'; - } - - itemClicked(e) { - const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); - - this.dismissDropdown(); - } - - renderContent() { - // TODO: Pass elements instead of querySelectors - this.droplab.changeHookList(this.hookId, '#js-dropdown-author', [droplabAjaxFilter], { + this.config = { droplabAjaxFilter: { endpoint: '/autocomplete/users.json', searchKey: 'search', @@ -29,7 +18,19 @@ }, searchValueFunction: this.getSearchInput, } - }); + }; + } + + itemClicked(e) { + const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); + + this.dismissDropdown(); + } + + renderContent() { + // TODO: Pass elements instead of querySelectors + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); } getSearchInput() { @@ -45,6 +46,10 @@ return valueWithoutColon; } } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); + } } global.DropdownAuthor = DropdownAuthor; diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 481faa7fd49..f885267880a 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -24,26 +24,30 @@ constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.listId = 'js-dropdown-hint'; + this.config = { + droplabFilter: { + template: 'hint', + filterFunction: this.filterMethod, + } + }; } itemClicked(e) { - const token = e.detail.selected.querySelector('.js-filter-hint').innerText.trim(); - const tag = e.detail.selected.querySelector('.js-filter-tag').innerText.trim(); + const selected = e.detail.selected; + if (!selected.hasAttribute('data-value')) { + const token = selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = selected.querySelector('.js-filter-tag').innerText.trim(); - if (tag.length) { - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); + if (tag.length) { + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); + } } this.dismissDropdown(); } renderContent() { - this.droplab.changeHookList(this.hookId, '#js-dropdown-hint', [droplabFilter], { - droplabFilter: { - template: 'hint', - filterFunction: this.filterMethod, - } - }); + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.setData(this.hookId, dropdownData); } @@ -60,11 +64,7 @@ } configure() { - this.droplab.addHook(this.input, this.dropdown, [droplabFilter], { - droplabFilter: { - template: 'hint', - } - }).init(); + this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index af47ad2a1f8..24a795808ca 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -6,7 +6,15 @@ constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.listId = 'js-dropdown-label'; - this.filterSymbol = '~'; + this.config = { + droplabAjax: { + endpoint: 'labels.json', + method: 'setData', + }, + droplabFilter: { + filterFunction: this.filterWithSymbol.bind(this, '~'), + } + }; } itemClicked(e) { @@ -22,17 +30,11 @@ } renderContent() { - // TODO: Pass elements instead of querySelectors - // TODO: Don't bind filterWithSymbol to (this), just pass the symbol - this.droplab.changeHookList(this.hookId, '#js-dropdown-label', [droplabAjax, droplabFilter], { - droplabAjax: { - endpoint: 'labels.json', - method: 'setData', - }, - droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this), - } - }); + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 9810767eb66..458a9b1c5c1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -6,7 +6,15 @@ constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.listId = 'js-dropdown-milestone'; - this.filterSymbol = '%'; + this.config = { + droplabAjax: { + endpoint: 'milestones.json', + method: 'setData', + }, + droplabFilter: { + filterFunction: this.filterWithSymbol.bind(this, '%'), + } + }; } itemClicked(e) { @@ -22,16 +30,11 @@ } renderContent() { - // TODO: Pass elements instead of querySelectors - this.droplab.changeHookList(this.hookId, '#js-dropdown-milestone', [droplabAjax, droplabFilter], { - droplabAjax: { - endpoint: 'milestones.json', - method: 'setData', - }, - droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this), - } - }); + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 80c3407b7fa..2f92c7b2e2a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -12,11 +12,16 @@ } bindEvents() { - this.dropdown.addEventListener('click.dl', this.itemClicked.bind(this)); + this.itemClickedWrapper = this.itemClicked.bind(this); + this.dropdown.addEventListener('click.dl', this.itemClickedWrapper); } unbindEvents() { - this.dropdown.removeEventListener('click.dl', this.itemClicked.bind(this)); + this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); + } + + getCurrentHook() { + return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; } getEscapedText(text) { @@ -49,34 +54,8 @@ // Overridden by dropdown sub class } - getFilterConfig(filterKeyword) { - const config = {}; - const filterConfig = {}; - - if (filterKeyword) { - filterConfig.text = filterKeyword; - } - - if (this.filterMethod) { - filterConfig.filter = this.filterMethod; - } - - config[this.hookId] = filterConfig; - return config; - } - - destroy() { - this.input.setAttribute(DATA_DROPDOWN_TRIGGER, ''); - this.droplab.setConfig(this.getFilterConfig()); - this.droplab.setData(this.hookId, []); - this.unbindEvents(); - } - - dismissDropdown() { - this.input.focus(); - // Propogate input change to FilteredSearchManager - // so that it can determine which dropdowns to open - this.input.dispatchEvent(new Event('input')); + renderContent() { + // Overriden by dropdown sub class } setAsDropdown() { @@ -97,13 +76,12 @@ return dataValue !== null; } - getCurrentHook() { - return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; - } - - renderContent() { - // Overriden by dropdown sub class - } + dismissDropdown() { + this.input.focus(); + // Propogate input change to FilteredSearchManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new Event('input')); + } render(forceRenderContent) { this.setAsDropdown(); @@ -134,14 +112,7 @@ } } - hide() { - const currentHook = this.getCurrentHook(); - if (currentHook) { - currentHook.list.hide(); - } - } - - filterWithSymbol(item, query) { + filterWithSymbol(filterSymbol, item, query) { const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); const valueWithoutColon = value.slice(1).toLowerCase(); const prefix = valueWithoutColon[0]; @@ -149,8 +120,8 @@ const title = item.title.toLowerCase(); - // Eg. this.filterSymbol = ~ for labels - const matchWithoutPrefix = prefix === this.filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; + // Eg. filterSymbol = ~ for labels + const matchWithoutPrefix = prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; const match = title.indexOf(valueWithoutColon) !== -1; item.droplab_hidden = !match && !matchWithoutPrefix; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index f28ce6b4366..9846f3ba50d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -69,20 +69,19 @@ } } - let dropdownHint; - let dropdownAuthor; - let dropdownAssignee; - let dropdownMilestone; - let dropdownLabel; - class FilteredSearchManager { constructor() { this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchInput = document.querySelector('.filtered-search'); + this.clearSearchButton = document.querySelector('.clear-search'); + + this.setupMapping(); this.bindEvents(); loadSearchParamsFromURL(); this.setDropdown(); - document.addEventListener('page:change', this.cleanup); + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('page:fetch', this.cleanupWrapper); } cleanup() { @@ -93,124 +92,105 @@ this.droplab = null; } - dropdownHint = null; - dropdownAuthor = null; - dropdownAssignee = null; - dropdownMilestone = null; - dropdownLabel = null; + this.setupMapping(); + + document.removeEventListener('page:fetch', this.cleanupWrapper); + } - document.removeEventListener('page:change', this.cleanup); + setupMapping() { + this.mapping = { + author: { + reference: null, + gl: 'DropdownAuthor', + element: document.querySelector('#js-dropdown-author'), + }, + assignee: { + reference: null, + gl: 'DropdownAssignee', + element: document.querySelector('#js-dropdown-assignee'), + }, + milestone: { + reference: null, + gl: 'DropdownMilestone', + element: document.querySelector('#js-dropdown-milestone'), + }, + label: { + reference: null, + gl: 'DropdownLabel', + element: document.querySelector('#js-dropdown-label'), + }, + hint: { + reference: null, + gl: 'DropdownHint', + element: document.querySelector('#js-dropdown-hint'), + }, + } } static addWordToInput(word, addSpace) { - const filteredSearchValue = document.querySelector('.filtered-search').value; + const filteredSearchInput = document.querySelector('.filtered-search') + const filteredSearchValue = filteredSearchInput.value; const hasExistingValue = filteredSearchValue.length !== 0; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); + if (lastToken.hasOwnProperty('key')) { console.log(lastToken); // Spaces inside the token means that the token value will be escaped by quotes const hasQuotes = lastToken.value.indexOf(' ') !== -1; - const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; - document.querySelector('.filtered-search').value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); + filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); } - document.querySelector('.filtered-search').value += hasExistingValue && addSpace ? ` ${word}` : word; + filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; } - loadDropdown(dropdownName = '', hideDropdown) { - let firstLoad = false; - const filteredSearch = document.querySelector('.filtered-search'); - - if(!this.droplab) { - firstLoad = true; - this.droplab = new DropLab(); - } - - dropdownName = dropdownName.toLowerCase(); - + load(key, firstLoad = false) { + console.log(`🦄 load ${key} dropdown`); + const glClass = this.mapping[key].gl; + const element = this.mapping[key].element; const filterIconPadding = 27; - const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName)[0]; + const dropdownOffset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; - if (!this.font) { - this.font = window.getComputedStyle(filteredSearch).font; + if (!this.mapping[key].reference) { + this.mapping[key].reference = new gl[glClass](this.droplab, element, this.filteredSearchInput); } - if (match && this.currentDropdown !== match.key) { - console.log(`🦄 load ${match.key} dropdown`); - - const dynamicDropdownPadding = 12; - const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding + dynamicDropdownPadding; - const dropdownAuthorElement = document.querySelector('#js-dropdown-author'); - const dropdownAssigneeElement = document.querySelector('#js-dropdown-assignee'); - const dropdownMilestoneElement = document.querySelector('#js-dropdown-milestone'); - const dropdownLabelElemenet = document.querySelector('#js-dropdown-label'); - - this.dismissCurrentDropdown(); - this.currentDropdown = match.key; - - if (match.key === 'author') { - if (!dropdownAuthor) { - dropdownAuthor = new gl.DropdownAuthor(this.droplab, dropdownAuthorElement, filteredSearch); - } - - dropdownAuthor.setOffset(dropdownOffset); - dropdownAuthor.render(); - } else if (match.key === 'assignee') { - if (!dropdownAssignee) { - dropdownAssignee = new gl.DropdownAssignee(this.droplab, dropdownAssigneeElement, filteredSearch); - } - - dropdownAssignee.setOffset(dropdownOffset); - dropdownAssignee.render(); - } else if (match.key === 'milestone') { - if (!dropdownMilestone) { - dropdownMilestone = new gl.DropdownMilestone(this.droplab, dropdownMilestoneElement, filteredSearch); - } + if (firstLoad) { + this.mapping[key].reference.configure(); + } - dropdownMilestone.setOffset(dropdownOffset); - dropdownMilestone.render(); - } else if (match.key === 'label') { - if (!dropdownLabel) { - dropdownLabel = new gl.DropdownLabel(this.droplab, dropdownLabelElemenet, filteredSearch); - } + this.mapping[key].reference.setOffset(dropdownOffset); + this.mapping[key].reference.render(firstLoad); - dropdownLabel.setOffset(dropdownOffset); - dropdownLabel.render(); - } + this.currentDropdown = key; + } - } else if (!match && this.currentDropdown !== 'hint') { - console.log('🦄 load hint dropdown'); + loadDropdown(dropdownName = '') { + let firstLoad = false; - const dropdownOffset = gl.text.getTextWidth(filteredSearch.value, this.font) + filterIconPadding; - const dropdownHintElement = document.querySelector('#js-dropdown-hint'); + if(!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } - this.dismissCurrentDropdown(); - this.currentDropdown = 'hint'; - if (!dropdownHint) { - dropdownHint = new gl.DropdownHint(this.droplab, dropdownHintElement, filteredSearch); - } + if (!this.font) { + this.font = window.getComputedStyle(this.filteredSearchInput).font; + } - if (firstLoad) { - dropdownHint.configure(); - } + const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); + const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; - dropdownHint.setOffset(dropdownOffset); - dropdownHint.render(firstLoad); + if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; + this.load(key, firstLoad); } - } - dismissCurrentDropdown() { - // if (this.currentDropdown === 'hint') { - // dropdownHint.hide(); - // } else if (this.currentDropdown === 'author') { - // // dropdownAuthor.hide(); - // } + gl.droplab = this.droplab; } setDropdown() { - const { lastToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); + const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); if (typeof lastToken === 'string') { // Token is not fully initialized yet @@ -228,32 +208,20 @@ } bindEvents() { - const filteredSearchInput = document.querySelector('.filtered-search'); - - filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); - filteredSearchInput.addEventListener('input', toggleClearSearchButton); - filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); - document.querySelector('.clear-search').addEventListener('click', this.clearSearch.bind(this)); + this.filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); + this.filteredSearchInput.addEventListener('input', toggleClearSearchButton); + this.filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); + this.clearSearchButton.addEventListener('click', this.clearSearch.bind(this)); } clearSearch(e) { e.stopPropagation(); e.preventDefault(); - document.querySelector('.filtered-search').value = ''; - document.querySelector('.clear-search').classList.add('hidden'); + this.filteredSearchInput.value = ''; + this.clearSearchButton.classList.add('hidden'); dropdownHint.resetFilters(); - this.loadDropdown('hint', true); - } - - checkDropdownToken(e) { - const input = e.target.value; - const { lastToken } = this.tokenizer.processTokens(input); - - // Check for dropdown token - if (lastToken[lastToken.length - 1] === ':') { - const token = lastToken.slice(0, -1); - } + this.loadDropdown('hint'); } checkForEnter(e) { diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 0a5de59cb63..53983ef8d6d 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -18,7 +18,7 @@ = icon('times') #js-dropdown-hint.dropdown-menu.hint-dropdown %ul - %li.filter-dropdown-item{ 'data-value': 'none' } + %li.filter-dropdown-item{ 'data-value': '' } %button.btn.btn-link = icon('search') %span -- cgit v1.2.3 From c18285cec0dadc0af8fa062a10d6cd581efb0b66 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 12:11:29 -0600 Subject: Fixed issue where dropdown would not open after clicking on a dropdown item --- app/assets/javascripts/droplab/droplab.js | 3 +-- app/views/shared/issuable/_search_bar.html.haml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index b17f156acb4..6b326338050 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -234,8 +234,7 @@ require('./window')(function(w){ var self = this; this.windowClickedWrapper = function(e){ var thisTag = e.target; - if(thisTag.tagName === 'LI' || thisTag.tagName === 'A' - || thisTag.tagName === 'BUTTON'){ + if(thisTag.tagName !== 'UL'){ // climb up the tree to find the UL thisTag = utils.closest(thisTag, 'UL'); } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 53983ef8d6d..2d2ecf030a8 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -17,13 +17,13 @@ %button.clear-search.hidden{ type: 'button' } = icon('times') #js-dropdown-hint.dropdown-menu.hint-dropdown - %ul + %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': '' } %button.btn.btn-link = icon('search') %span Keep typing and press Enter - %ul.filter-dropdown{ 'data-dynamic' => true } + %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link %i.fa{ 'class': '{{icon}}'} @@ -31,8 +31,8 @@ {{hint}} %span.js-filter-tag.dropdown-light-content {{tag}} - #js-dropdown-author.dropdown-menu{ 'data-dropdown' => true } - %ul.filter-dropdown{ 'data-dynamic' => true } + #js-dropdown-author.dropdown-menu + %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } @@ -41,13 +41,13 @@ {{name}} %span.dropdown-light-content @{{username}} - #js-dropdown-assignee.dropdown-menu{ 'data-dropdown' => true } - %ul + #js-dropdown-assignee.dropdown-menu + %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No assignee %li.divider - %ul.filter-dropdown{ 'data-dynamic' => true } + %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } @@ -57,22 +57,22 @@ %span.dropdown-light-content @{{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } - %ul + %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No milestone %li.divider - %ul.filter-dropdown{ 'data-dynamic' => true } + %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } - %ul + %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link No label %li.divider - %ul.filter-dropdown{ 'data-dynamic' => true } + %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} -- cgit v1.2.3 From f0608878ce8fd8dfaa91a285eff6d3db11f509c3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 13:15:09 -0600 Subject: Fix bug where dropdowns would not dismiss properly --- app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 | 2 +- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 9 ++++++--- app/assets/javascripts/filtered_search/dropdown_label.js.es6 | 3 ++- app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 | 2 +- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 7 +++++-- 5 files changed, 15 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index b2b03b637e7..850cca670e4 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -29,7 +29,7 @@ gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); } - this.dismissDropdown(); + this.dismissDropdown(!dataValueSet); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index f885267880a..ea384af09a9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -34,16 +34,19 @@ itemClicked(e) { const selected = e.detail.selected; - if (!selected.hasAttribute('data-value')) { + + if (selected.hasAttribute('data-value')) { + this.dismissDropdown(); + } else { const token = selected.querySelector('.js-filter-hint').innerText.trim(); const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); } + this.dismissDropdown(); + this.dispatchInputEvent(); } - - this.dismissDropdown(); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index 24a795808ca..c79df0aee4a 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -26,7 +26,8 @@ gl.FilteredSearchManager.addWordToInput(labelName); } - this.dismissDropdown(); + // debugger + this.dismissDropdown(!dataValueSet); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 458a9b1c5c1..10535097747 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -26,7 +26,7 @@ gl.FilteredSearchManager.addWordToInput(this.getSelectedText(milestoneName)); } - this.dismissDropdown(); + this.dismissDropdown(!dataValueSet); } renderContent() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 2f92c7b2e2a..cd46e430e01 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -77,11 +77,15 @@ } dismissDropdown() { + this.getCurrentHook().list.hide(); this.input.focus(); + } + + dispatchInputEvent() { // Propogate input change to FilteredSearchManager // so that it can determine which dropdowns to open this.input.dispatchEvent(new Event('input')); - } + } render(forceRenderContent) { this.setAsDropdown(); @@ -91,7 +95,6 @@ if (firstTimeInitialized || forceRenderContent) { this.renderContent(); } else if(this.getCurrentHook().list.list.id !== this.listId) { - // this.droplab.changeHookList(this.hookId, `#${this.listId}`); this.renderContent(); } } -- cgit v1.2.3 From ddc42a61f064757d69d4c61f784dacbcd1334d4d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 13:15:38 -0600 Subject: Fix bug where token values with 2 double quotes were not treated as a complete value --- .../filtered_search/filtered_search_tokenizer.es6 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index d6df83a3fb9..5ad433f4a09 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -119,19 +119,26 @@ const keyMatch = validTokenKeys.filter(v => v.key === tokenKey)[0]; const symbolMatch = validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; + const doubleQuoteOccurrences = tokenValue.split('"').length - 1; + const singleQuoteOccurrences = tokenValue.split('\'').length - 1; + const doubleQuoteIndex = tokenValue.indexOf('"'); const singleQuoteIndex = tokenValue.indexOf('\''); const doubleQuoteExist = doubleQuoteIndex !== -1; const singleQuoteExist = singleQuoteIndex !== -1; - if ((doubleQuoteExist && !singleQuoteExist) || - (doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex)) { + const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist; + const doubleQuoteIsBeforeSingleQuote = doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; + + const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist; + const singleQuoteIsBeforeDoubleQuote = doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; + + if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) && doubleQuoteOccurrences % 2 !== 0) { // " is found and is in front of ' (if any) lastQuotation = '"'; incompleteToken = true; - } else if ((singleQuoteExist && !doubleQuoteExist) || - (doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex)) { + } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) && singleQuoteOccurrences % 2 !== 0) { // ' is found and is in front of " (if any) lastQuotation = '\''; incompleteToken = true; -- cgit v1.2.3 From 002d17f1b7e4d298f45cb7d0dc944e20cbd734b5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 13:17:19 -0600 Subject: Fix clear button --- app/assets/javascripts/droplab/droplab.js | 51 ++++++++++++---------- .../filtered_search/dropdown_assignee.js.es6 | 4 +- .../filtered_search/dropdown_author.js.es6 | 4 +- .../filtered_search/dropdown_label.js.es6 | 3 +- .../filtered_search/dropdown_milestone.js.es6 | 3 +- .../filtered_search_dropdown.js.es6 | 35 +++++---------- .../filtered_search/filtered_search_manager.js.es6 | 17 ++++++-- .../filtered_search/filtered_search_tokenizer.es6 | 10 ++--- 8 files changed, 66 insertions(+), 61 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 6b326338050..42ddb7a4a56 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -51,26 +51,28 @@ Object.assign(DropDown.prototype, { return this.items; }, + clickEvent: function(e) { + // climb up the tree to find the LI + var selected = utils.closest(e.target, 'LI'); + + if(selected) { + e.preventDefault(); + this.hide(); + var listEvent = new CustomEvent('click.dl', { + detail: { + list: this, + selected: selected, + data: e.target.dataset, + }, + }); + this.list.dispatchEvent(listEvent); + } + }, + addEvents: function() { - var self = this; + this.clickWrapper = this.clickEvent.bind(this); // event delegation. - this.list.addEventListener('click', function(e) { - // climb up the tree to find the LI - var selected = utils.closest(e.target, 'LI'); - - if(selected) { - e.preventDefault(); - self.hide(); - var listEvent = new CustomEvent('click.dl', { - detail: { - list: self, - selected: selected, - data: e.target.dataset, - }, - }); - self.list.dispatchEvent(listEvent); - } - }); + this.list.addEventListener('click', this.clickWrapper); }, toggle: function() { @@ -93,6 +95,7 @@ Object.assign(DropDown.prototype, { // call render manually on data; render: function(data){ + // debugger // empty the list first var sampleItem; var newChildren = []; @@ -134,17 +137,23 @@ Object.assign(DropDown.prototype, { }, show: function() { + // debugger this.list.style.display = 'block'; this.hidden = false; }, hide: function() { + // debugger this.list.style.display = 'none'; this.hidden = true; }, destroy: function() { - this.hide(); + if (!this.hidden) { + this.hide(); + } + + this.list.removeEventListener('click', this.clickWrapper); } }); @@ -257,10 +266,6 @@ require('./window')(function(w){ // list = document.querySelector(list); this.hooks.every(function(hook, i) { if(hook.trigger === trigger) { - // Restore initial State - hook.list.list.innerHTML = hook.list.initialState; - hook.list.hide(); - hook.destroy(); this.hooks.splice(i, 1); this.addHook(trigger, list, plugins, config); diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index 850cca670e4..3420397edda 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -32,9 +32,9 @@ this.dismissDropdown(!dataValueSet); } - renderContent() { - // TODO: Pass elements instead of querySelectors + renderContent(forceShowList = false) { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); + super.renderContent(forceShowList); } getSearchInput() { diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 9bd49ab1a78..f1401f6f9d2 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -28,9 +28,9 @@ this.dismissDropdown(); } - renderContent() { - // TODO: Pass elements instead of querySelectors + renderContent(forceShowList) { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); + super.renderContent(forceShowList); } getSearchInput() { diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index c79df0aee4a..4c9926c1f78 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -30,8 +30,9 @@ this.dismissDropdown(!dataValueSet); } - renderContent() { + renderContent(forceShowList) { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + super.renderContent(forceShowList); } configure() { diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 10535097747..33967ddff24 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -29,8 +29,9 @@ this.dismissDropdown(!dataValueSet); } - renderContent() { + renderContent(forceShowList = false) { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + super.renderContent(forceShowList); } configure() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index cd46e430e01..163dac65842 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -4,6 +4,7 @@ class FilteredSearchDropdown { constructor(droplab, dropdown, input) { + console.log('constructor'); this.droplab = droplab; this.hookId = 'filtered-search'; this.input = input; @@ -54,8 +55,10 @@ // Overridden by dropdown sub class } - renderContent() { - // Overriden by dropdown sub class + renderContent(forceShowList = false) { + if (forceShowList && this.getCurrentHook().list.hidden) { + this.getCurrentHook().list.show(); + } } setAsDropdown() { @@ -77,7 +80,6 @@ } dismissDropdown() { - this.getCurrentHook().list.hide(); this.input.focus(); } @@ -87,31 +89,16 @@ this.input.dispatchEvent(new Event('input')); } - render(forceRenderContent) { + render(forceRenderContent = false, forceShowList = false) { this.setAsDropdown(); - const firstTimeInitialized = this.getCurrentHook() === undefined; - - if (firstTimeInitialized || forceRenderContent) { - this.renderContent(); - } else if(this.getCurrentHook().list.list.id !== this.listId) { - this.renderContent(); - } - } - - resetFilters() { const currentHook = this.getCurrentHook(); + const firstTimeInitialized = currentHook === undefined; - if (currentHook) { - const list = currentHook.list; - - if (list.data) { - const data = list.data.map((item) => { - item.droplab_hidden = false; - }); - - list.render(data); - } + if (firstTimeInitialized || forceRenderContent) { + this.renderContent(forceShowList); + } else if(currentHook.list.list.id !== this.listId) { + this.renderContent(forceShowList); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 9846f3ba50d..4f5d144bff3 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -150,6 +150,7 @@ const element = this.mapping[key].element; const filterIconPadding = 27; const dropdownOffset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; + let forceShowList = false; if (!this.mapping[key].reference) { this.mapping[key].reference = new gl[glClass](this.droplab, element, this.filteredSearchInput); @@ -159,8 +160,13 @@ this.mapping[key].reference.configure(); } + if (this.currentDropdown === 'hint') { + // Clicked from hint dropdown + forceShowList = true; + } + this.mapping[key].reference.setOffset(dropdownOffset); - this.mapping[key].reference.render(firstLoad); + this.mapping[key].reference.render(firstLoad, forceShowList); this.currentDropdown = key; } @@ -207,6 +213,12 @@ } } + // dismissCurrentDropdown() { + // if (this.currentDropdown === 'hint') { + // this.mapping['hint'].hide(); + // } + // } + bindEvents() { this.filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); this.filteredSearchInput.addEventListener('input', toggleClearSearchButton); @@ -220,8 +232,7 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); - dropdownHint.resetFilters(); - this.loadDropdown('hint'); + this.setDropdown(); } checkForEnter(e) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index 5ad433f4a09..4abb5e94d81 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -3,11 +3,11 @@ class FilteredSearchTokenizer { // TODO: Remove when going to pro static printTokens(tokens, searchToken, lastToken) { - // console.log('tokens:'); - // tokens.forEach(token => console.log(token)); - // console.log(`search: ${searchToken}`); - // console.log('last token:'); - // console.log(lastToken); + console.log('tokens:'); + tokens.forEach(token => console.log(token)); + console.log(`search: ${searchToken}`); + console.log('last token:'); + console.log(lastToken); } static parseToken(input) { -- cgit v1.2.3 From 21d1d9b2c0d794426de73aab6924dbbe05e61ba5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 15:10:15 -0600 Subject: Add username to page_filter_path --- app/helpers/application_helper.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app') diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c816b616631..a112928c6de 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -244,7 +244,9 @@ module ApplicationHelper scope: params[:scope], milestone_title: params[:milestone_title], assignee_id: params[:assignee_id], + assignee_username: params[:assignee_username], author_id: params[:author_id], + author_username: params[:author_username], search: params[:search], label_name: params[:label_name] } -- cgit v1.2.3 From 0eb9f53715a8fbbe8ede0043aad5a9c9377716e5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 15:20:41 -0600 Subject: Prevent droplab from opening dropdown by cleaning it --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 4f5d144bff3..91de7783cc1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -240,6 +240,10 @@ if (e.keyCode === 13) { e.stopPropagation(); e.preventDefault(); + + // Prevent droplab from opening dropdown + this.droplab.destroy(); + this.search(); } } -- cgit v1.2.3 From e5b061b801ee7b806dd401c97a5b268e3f34859a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 15:36:44 -0600 Subject: Reposition dropdown when backspace is hit --- .../filtered_search/filtered_search_manager.js.es6 | 45 ++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 91de7783cc1..d21ae70cdb9 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -94,6 +94,7 @@ this.setupMapping(); + this.unbindEvents(); document.removeEventListener('page:fetch', this.cleanupWrapper); } @@ -144,12 +145,17 @@ filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; } + updateDropdownOffset(key) { + const filterIconPadding = 27; + const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; + + this.mapping[key].reference.setOffset(offset); + } + load(key, firstLoad = false) { console.log(`🦄 load ${key} dropdown`); const glClass = this.mapping[key].gl; const element = this.mapping[key].element; - const filterIconPadding = 27; - const dropdownOffset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; let forceShowList = false; if (!this.mapping[key].reference) { @@ -165,7 +171,7 @@ forceShowList = true; } - this.mapping[key].reference.setOffset(dropdownOffset); + this.updateDropdownOffset(key); this.mapping[key].reference.render(firstLoad, forceShowList); this.currentDropdown = key; @@ -213,17 +219,25 @@ } } - // dismissCurrentDropdown() { - // if (this.currentDropdown === 'hint') { - // this.mapping['hint'].hide(); - // } - // } - bindEvents() { - this.filteredSearchInput.addEventListener('input', this.setDropdown.bind(this)); + this.setDropdownWrapper = this.setDropdown.bind(this); + this.checkForEnterWrapper = this.checkForEnter.bind(this); + this.clearSearchWrapper = this.clearSearch.bind(this); + this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); + + this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.addEventListener('input', toggleClearSearchButton); - this.filteredSearchInput.addEventListener('keydown', this.checkForEnter.bind(this)); - this.clearSearchButton.addEventListener('click', this.clearSearch.bind(this)); + this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); + this.clearSearchButton.addEventListener('click', this.clearSearchWrapper); + } + + unbindEvents() { + this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); + this.filteredSearchInput.removeEventListener('input', toggleClearSearchButton); + this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); + this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); + this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper); } clearSearch(e) { @@ -235,6 +249,13 @@ this.setDropdown(); } + checkForBackspace(e) { + if (e.keyCode === 8) { + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); + } + } + checkForEnter(e) { // Enter KeyCode if (e.keyCode === 13) { -- cgit v1.2.3 From 88ed015915e63c07181122c461523d3e1610e98e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 15:47:56 -0600 Subject: Fix clear button so that it resets the dropdowns properly --- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 4 ++++ .../javascripts/filtered_search/filtered_search_manager.js.es6 | 8 ++++++++ 2 files changed, 12 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 163dac65842..03835b6522b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -117,6 +117,10 @@ item.droplab_hidden = !match && !matchWithoutPrefix; return item; } + + hideDropdown() { + this.getCurrentHook().list.hide(); + } } global.FilteredSearchDropdown = FilteredSearchDropdown; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d21ae70cdb9..0654d7d816a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -246,7 +246,15 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); + + // Force dropdown to hide + this.mapping[this.currentDropdown].reference.hideDropdown(); + + // Re-Load dropdown this.setDropdown(); + + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); } checkForBackspace(e) { -- cgit v1.2.3 From 98a95633f558f4f4762d4e523ef0c495d6f79f68 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 9 Dec 2016 16:02:43 -0600 Subject: Reset filters when clear search is clicked --- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 9 +++++++++ .../javascripts/filtered_search/filtered_search_manager.js.es6 | 3 +++ 2 files changed, 12 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 03835b6522b..5186c15cb67 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -121,6 +121,15 @@ hideDropdown() { this.getCurrentHook().list.hide(); } + + resetFilters() { + const hook = this.getCurrentHook(); + const data = hook.list.data; + const results = data.map(function(o) { + o.droplab_hidden = false; + }); + hook.list.render(results); + } } global.FilteredSearchDropdown = FilteredSearchDropdown; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 0654d7d816a..c7e01fc710d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -247,6 +247,9 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); + // Reset Filters + this.mapping[this.currentDropdown].reference.resetFilters(); + // Force dropdown to hide this.mapping[this.currentDropdown].reference.hideDropdown(); -- cgit v1.2.3 From 7b202460f7333e1a9997f1ee79eca71975cc676f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 11:13:17 -0600 Subject: Fix ajax bug --- app/assets/javascripts/droplab/droplab.js | 7 +------ app/assets/javascripts/droplab/droplab_ajax_filter.js | 9 +++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 42ddb7a4a56..359cd82bbcd 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -31,18 +31,13 @@ if ( typeof CustomEvent === "function" ) { var CustomEvent = require('./custom_event_polyfill'); var utils = require('./utils'); -var DropDown = function(list, trigger) { +var DropDown = function(list) { this.hidden = true; this.list = list; - this.trigger = trigger; this.items = []; this.getItems(); this.addEvents(); this.initialState = list.innerHTML; - - if (this.initialState.indexOf('{{') == -1) { - debugger - } }; Object.assign(DropDown.prototype, { diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index c345fda1075..0d6a7892bdc 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -5,6 +5,7 @@ require('../window')(function(w){ w.droplabAjaxFilter = { init: function(hook) { + this.destroyed = false; this.hook = hook; this.notLoading(); @@ -49,14 +50,16 @@ require('../window')(function(w){ } this.loading = true; + this.hook.list.setData([]); var params = config.params || {}; params[config.searchKey] = searchValue; var self = this; this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { - self.hook.restoreInitialState.call(self.hook); - self.hook.list.setData.call(self.hook.list, data[0]); + if (!self.destroyed) { + self.hook.list.setData.call(self.hook.list, data[0]); + } self.notLoading(); }); }, @@ -92,6 +95,8 @@ require('../window')(function(w){ clearTimeout(this.timeout); } + this.destroyed = true; + this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper); this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper); } -- cgit v1.2.3 From 99ffd0d44e0c813f9c4216b01fa321a5409ed360 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 13:35:39 -0600 Subject: Remove ajax clear setData for smoother ux --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 0d6a7892bdc..7603556d2ef 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -51,8 +51,6 @@ require('../window')(function(w){ this.loading = true; - this.hook.list.setData([]); - var params = config.params || {}; params[config.searchKey] = searchValue; var self = this; -- cgit v1.2.3 From 85f1793590b31171bac3c1e1f5d4420d96302551 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 13:47:36 -0600 Subject: Pass project ID through the DOM --- app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 | 2 +- app/assets/javascripts/filtered_search/dropdown_author.js.es6 | 2 +- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 4 ++++ app/views/shared/issuable/_search_bar.html.haml | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index 3420397edda..ff3fd3a4e2b 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -13,7 +13,7 @@ params: { per_page: 20, active: true, - project_id: 2, + project_id: this.getProjectId(), current_user: true, }, searchValueFunction: this.getSearchInput, diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index f1401f6f9d2..517cbab8ee7 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -13,7 +13,7 @@ params: { per_page: 20, active: true, - project_id: 2, + project_id: this.getProjectId(), current_user: true, }, searchValueFunction: this.getSearchInput, diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 5186c15cb67..478c4e6bf92 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -21,6 +21,10 @@ this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); } + getProjectId() { + return this.input.getAttribute('data-project-id'); + } + getCurrentHook() { return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 2d2ecf030a8..86692e77697 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -12,7 +12,7 @@ class: "check_all_issues left" .issues-other-filters.filtered-search-container .filtered-search-input-container - %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search' } + %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id } = icon('filter') %button.clear-search.hidden{ type: 'button' } = icon('times') -- cgit v1.2.3 From 575d4491cac225544081135820a1fa53a72d4709 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 13:55:41 -0600 Subject: Fixed bug where filters were not being reset after being cleared --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c7e01fc710d..5d38a23d9fd 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -247,15 +247,16 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); - // Reset Filters - this.mapping[this.currentDropdown].reference.resetFilters(); - // Force dropdown to hide + // Force current dropdown to hide this.mapping[this.currentDropdown].reference.hideDropdown(); // Re-Load dropdown this.setDropdown(); + // Reset filters for current dropdown + this.mapping[this.currentDropdown].reference.resetFilters(); + // Reposition dropdown so that it is aligned with cursor this.updateDropdownOffset(this.currentDropdown); } -- cgit v1.2.3 From a7ecbf7c79a4789d10493ad3bdb9b02cfd124334 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 14:04:16 -0600 Subject: Add missing space for extracting params --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 5d38a23d9fd..055f229cd45 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -33,6 +33,7 @@ if (validCondition) { inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; + inputValue += ' '; } else { // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + -- cgit v1.2.3 From cf3504ed602aa1b979f13a394192dde50b8cffed Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 14:14:21 -0600 Subject: Make ajax filter more consistent and only filter when typed --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 7603556d2ef..f2720a0371b 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -12,7 +12,7 @@ require('../window')(function(w){ this.debounceTriggerWrapper = this.debounceTrigger.bind(this); this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper); this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper); - this.trigger(); + this.trigger(true); }, notLoading: function notLoading() { @@ -22,6 +22,7 @@ require('../window')(function(w){ debounceTrigger: function debounceTrigger(e) { var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93]; var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1; + var focusEvent = false; if (invalidKeyPressed || this.loading) { return; } @@ -30,10 +31,14 @@ require('../window')(function(w){ clearTimeout(this.timeout); } - this.timeout = setTimeout(this.trigger.bind(this), 200); + if (e.type === 'focus') { + focusEvent = true; + } + + this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200); }, - trigger: function trigger() { + trigger: function trigger(getEntireList = false) { var config = this.hook.config.droplabAjaxFilter; var searchValue = this.trigger.value; @@ -45,6 +50,10 @@ require('../window')(function(w){ searchValue = config.searchValueFunction(); } + if (getEntireList) { + searchValue = ''; + } + if (searchValue === config.searchKey) { return this.list.show(); } -- cgit v1.2.3 From efb668208ae25393cd5535ea769c537c55a54313 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 15:37:26 -0600 Subject: Add loading message for droplab_ajax --- app/assets/javascripts/droplab/droplab_ajax.js | 15 +++++++++++++++ .../javascripts/filtered_search/dropdown_label.js.es6 | 4 ++++ .../javascripts/filtered_search/dropdown_milestone.js.es6 | 4 ++++ app/assets/stylesheets/framework/filters.scss | 4 ++++ 4 files changed, 27 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index b81663c281d..629260006f3 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -23,6 +23,7 @@ require('../window')(function(w){ }, init: function init(hook) { + var self = this; var config = hook.config.droplabAjax; if (!config || !config.endpoint || !config.method) { @@ -33,7 +34,21 @@ require('../window')(function(w){ return; } + if (config.loadingTemplate) { + var dynamicList = hook.list.list.querySelector('[data-dynamic]'); + + var loadingTemplate = document.createElement('div'); + loadingTemplate.innerHTML = config.loadingTemplate; + loadingTemplate.setAttribute('data-loading-template', true); + + this.listTemplate = dynamicList.outerHTML; + dynamicList.outerHTML = loadingTemplate.outerHTML; + } + this._loadUrlData(config.endpoint).then(function(d) { + if (config.loadingTemplate) { + hook.list.list.querySelector('[data-loading-template]').outerHTML = self.listTemplate; + } hook.list[config.method].call(hook.list, d); }).catch(function(e) { if(e.message) { diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index 4c9926c1f78..0912336b6cf 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -10,6 +10,10 @@ droplabAjax: { endpoint: 'labels.json', method: 'setData', + loadingTemplate: ` +
+ +
`, }, droplabFilter: { filterFunction: this.filterWithSymbol.bind(this, '~'), diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 33967ddff24..73d67573868 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -10,6 +10,10 @@ droplabAjax: { endpoint: 'milestones.json', method: 'setData', + loadingTemplate: ` +
+ +
`, }, droplabFilter: { filterFunction: this.filterWithSymbol.bind(this, '%'), diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 205cecb4906..b6c137d647a 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -121,3 +121,7 @@ .hint-dropdown { width: 250px; } + +.filter-dropdown-loading { + padding: 8px 16px; +} -- cgit v1.2.3 From 5066366162cc1da1004bc0d3df4a6377d68dbce4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 15:37:47 -0600 Subject: Add loading template to droplab_ajax_filter --- .../javascripts/droplab/droplab_ajax_filter.js | 24 +++++++++++++++++++++- .../filtered_search/dropdown_assignee.js.es6 | 4 ++++ .../filtered_search/dropdown_author.js.es6 | 4 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index f2720a0371b..8d024c4b6d7 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -50,6 +50,18 @@ require('../window')(function(w){ searchValue = config.searchValueFunction(); } + if (config.loadingTemplate && this.hook.list.data === undefined || + this.hook.list.data.length === 0) { + var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); + + var loadingTemplate = document.createElement('div'); + loadingTemplate.innerHTML = config.loadingTemplate; + loadingTemplate.setAttribute('data-loading-template', true); + + this.listTemplate = dynamicList.outerHTML; + dynamicList.outerHTML = loadingTemplate.outerHTML; + } + if (getEntireList) { searchValue = ''; } @@ -64,8 +76,18 @@ require('../window')(function(w){ params[config.searchKey] = searchValue; var self = this; this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { + if (config.loadingTemplate && self.hook.list.data === undefined || + self.hook.list.data.length === 0) { + self.hook.list.list.querySelector('[data-loading-template]').outerHTML = self.listTemplate; + } + if (!self.destroyed) { - self.hook.list.setData.call(self.hook.list, data[0]); + if (data[0].length === 0) { + self.hook.list.hide(); + } else { + self.hook.list.show(); + self.hook.list.setData.call(self.hook.list, data[0]); + } } self.notLoading(); }); diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index ff3fd3a4e2b..edc717304b2 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -17,6 +17,10 @@ current_user: true, }, searchValueFunction: this.getSearchInput, + loadingTemplate: ` +
+ +
`, } }; } diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 517cbab8ee7..8d95a879c79 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -17,6 +17,10 @@ current_user: true, }, searchValueFunction: this.getSearchInput, + loadingTemplate: ` +
+ +
`, } }; } -- cgit v1.2.3 From 0791116f5f6a6f3622af6d1caa9ba9da1818275f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 15:40:12 -0600 Subject: Refactor loadingTemplate to abstract class --- app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 | 5 +---- app/assets/javascripts/filtered_search/dropdown_author.js.es6 | 5 +---- app/assets/javascripts/filtered_search/dropdown_label.js.es6 | 5 +---- app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 | 5 +---- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 3 +++ 5 files changed, 7 insertions(+), 16 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 index edc717304b2..0ce4eebedc9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 @@ -17,10 +17,7 @@ current_user: true, }, searchValueFunction: this.getSearchInput, - loadingTemplate: ` -
- -
`, + loadingTemplate: this.loadingTemplate, } }; } diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 index 8d95a879c79..3dc649cc17d 100644 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 @@ -17,10 +17,7 @@ current_user: true, }, searchValueFunction: this.getSearchInput, - loadingTemplate: ` -
- -
`, + loadingTemplate: this.loadingTemplate, } }; } diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 index 0912336b6cf..bf009454de5 100644 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 @@ -10,10 +10,7 @@ droplabAjax: { endpoint: 'labels.json', method: 'setData', - loadingTemplate: ` -
- -
`, + loadingTemplate: this.loadingTemplate, }, droplabFilter: { filterFunction: this.filterWithSymbol.bind(this, '~'), diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 index 73d67573868..7f5822aed84 100644 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 @@ -10,10 +10,7 @@ droplabAjax: { endpoint: 'milestones.json', method: 'setData', - loadingTemplate: ` -
- -
`, + loadingTemplate: this.loadingTemplate, }, droplabFilter: { filterFunction: this.filterWithSymbol.bind(this, '%'), diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 478c4e6bf92..6b713a7017e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -9,6 +9,9 @@ this.hookId = 'filtered-search'; this.input = input; this.dropdown = dropdown; + this.loadingTemplate = `
+ +
`; this.bindEvents(); } -- cgit v1.2.3 From f67316a7cb19374812bfc61dc98ece3110538e1b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 16:04:58 -0600 Subject: Hide list if it is dynamic and there are no items to render --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 8d024c4b6d7..bdd9b059bb3 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -82,12 +82,16 @@ require('../window')(function(w){ } if (!self.destroyed) { - if (data[0].length === 0) { + var hookListChildren = self.hook.list.list.children; + var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); + + if (onlyDynamicList && data[0].length === 0) { self.hook.list.hide(); - } else { + } else if (onlyDynamicList && data[0].length !== 0) { self.hook.list.show(); - self.hook.list.setData.call(self.hook.list, data[0]); } + + self.hook.list.setData.call(self.hook.list, data[0]); } self.notLoading(); }); -- cgit v1.2.3 From 7eb888e60e506ab25ba3593cde53dc94c19b9d7e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 16:06:45 -0600 Subject: Only return data response for droplab ajax filter --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index bdd9b059bb3..943ee9fa0a4 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -85,13 +85,13 @@ require('../window')(function(w){ var hookListChildren = self.hook.list.list.children; var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); - if (onlyDynamicList && data[0].length === 0) { + if (onlyDynamicList && data.length === 0) { self.hook.list.hide(); - } else if (onlyDynamicList && data[0].length !== 0) { + } else if (onlyDynamicList && data.length !== 0) { self.hook.list.show(); } - self.hook.list.setData.call(self.hook.list, data[0]); + self.hook.list.setData.call(self.hook.list, data); } self.notLoading(); }); @@ -105,7 +105,7 @@ require('../window')(function(w){ if(xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); - return resolve([data, xhr]); + return resolve(data); } else { return reject([xhr.responseText, xhr.status]); } -- cgit v1.2.3 From 172ce7001ae606f7f0df56aedc6c5d6ad1d9305c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 18:42:20 -0600 Subject: Fix casing and add upcoming milestone filter --- app/views/shared/issuable/_search_bar.html.haml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 86692e77697..7ebc4d6b153 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -45,7 +45,7 @@ %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link - No assignee + No Assignee %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item @@ -60,7 +60,10 @@ %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link - No milestone + No Milestone + %li.filter-dropdown-item{ 'data-value': 'upcoming' } + %button.btn.btn-link + Upcoming %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item @@ -70,7 +73,7 @@ %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value': 'none' } %button.btn.btn-link - No label + No Label %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item -- cgit v1.2.3 From acfe967eb587078672953971673e2cd2c02b43ee Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 10 Dec 2016 18:53:56 -0600 Subject: Add mobile viewport --- app/assets/stylesheets/framework/mobile.scss | 6 +++++- app/views/shared/issuable/_search_bar.html.haml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 7eb9962ba33..db3bf9f86c4 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -23,12 +23,16 @@ margin-right: 0; } - .issues-details-filters, + .issues-details-filters:not(.filtered-search-block), .dash-projects-filters, .check-all-holder { display: none; } + .issues-holder .issue-check { + display: none; + } + .rss-btn { display: none; } diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 7ebc4d6b153..f7c72e3ced8 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -2,7 +2,7 @@ - boards_page = controller.controller_name == 'boards' .issues-filters - .issues-details-filters.row-content-block.second-block + .issues-details-filters.row-content-block.second-block.filtered-search-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do - if params[:search].present? = hidden_field_tag :search, params[:search] -- cgit v1.2.3 From 5c0802deae6a3a87e7a497d3250cbeb98e61045b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 11 Dec 2016 18:05:33 -0600 Subject: Add check in case the data attribute does not exist --- app/assets/javascripts/droplab/droplab_ajax.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index 629260006f3..ebb518eeef4 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -47,7 +47,11 @@ require('../window')(function(w){ this._loadUrlData(config.endpoint).then(function(d) { if (config.loadingTemplate) { - hook.list.list.querySelector('[data-loading-template]').outerHTML = self.listTemplate; + var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } } hook.list[config.method].call(hook.list, d); }).catch(function(e) { -- cgit v1.2.3 From 46a1f36986aa61597f54d14c76b1d2258b0933e5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 11 Dec 2016 18:05:55 -0600 Subject: Refactor dropdown filters --- .../filtered_search/dropdown_assignee.js.es6 | 61 ---------------------- .../filtered_search/dropdown_author.js.es6 | 57 -------------------- .../filtered_search/dropdown_hint.js.es6 | 2 +- .../filtered_search/dropdown_label.js.es6 | 45 ---------------- .../filtered_search/dropdown_milestone.js.es6 | 44 ---------------- .../filtered_search/dropdown_non_user.js.es6 | 44 ++++++++++++++++ .../filtered_search/dropdown_user.js.es6 | 57 ++++++++++++++++++++ .../filtered_search/filtered_search_manager.js.es6 | 16 ++++-- app/views/shared/issuable/_search_bar.html.haml | 4 +- 9 files changed, 115 insertions(+), 215 deletions(-) delete mode 100644 app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 delete mode 100644 app/assets/javascripts/filtered_search/dropdown_author.js.es6 delete mode 100644 app/assets/javascripts/filtered_search/dropdown_label.js.es6 delete mode 100644 app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 create mode 100644 app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 create mode 100644 app/assets/javascripts/filtered_search/dropdown_user.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 b/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 deleted file mode 100644 index 0ce4eebedc9..00000000000 --- a/app/assets/javascripts/filtered_search/dropdown_assignee.js.es6 +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable no-param-reassign */ -/*= require filtered_search/filtered_search_dropdown */ - -((global) => { - class DropdownAssignee extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); - this.listId = 'js-dropdown-assignee'; - this.config = { - droplabAjaxFilter: { - endpoint: '/autocomplete/users.json', - searchKey: 'search', - params: { - per_page: 20, - active: true, - project_id: this.getProjectId(), - current_user: true, - }, - searchValueFunction: this.getSearchInput, - loadingTemplate: this.loadingTemplate, - } - }; - } - - itemClicked(e) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); - - if (!dataValueSet) { - const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); - } - - this.dismissDropdown(!dataValueSet); - } - - renderContent(forceShowList = false) { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); - super.renderContent(forceShowList); - } - - getSearchInput() { - const query = document.querySelector('.filtered-search').value; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1); - const hasPrefix = valueWithoutColon[0] === '@'; - const valueWithoutPrefix = valueWithoutColon.slice(1); - - if (hasPrefix) { - return valueWithoutPrefix; - } else { - return valueWithoutColon; - } - } - - configure() { - this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); - } - } - - global.DropdownAssignee = DropdownAssignee; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 b/app/assets/javascripts/filtered_search/dropdown_author.js.es6 deleted file mode 100644 index 3dc649cc17d..00000000000 --- a/app/assets/javascripts/filtered_search/dropdown_author.js.es6 +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable no-param-reassign */ -/*= require filtered_search/filtered_search_dropdown */ - -((global) => { - class DropdownAuthor extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); - this.listId = 'js-dropdown-author'; - this.config = { - droplabAjaxFilter: { - endpoint: '/autocomplete/users.json', - searchKey: 'search', - params: { - per_page: 20, - active: true, - project_id: this.getProjectId(), - current_user: true, - }, - searchValueFunction: this.getSearchInput, - loadingTemplate: this.loadingTemplate, - } - }; - } - - itemClicked(e) { - const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); - - this.dismissDropdown(); - } - - renderContent(forceShowList) { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); - super.renderContent(forceShowList); - } - - getSearchInput() { - const query = document.querySelector('.filtered-search').value; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1); - const hasPrefix = valueWithoutColon[0] === '@'; - const valueWithoutPrefix = valueWithoutColon.slice(1); - - if (hasPrefix) { - return valueWithoutPrefix; - } else { - return valueWithoutColon; - } - } - - configure() { - this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); - } - } - - global.DropdownAuthor = DropdownAuthor; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index ea384af09a9..d445a796f43 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -23,7 +23,7 @@ class DropdownHint extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { super(droplab, dropdown, input); - this.listId = 'js-dropdown-hint'; + this.listId = dropdown.id; this.config = { droplabFilter: { template: 'hint', diff --git a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 b/app/assets/javascripts/filtered_search/dropdown_label.js.es6 deleted file mode 100644 index bf009454de5..00000000000 --- a/app/assets/javascripts/filtered_search/dropdown_label.js.es6 +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable no-param-reassign */ -/*= require filtered_search/filtered_search_dropdown */ - -((global) => { - class DropdownLabel extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); - this.listId = 'js-dropdown-label'; - this.config = { - droplabAjax: { - endpoint: 'labels.json', - method: 'setData', - loadingTemplate: this.loadingTemplate, - }, - droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this, '~'), - } - }; - } - - itemClicked(e) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); - - if (!dataValueSet) { - const labelTitle = e.detail.selected.querySelector('.label-title').innerText.trim(); - const labelName = `~${this.getEscapedText(labelTitle)}`; - gl.FilteredSearchManager.addWordToInput(labelName); - } - - // debugger - this.dismissDropdown(!dataValueSet); - } - - renderContent(forceShowList) { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); - super.renderContent(forceShowList); - } - - configure() { - this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); - } - } - - global.DropdownLabel = DropdownLabel; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 b/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 deleted file mode 100644 index 7f5822aed84..00000000000 --- a/app/assets/javascripts/filtered_search/dropdown_milestone.js.es6 +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable no-param-reassign */ -/*= require filtered_search/filtered_search_dropdown */ - -((global) => { - class DropdownMilestone extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); - this.listId = 'js-dropdown-milestone'; - this.config = { - droplabAjax: { - endpoint: 'milestones.json', - method: 'setData', - loadingTemplate: this.loadingTemplate, - }, - droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this, '%'), - } - }; - } - - itemClicked(e) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); - - if (!dataValueSet) { - const milestoneTitle = e.detail.selected.querySelector('.btn-link').innerText.trim(); - const milestoneName = `%${this.getEscapedText(milestoneTitle)}`; - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(milestoneName)); - } - - this.dismissDropdown(!dataValueSet); - } - - renderContent(forceShowList = false) { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); - super.renderContent(forceShowList); - } - - configure() { - this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); - } - } - - global.DropdownMilestone = DropdownMilestone; -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 new file mode 100644 index 00000000000..05c9284bc96 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -0,0 +1,44 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownNonUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input, endpoint, symbol) { + super(droplab, dropdown, input); + this.listId = dropdown.id; + this.config = { + droplabAjax: { + endpoint: endpoint, + method: 'setData', + loadingTemplate: this.loadingTemplate, + }, + droplabFilter: { + filterFunction: this.filterWithSymbol.bind(this, symbol), + } + }; + } + + itemClicked(e) { + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); + const name = `%${this.getEscapedText(title)}`; + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(name)); + } + + this.dismissDropdown(!dataValueSet); + } + + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + super.renderContent(forceShowList); + } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); + } + } + + global.DropdownNonUser = DropdownNonUser; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 new file mode 100644 index 00000000000..1a597bbbc9d --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -0,0 +1,57 @@ +/* eslint-disable no-param-reassign */ +/*= require filtered_search/filtered_search_dropdown */ + +((global) => { + class DropdownUser extends gl.FilteredSearchDropdown { + constructor(droplab, dropdown, input) { + super(droplab, dropdown, input); + this.listId = dropdown.id; + this.config = { + droplabAjaxFilter: { + endpoint: '/autocomplete/users.json', + searchKey: 'search', + params: { + per_page: 20, + active: true, + project_id: this.getProjectId(), + current_user: true, + }, + searchValueFunction: this.getSearchInput, + loadingTemplate: this.loadingTemplate, + }, + }; + } + + itemClicked(e) { + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); + gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); + } + + this.dismissDropdown(!dataValueSet); + } + + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); + super.renderContent(forceShowList); + } + + getSearchInput() { + const query = document.querySelector('.filtered-search').value; + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutColon = value.slice(1); + const hasPrefix = valueWithoutColon[0] === '@'; + const valueWithoutPrefix = valueWithoutColon.slice(1); + + return hasPrefix ? valueWithoutPrefix : valueWithoutColon; + } + + configure() { + this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); + } + } + + global.DropdownUser = DropdownUser; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 055f229cd45..c92d669114e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -103,22 +103,24 @@ this.mapping = { author: { reference: null, - gl: 'DropdownAuthor', + gl: 'DropdownUser', element: document.querySelector('#js-dropdown-author'), }, assignee: { reference: null, - gl: 'DropdownAssignee', + gl: 'DropdownUser', element: document.querySelector('#js-dropdown-assignee'), }, milestone: { reference: null, - gl: 'DropdownMilestone', + gl: 'DropdownNonUser', + extraArguments: ['milestones.json', '%'], element: document.querySelector('#js-dropdown-milestone'), }, label: { reference: null, - gl: 'DropdownLabel', + gl: 'DropdownNonUser', + extraArguments: ['labels.json', '~'], element: document.querySelector('#js-dropdown-label'), }, hint: { @@ -160,7 +162,11 @@ let forceShowList = false; if (!this.mapping[key].reference) { - this.mapping[key].reference = new gl[glClass](this.droplab, element, this.filteredSearchInput); + var dl = this.droplab; + const defaultArguments = [null, dl, element, this.filteredSearchInput]; + const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); + + this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); } if (firstLoad) { diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index f7c72e3ced8..335552c0a26 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -67,7 +67,7 @@ %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item - %button.btn.btn-link + %button.btn.btn-link.js-data-value {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true } @@ -79,7 +79,7 @@ %li.filter-dropdown-item %button.btn.btn-link %span.dropdown-label-box{ 'style': 'background: {{color}}'} - %span.label-title + %span.label-title.js-data-value {{title}} .pull-right - if boards_page -- cgit v1.2.3 From f4db75728e8c16876cb3f74e12d4d707ab8f47c1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 11 Dec 2016 18:11:58 -0600 Subject: Refactor filtered_search_dropdown --- .../filtered_search/dropdown_non_user.js.es6 | 39 ++++++++++++++++++++-- .../filtered_search/dropdown_user.js.es6 | 4 +++ .../filtered_search_dropdown.js.es6 | 39 ---------------------- 3 files changed, 41 insertions(+), 41 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 05c9284bc96..f03c27c3ec0 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -6,6 +6,7 @@ constructor(droplab, dropdown, input, endpoint, symbol) { super(droplab, dropdown, input); this.listId = dropdown.id; + this.symbol = symbol; this.config = { droplabAjax: { endpoint: endpoint, @@ -13,7 +14,7 @@ loadingTemplate: this.loadingTemplate, }, droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this, symbol), + filterFunction: this.filterWithSymbol.bind(this, this.symbol), } }; } @@ -23,13 +24,47 @@ if (!dataValueSet) { const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); - const name = `%${this.getEscapedText(title)}`; + const name = `${this.symbol}${this.getEscapedText(title)}`; gl.FilteredSearchManager.addWordToInput(this.getSelectedText(name)); } this.dismissDropdown(!dataValueSet); } + getEscapedText(text) { + let escapedText = text; + + // Encapsulate value with quotes if it has spaces + if (text.indexOf(' ') !== -1) { + if (text.indexOf('"') !== -1) { + // Use single quotes if value contains double quotes + escapedText = `'${text}'`; + } else { + // Known side effect: values's with both single and double quotes + // won't escape properly + escapedText = `"${text}"`; + } + } + + return escapedText; + } + + filterWithSymbol(filterSymbol, item, query) { + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutColon = value.slice(1).toLowerCase(); + const prefix = valueWithoutColon[0]; + const valueWithoutPrefix = valueWithoutColon.slice(1); + + const title = item.title.toLowerCase(); + + // Eg. filterSymbol = ~ for labels + const matchWithoutPrefix = prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; + const match = title.indexOf(valueWithoutColon) !== -1; + + item.droplab_hidden = !match && !matchWithoutPrefix; + return item; + } + renderContent(forceShowList = false) { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); super.renderContent(forceShowList); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 1a597bbbc9d..6827ab1658a 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -38,6 +38,10 @@ super.renderContent(forceShowList); } + getProjectId() { + return this.input.getAttribute('data-project-id'); + } + getSearchInput() { const query = document.querySelector('.filtered-search').value; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 6b713a7017e..c63ba1acf0b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -4,7 +4,6 @@ class FilteredSearchDropdown { constructor(droplab, dropdown, input) { - console.log('constructor'); this.droplab = droplab; this.hookId = 'filtered-search'; this.input = input; @@ -24,32 +23,10 @@ this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper); } - getProjectId() { - return this.input.getAttribute('data-project-id'); - } - getCurrentHook() { return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; } - getEscapedText(text) { - let escapedText = text; - - // Encapsulate value with quotes if it has spaces - if (text.indexOf(' ') !== -1) { - if (text.indexOf('"') !== -1) { - // Use single quotes if value contains double quotes - escapedText = `'${text}'`; - } else { - // Known side effect: values's with both single and double quotes - // won't escape properly - escapedText = `"${text}"`; - } - } - - return escapedText; - } - getSelectedText(selectedToken) { // TODO: Get last word from FilteredSearchTokenizer const lastWord = this.input.value.split(' ').last(); @@ -109,22 +86,6 @@ } } - filterWithSymbol(filterSymbol, item, query) { - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); - const prefix = valueWithoutColon[0]; - const valueWithoutPrefix = valueWithoutColon.slice(1); - - const title = item.title.toLowerCase(); - - // Eg. filterSymbol = ~ for labels - const matchWithoutPrefix = prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; - const match = title.indexOf(valueWithoutColon) !== -1; - - item.droplab_hidden = !match && !matchWithoutPrefix; - return item; - } - hideDropdown() { this.getCurrentHook().list.hide(); } -- cgit v1.2.3 From 513cdda31667f0058b24e8f66d87ddfcf89b7fb4 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 09:21:38 -0600 Subject: Refactor filtered search manager --- .../filtered_search/dropdown_hint.js.es6 | 2 +- .../filtered_search/dropdown_non_user.js.es6 | 2 +- .../filtered_search/dropdown_user.js.es6 | 2 +- .../filtered_search_dropdown.js.es6 | 2 +- .../filtered_search_dropdown_manager.js.es6 | 174 +++++++++++++++++++++ .../filtered_search/filtered_search_manager.js.es6 | 160 +------------------ 6 files changed, 185 insertions(+), 157 deletions(-) create mode 100644 app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index d445a796f43..53952e6bc63 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -42,7 +42,7 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(token)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(token)); } this.dismissDropdown(); this.dispatchInputEvent(); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index f03c27c3ec0..e4df39cfde1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -25,7 +25,7 @@ if (!dataValueSet) { const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); const name = `${this.symbol}${this.getEscapedText(title)}`; - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(name)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(name)); } this.dismissDropdown(!dataValueSet); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 6827ab1658a..d3c3be9b914 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -27,7 +27,7 @@ if (!dataValueSet) { const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchManager.addWordToInput(this.getSelectedText(username)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(username)); } this.dismissDropdown(!dataValueSet); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index c63ba1acf0b..38ecbbf552d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -57,7 +57,7 @@ const dataValue = selected.getAttribute('data-value'); if (dataValue) { - gl.FilteredSearchManager.addWordToInput(dataValue); + gl.FilteredSearchDropdownManager.addWordToInput(dataValue); } return dataValue !== null; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 new file mode 100644 index 00000000000..67a474985c0 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -0,0 +1,174 @@ +/* eslint-disable no-param-reassign */ +((global) => { + class FilteredSearchDropdownManager { + constructor() { + this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchInput = document.querySelector('.filtered-search'); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('page:fetch', this.cleanupWrapper); + } + + cleanup() { + if (this.droplab) { + this.droplab.destroy(); + this.droplab = null; + } + + this.setupMapping(); + + document.removeEventListener('page:fetch', this.cleanupWrapper); + } + + setupMapping() { + this.mapping = { + author: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-author'), + }, + assignee: { + reference: null, + gl: 'DropdownUser', + element: document.querySelector('#js-dropdown-assignee'), + }, + milestone: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['milestones.json', '%'], + element: document.querySelector('#js-dropdown-milestone'), + }, + label: { + reference: null, + gl: 'DropdownNonUser', + extraArguments: ['labels.json', '~'], + element: document.querySelector('#js-dropdown-label'), + }, + hint: { + reference: null, + gl: 'DropdownHint', + element: document.querySelector('#js-dropdown-hint'), + }, + } + } + + static addWordToInput(word, addSpace) { + const filteredSearchInput = document.querySelector('.filtered-search') + const filteredSearchValue = filteredSearchInput.value; + const hasExistingValue = filteredSearchValue.length !== 0; + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); + + if (lastToken.hasOwnProperty('key')) { + console.log(lastToken); + // Spaces inside the token means that the token value will be escaped by quotes + const hasQuotes = lastToken.value.indexOf(' ') !== -1; + const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; + filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); + } + + filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; + } + + updateCurrentDropdownOffset() { + this.updateDropdownOffset(this.currentDropdown); + } + + updateDropdownOffset(key) { + const filterIconPadding = 27; + const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; + + this.mapping[key].reference.setOffset(offset); + } + + load(key, firstLoad = false) { + console.log(`🦄 load ${key} dropdown`); + const glClass = this.mapping[key].gl; + const element = this.mapping[key].element; + let forceShowList = false; + + if (!this.mapping[key].reference) { + var dl = this.droplab; + const defaultArguments = [null, dl, element, this.filteredSearchInput]; + const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); + + this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); + } + + if (firstLoad) { + this.mapping[key].reference.configure(); + } + + if (this.currentDropdown === 'hint') { + // Clicked from hint dropdown + forceShowList = true; + } + + this.updateDropdownOffset(key); + this.mapping[key].reference.render(firstLoad, forceShowList); + + this.currentDropdown = key; + } + + loadDropdown(dropdownName = '') { + let firstLoad = false; + + if(!this.droplab) { + firstLoad = true; + this.droplab = new DropLab(); + } + + if (!this.font) { + this.font = window.getComputedStyle(this.filteredSearchInput).font; + } + + const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); + const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; + + if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; + this.load(key, firstLoad); + } + + gl.droplab = this.droplab; + } + + setDropdown() { + const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); + + if (typeof lastToken === 'string') { + // Token is not fully initialized yet + // because it has no value + // Eg. token = 'label:' + const { tokenKey } = this.tokenizer.parseToken(lastToken); + this.loadDropdown(tokenKey); + } else if (lastToken.hasOwnProperty('key')) { + // Token has been initialized into an object + // because it has a value + this.loadDropdown(lastToken.key); + } else { + this.loadDropdown('hint'); + } + } + + resetDropdowns() { + // Force current dropdown to hide + this.mapping[this.currentDropdown].reference.hideDropdown(); + + // Re-Load dropdown + this.setDropdown(); + + // Reset filters for current dropdown + this.mapping[this.currentDropdown].reference.resetFilters(); + + // Reposition dropdown so that it is aligned with cursor + this.updateDropdownOffset(this.currentDropdown); + } + + destroyDroplab() { + this.droplab.destroy(); + } + } + + global.FilteredSearchDropdownManager = FilteredSearchDropdownManager; +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index c92d669114e..d9ea44b3a13 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -75,159 +75,24 @@ this.tokenizer = gl.FilteredSearchTokenizer; this.filteredSearchInput = document.querySelector('.filtered-search'); this.clearSearchButton = document.querySelector('.clear-search'); + this.dropdownManager = new gl.FilteredSearchDropdownManager(); - this.setupMapping(); + this.dropdownManager.setupMapping(); this.bindEvents(); loadSearchParamsFromURL(); - this.setDropdown(); + this.dropdownManager.setDropdown(); this.cleanupWrapper = this.cleanup.bind(this); document.addEventListener('page:fetch', this.cleanupWrapper); } cleanup() { - console.log('cleanup') - - if (this.droplab) { - this.droplab.destroy(); - this.droplab = null; - } - - this.setupMapping(); - this.unbindEvents(); document.removeEventListener('page:fetch', this.cleanupWrapper); } - setupMapping() { - this.mapping = { - author: { - reference: null, - gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-author'), - }, - assignee: { - reference: null, - gl: 'DropdownUser', - element: document.querySelector('#js-dropdown-assignee'), - }, - milestone: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: ['milestones.json', '%'], - element: document.querySelector('#js-dropdown-milestone'), - }, - label: { - reference: null, - gl: 'DropdownNonUser', - extraArguments: ['labels.json', '~'], - element: document.querySelector('#js-dropdown-label'), - }, - hint: { - reference: null, - gl: 'DropdownHint', - element: document.querySelector('#js-dropdown-hint'), - }, - } - } - - static addWordToInput(word, addSpace) { - const filteredSearchInput = document.querySelector('.filtered-search') - const filteredSearchValue = filteredSearchInput.value; - const hasExistingValue = filteredSearchValue.length !== 0; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); - - if (lastToken.hasOwnProperty('key')) { - console.log(lastToken); - // Spaces inside the token means that the token value will be escaped by quotes - const hasQuotes = lastToken.value.indexOf(' ') !== -1; - const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; - filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); - } - - filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; - } - - updateDropdownOffset(key) { - const filterIconPadding = 27; - const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; - - this.mapping[key].reference.setOffset(offset); - } - - load(key, firstLoad = false) { - console.log(`🦄 load ${key} dropdown`); - const glClass = this.mapping[key].gl; - const element = this.mapping[key].element; - let forceShowList = false; - - if (!this.mapping[key].reference) { - var dl = this.droplab; - const defaultArguments = [null, dl, element, this.filteredSearchInput]; - const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); - - this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); - } - - if (firstLoad) { - this.mapping[key].reference.configure(); - } - - if (this.currentDropdown === 'hint') { - // Clicked from hint dropdown - forceShowList = true; - } - - this.updateDropdownOffset(key); - this.mapping[key].reference.render(firstLoad, forceShowList); - - this.currentDropdown = key; - } - - loadDropdown(dropdownName = '') { - let firstLoad = false; - - if(!this.droplab) { - firstLoad = true; - this.droplab = new DropLab(); - } - - if (!this.font) { - this.font = window.getComputedStyle(this.filteredSearchInput).font; - } - - const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; - const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); - const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; - - if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { - const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; - this.load(key, firstLoad); - } - - gl.droplab = this.droplab; - } - - setDropdown() { - const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); - - if (typeof lastToken === 'string') { - // Token is not fully initialized yet - // because it has no value - // Eg. token = 'label:' - const { tokenKey } = this.tokenizer.parseToken(lastToken); - this.loadDropdown(tokenKey); - } else if (lastToken.hasOwnProperty('key')) { - // Token has been initialized into an object - // because it has a value - this.loadDropdown(lastToken.key); - } else { - this.loadDropdown('hint'); - } - } - bindEvents() { - this.setDropdownWrapper = this.setDropdown.bind(this); + this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); this.checkForEnterWrapper = this.checkForEnter.bind(this); this.clearSearchWrapper = this.clearSearch.bind(this); this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); @@ -254,24 +119,13 @@ this.filteredSearchInput.value = ''; this.clearSearchButton.classList.add('hidden'); - - // Force current dropdown to hide - this.mapping[this.currentDropdown].reference.hideDropdown(); - - // Re-Load dropdown - this.setDropdown(); - - // Reset filters for current dropdown - this.mapping[this.currentDropdown].reference.resetFilters(); - - // Reposition dropdown so that it is aligned with cursor - this.updateDropdownOffset(this.currentDropdown); + this.dropdownManager.resetDropdowns(); } checkForBackspace(e) { if (e.keyCode === 8) { // Reposition dropdown so that it is aligned with cursor - this.updateDropdownOffset(this.currentDropdown); + this.dropdownManager.updateCurrentDropdownOffset(); } } @@ -282,7 +136,7 @@ e.preventDefault(); // Prevent droplab from opening dropdown - this.droplab.destroy(); + this.dropdownManager.destroyDroplab(); this.search(); } -- cgit v1.2.3 From 6c811d478d60246a8c2abf4b1bc4fd252d344ed9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 09:47:21 -0600 Subject: Remove show() as it is automatically called on setData when there is data --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 943ee9fa0a4..6e1eb080e3b 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -87,8 +87,6 @@ require('../window')(function(w){ if (onlyDynamicList && data.length === 0) { self.hook.list.hide(); - } else if (onlyDynamicList && data.length !== 0) { - self.hook.list.show(); } self.hook.list.setData.call(self.hook.list, data); -- cgit v1.2.3 From 2bbc44cb7edb81d1e83836574573b365f0b4d1cb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 09:55:27 -0600 Subject: Refactor itemClicked --- .../javascripts/filtered_search/dropdown_non_user.js.es6 | 11 +++-------- app/assets/javascripts/filtered_search/dropdown_user.js.es6 | 11 +++-------- .../filtered_search/filtered_search_dropdown.js.es6 | 11 +++++++++-- 3 files changed, 15 insertions(+), 18 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index e4df39cfde1..752a9a6e242 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -20,15 +20,10 @@ } itemClicked(e) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); - - if (!dataValueSet) { + super.itemClicked(e, (selected) => { const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); - const name = `${this.symbol}${this.getEscapedText(title)}`; - gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(name)); - } - - this.dismissDropdown(!dataValueSet); + return `${this.symbol}${this.getEscapedText(title)}`; + }); } getEscapedText(text) { diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index d3c3be9b914..749fb9d90aa 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -23,14 +23,9 @@ } itemClicked(e) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); - - if (!dataValueSet) { - const username = e.detail.selected.querySelector('.dropdown-light-content').innerText.trim(); - gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(username)); - } - - this.dismissDropdown(!dataValueSet); + super.itemClicked(e, (selected) => { + return selected.querySelector('.dropdown-light-content').innerText.trim(); + }); } renderContent(forceShowList = false) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 38ecbbf552d..990d56188cb 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -35,8 +35,15 @@ return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); } - itemClicked(e) { - // Overridden by dropdown sub class + itemClicked(e, getValueFunction) { + const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + + if (!dataValueSet) { + const value = getValueFunction(e.detail.selected) + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(value)); + } + + this.dismissDropdown(); } renderContent(forceShowList = false) { -- cgit v1.2.3 From 5589ab1e0be2d682a8be424289d17b4e566caba0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 10:28:22 -0600 Subject: Refactor and add comments --- .../filtered_search/dropdown_hint.js.es6 | 3 +- .../filtered_search/dropdown_non_user.js.es6 | 17 ++++----- .../filtered_search/dropdown_user.js.es6 | 3 +- .../filtered_search_dropdown.js.es6 | 39 ++++++++++---------- .../filtered_search_dropdown_manager.js.es6 | 41 ++++++++++++---------- .../filtered_search/filtered_search_manager.js.es6 | 9 ++--- .../filtered_search/filtered_search_tokenizer.es6 | 12 ------- 7 files changed, 58 insertions(+), 66 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 53952e6bc63..43a0b1da0fe 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -23,7 +23,6 @@ class DropdownHint extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { super(droplab, dropdown, input); - this.listId = dropdown.id; this.config = { droplabFilter: { template: 'hint', @@ -66,7 +65,7 @@ return item; } - configure() { + init() { this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 752a9a6e242..0969df65836 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -5,7 +5,6 @@ class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input, endpoint, symbol) { super(droplab, dropdown, input); - this.listId = dropdown.id; this.symbol = symbol; this.config = { droplabAjax: { @@ -28,15 +27,17 @@ getEscapedText(text) { let escapedText = text; + const hasSpace = text.indexOf(' ') !== -1; + const hasDoubleQuote = text.indexOf('"') !== -1; + const hasSingleQuote = text.indexOf('\'') !== -1; // Encapsulate value with quotes if it has spaces - if (text.indexOf(' ') !== -1) { - if (text.indexOf('"') !== -1) { - // Use single quotes if value contains double quotes + // Known side effect: values's with both single and double quotes + // won't escape properly + if (hasSpace) { + if (hasDoubleQuote) { escapedText = `'${text}'`; - } else { - // Known side effect: values's with both single and double quotes - // won't escape properly + } else if (hasSingleQuote) { escapedText = `"${text}"`; } } @@ -65,7 +66,7 @@ super.renderContent(forceShowList); } - configure() { + init() { this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 749fb9d90aa..8bc274e0b12 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -5,7 +5,6 @@ class DropdownUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { super(droplab, dropdown, input); - this.listId = dropdown.id; this.config = { droplabAjaxFilter: { endpoint: '/autocomplete/users.json', @@ -47,7 +46,7 @@ return hasPrefix ? valueWithoutPrefix : valueWithoutColon; } - configure() { + init() { this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 990d56188cb..85d684e3058 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -46,14 +46,8 @@ this.dismissDropdown(); } - renderContent(forceShowList = false) { - if (forceShowList && this.getCurrentHook().list.hidden) { - this.getCurrentHook().list.show(); - } - } - setAsDropdown() { - this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.listId}`); + this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`); } setOffset(offset = 0) { @@ -67,17 +61,14 @@ gl.FilteredSearchDropdownManager.addWordToInput(dataValue); } + // Return boolean based on whether it was set return dataValue !== null; } - dismissDropdown() { - this.input.focus(); - } - - dispatchInputEvent() { - // Propogate input change to FilteredSearchManager - // so that it can determine which dropdowns to open - this.input.dispatchEvent(new Event('input')); + renderContent(forceShowList = false) { + if (forceShowList && this.getCurrentHook().list.hidden) { + this.getCurrentHook().list.show(); + } } render(forceRenderContent = false, forceShowList = false) { @@ -88,11 +79,23 @@ if (firstTimeInitialized || forceRenderContent) { this.renderContent(forceShowList); - } else if(currentHook.list.list.id !== this.listId) { + } else if(currentHook.list.list.id !== this.dropdown.id) { this.renderContent(forceShowList); } } + dismissDropdown() { + // Focusing on the input will dismiss dropdown + // (default droplab functionality) + this.input.focus(); + } + + dispatchInputEvent() { + // Propogate input change to FilteredSearchDropdownManager + // so that it can determine which dropdowns to open + this.input.dispatchEvent(new Event('input')); + } + hideDropdown() { this.getCurrentHook().list.hide(); } @@ -100,9 +103,7 @@ resetFilters() { const hook = this.getCurrentHook(); const data = hook.list.data; - const results = data.map(function(o) { - o.droplab_hidden = false; - }); + const results = data.map(o => o.droplab_hidden = false); hook.list.render(results); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 67a474985c0..a0764c275e5 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -5,6 +5,8 @@ this.tokenizer = gl.FilteredSearchTokenizer; this.filteredSearchInput = document.querySelector('.filtered-search'); + this.setupMapping(); + this.cleanupWrapper = this.cleanup.bind(this); document.addEventListener('page:fetch', this.cleanupWrapper); } @@ -52,21 +54,22 @@ } } - static addWordToInput(word, addSpace) { - const filteredSearchInput = document.querySelector('.filtered-search') - const filteredSearchValue = filteredSearchInput.value; - const hasExistingValue = filteredSearchValue.length !== 0; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(filteredSearchValue); + static addWordToInput(word, addSpace = false) { + const input = document.querySelector('.filtered-search') + const value = input.value; + const hasExistingValue = value.length !== 0; + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(value); if (lastToken.hasOwnProperty('key')) { - console.log(lastToken); // Spaces inside the token means that the token value will be escaped by quotes const hasQuotes = lastToken.value.indexOf(' ') !== -1; + + // Add 2 length to account for the length of the front and back quotes const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; - filteredSearchInput.value = filteredSearchValue.slice(0, -1 * (lengthToRemove)); + input.value = value.slice(0, -1 * (lengthToRemove)); } - filteredSearchInput.value += hasExistingValue && addSpace ? ` ${word}` : word; + input.value += hasExistingValue && addSpace ? ` ${word}` : word; } updateCurrentDropdownOffset() { @@ -74,6 +77,10 @@ } updateDropdownOffset(key) { + if (!this.font) { + this.font = window.getComputedStyle(this.filteredSearchInput).font; + } + const filterIconPadding = 27; const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; @@ -87,19 +94,20 @@ let forceShowList = false; if (!this.mapping[key].reference) { - var dl = this.droplab; + const dl = this.droplab; const defaultArguments = [null, dl, element, this.filteredSearchInput]; const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); + // Passing glArguments to `new gl[glClass]()` this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); } if (firstLoad) { - this.mapping[key].reference.configure(); + this.mapping[key].reference.init(); } if (this.currentDropdown === 'hint') { - // Clicked from hint dropdown + // Force the dropdown to show if it was clicked from the hint dropdown forceShowList = true; } @@ -117,15 +125,12 @@ this.droplab = new DropLab(); } - if (!this.font) { - this.font = window.getComputedStyle(this.filteredSearchInput).font; - } - const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { + // `hint` is not listed as a tokenKey (since it is not a real `filter`) const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; this.load(key, firstLoad); } @@ -137,14 +142,12 @@ const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); if (typeof lastToken === 'string') { - // Token is not fully initialized yet - // because it has no value + // Token is not fully initialized yet because it has no value // Eg. token = 'label:' const { tokenKey } = this.tokenizer.parseToken(lastToken); this.loadDropdown(tokenKey); } else if (lastToken.hasOwnProperty('key')) { - // Token has been initialized into an object - // because it has a value + // Token has been initialized into an object because it has a value this.loadDropdown(lastToken.key); } else { this.loadDropdown('hint'); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d9ea44b3a13..d3bccc4b14c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ ((global) => { + // TODO: Encapsulate inside class? function toggleClearSearchButton(e) { const clearSearchButton = document.querySelector('.clear-search'); @@ -25,6 +26,7 @@ let conditionIndex = 0; const validCondition = gl.FilteredSearchTokenKeys.get() .filter(v => v.conditions && v.conditions.filter((c, index) => { + // TODO: Add comment here if (c.url === p) { conditionIndex = index; } @@ -32,6 +34,7 @@ })[0])[0]; if (validCondition) { + // Parse params based on rules provided in the conditions key of gl.FilteredSearchTokenKeys.get() inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; inputValue += ' '; } else { @@ -77,7 +80,6 @@ this.clearSearchButton = document.querySelector('.clear-search'); this.dropdownManager = new gl.FilteredSearchDropdownManager(); - this.dropdownManager.setupMapping(); this.bindEvents(); loadSearchParamsFromURL(); this.dropdownManager.setDropdown(); @@ -130,7 +132,6 @@ } checkForEnter(e) { - // Enter KeyCode if (e.keyCode === 13) { e.stopPropagation(); e.preventDefault(); @@ -143,7 +144,6 @@ } search() { - console.log('search'); let path = '?scope=all&utf8=✓'; // Check current state @@ -152,9 +152,10 @@ const defaultState = 'opened'; let currentState = defaultState; - const { tokens, searchToken } = this.tokenizer.processTokens(document.querySelector('.filtered-search').value); + const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); if (stateIndex !== -1) { + // TODO: Add comment here const remaining = currentPath.slice(stateIndex + 6); const separatorIndex = remaining.indexOf('&'); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index 4abb5e94d81..ac45d3b7986 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -1,15 +1,6 @@ /* eslint-disable no-param-reassign */ ((global) => { class FilteredSearchTokenizer { - // TODO: Remove when going to pro - static printTokens(tokens, searchToken, lastToken) { - console.log('tokens:'); - tokens.forEach(token => console.log(token)); - console.log(`search: ${searchToken}`); - console.log('last token:'); - console.log(lastToken); - } - static parseToken(input) { const colonIndex = input.indexOf(':'); let tokenKey; @@ -163,9 +154,6 @@ searchToken = searchTerms.trim(); - // TODO: Remove when going to PRO - gl.FilteredSearchTokenizer.printTokens(tokens, searchToken, lastToken); - return { tokens, searchToken, -- cgit v1.2.3 From aebee11884d2176a45cb17efa97bf3fdbc95449a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 10:55:43 -0600 Subject: Fix bug where labels with spaces weren't being escaped when selected --- app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 0969df65836..84abaa920d6 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -29,7 +29,6 @@ let escapedText = text; const hasSpace = text.indexOf(' ') !== -1; const hasDoubleQuote = text.indexOf('"') !== -1; - const hasSingleQuote = text.indexOf('\'') !== -1; // Encapsulate value with quotes if it has spaces // Known side effect: values's with both single and double quotes @@ -37,7 +36,8 @@ if (hasSpace) { if (hasDoubleQuote) { escapedText = `'${text}'`; - } else if (hasSingleQuote) { + } else { + // Encapsulate singleQuotes or if it hasSpace escapedText = `"${text}"`; } } -- cgit v1.2.3 From 262ad96aa9413e2ff1380930703e9e3a649bb855 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 10:55:55 -0600 Subject: Remove unnecessary function --- .../filtered_search/filtered_search_dropdown.js.es6 | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 85d684e3058..a9dbb0f7ccb 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -27,20 +27,12 @@ return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; } - getSelectedText(selectedToken) { - // TODO: Get last word from FilteredSearchTokenizer - const lastWord = this.input.value.split(' ').last(); - const lastWordIndex = selectedToken.indexOf(lastWord); - - return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); - } - itemClicked(e, getValueFunction) { const dataValueSet = this.setDataValueIfSelected(e.detail.selected); if (!dataValueSet) { - const value = getValueFunction(e.detail.selected) - gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(value)); + const value = getValueFunction(e.detail.selected); + gl.FilteredSearchDropdownManager.addWordToInput(value); } this.dismissDropdown(); -- cgit v1.2.3 From 274f3e23e35ddf3116cd7c227b94ce68378c76af Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 11:06:45 -0600 Subject: Add comments to resolve todos --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d3bccc4b14c..14e2e698f93 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -26,7 +26,7 @@ let conditionIndex = 0; const validCondition = gl.FilteredSearchTokenKeys.get() .filter(v => v.conditions && v.conditions.filter((c, index) => { - // TODO: Add comment here + // Return TokenKeys that have conditions that much the URL if (c.url === p) { conditionIndex = index; } @@ -155,8 +155,8 @@ const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); if (stateIndex !== -1) { - // TODO: Add comment here - const remaining = currentPath.slice(stateIndex + 6); + // Get currentState from url params if available + const remaining = currentPath.slice(stateIndex + 'state='.length); const separatorIndex = remaining.indexOf('&'); currentState = separatorIndex === -1 ? remaining : remaining.slice(0, separatorIndex); -- cgit v1.2.3 From 8925c9604f95b66a9b8a4579e321961312bfc78d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 11:08:15 -0600 Subject: Add additional check before setting outerHTML --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 6e1eb080e3b..c6c062d0886 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -78,7 +78,11 @@ require('../window')(function(w){ this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { if (config.loadingTemplate && self.hook.list.data === undefined || self.hook.list.data.length === 0) { - self.hook.list.list.querySelector('[data-loading-template]').outerHTML = self.listTemplate; + const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } } if (!self.destroyed) { -- cgit v1.2.3 From 16e3fe3f15971bc34c48d65c902ce83a156e350d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 11:19:44 -0600 Subject: Fix missing method from refactoring --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 43a0b1da0fe..1aef27163c6 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -41,13 +41,20 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedText(token)); + gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedTextWithoutEscaping(token)); } this.dismissDropdown(); this.dispatchInputEvent(); } } + getSelectedTextWithoutEscaping(selectedToken) { + const lastWord = this.input.value.split(' ').last(); + const lastWordIndex = selectedToken.indexOf(lastWord); + + return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); + } + renderContent() { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.setData(this.hookId, dropdownData); -- cgit v1.2.3 From 6eafd748493e1125e1f5dea698dd3ca6affe15c5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:25:31 -0600 Subject: Fix code styling issues --- .../javascripts/filtered_search/dropdown_hint.js.es6 | 2 +- .../filtered_search/filtered_search_dropdown.js.es6 | 9 +++++---- .../filtered_search_dropdown_manager.js.es6 | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 1aef27163c6..a79779e4977 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -32,7 +32,7 @@ } itemClicked(e) { - const selected = e.detail.selected; + const { selected } = e.detail; if (selected.hasAttribute('data-value')) { this.dismissDropdown(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index a9dbb0f7ccb..130e6bba341 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -24,14 +24,15 @@ } getCurrentHook() { - return this.droplab.hooks.filter(h => h.id === this.hookId)[0]; + return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null; } itemClicked(e, getValueFunction) { - const dataValueSet = this.setDataValueIfSelected(e.detail.selected); + const { selected } = e.detail; + const dataValueSet = this.setDataValueIfSelected(selected); if (!dataValueSet) { - const value = getValueFunction(e.detail.selected); + const value = getValueFunction(selected); gl.FilteredSearchDropdownManager.addWordToInput(value); } @@ -67,7 +68,7 @@ this.setAsDropdown(); const currentHook = this.getCurrentHook(); - const firstTimeInitialized = currentHook === undefined; + const firstTimeInitialized = currentHook === null; if (firstTimeInitialized || forceRenderContent) { this.renderContent(forceShowList); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index a0764c275e5..59166840c50 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -55,7 +55,7 @@ } static addWordToInput(word, addSpace = false) { - const input = document.querySelector('.filtered-search') + const input = document.querySelector('.filtered-search'); const value = input.value; const hasExistingValue = value.length !== 0; const { lastToken } = gl.FilteredSearchTokenizer.processTokens(value); @@ -88,22 +88,22 @@ } load(key, firstLoad = false) { - console.log(`🦄 load ${key} dropdown`); - const glClass = this.mapping[key].gl; - const element = this.mapping[key].element; + const mappingKey = this.mapping[key]; + const glClass = mappingKey.gl; + const element = mappingKey.element; let forceShowList = false; - if (!this.mapping[key].reference) { + if (!mappingKey.reference) { const dl = this.droplab; const defaultArguments = [null, dl, element, this.filteredSearchInput]; - const glArguments = defaultArguments.concat(this.mapping[key].extraArguments || []); + const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); // Passing glArguments to `new gl[glClass]()` - this.mapping[key].reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); + mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); } if (firstLoad) { - this.mapping[key].reference.init(); + mappingKey.reference.init(); } if (this.currentDropdown === 'hint') { @@ -112,7 +112,7 @@ } this.updateDropdownOffset(key); - this.mapping[key].reference.render(firstLoad, forceShowList); + mappingKey.reference.render(firstLoad, forceShowList); this.currentDropdown = key; } @@ -120,7 +120,7 @@ loadDropdown(dropdownName = '') { let firstLoad = false; - if(!this.droplab) { + if (!this.droplab) { firstLoad = true; this.droplab = new DropLab(); } -- cgit v1.2.3 From d8b8b9c88d0fbe4cfa1fae0796feaa82136cc747 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:27:10 -0600 Subject: Add support for delete key --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 14e2e698f93..ebbd7e3129e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -125,7 +125,9 @@ } checkForBackspace(e) { - if (e.keyCode === 8) { + // 8 = Backspace Key + // 46 = Delete Key + if (e.keyCode === 8 || e.keyCode === 46) { // Reposition dropdown so that it is aligned with cursor this.dropdownManager.updateCurrentDropdownOffset(); } -- cgit v1.2.3 From 214b6495ca1853c9653a0ff109e0163dbc1d1cb6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:28:17 -0600 Subject: Remove unnecessary stopPropagation --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 2 -- 1 file changed, 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index ebbd7e3129e..e068b5d2ebf 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -115,7 +115,6 @@ } clearSearch(e) { - e.stopPropagation(); e.preventDefault(); this.filteredSearchInput.value = ''; @@ -135,7 +134,6 @@ checkForEnter(e) { if (e.keyCode === 13) { - e.stopPropagation(); e.preventDefault(); // Prevent droplab from opening dropdown -- cgit v1.2.3 From f0935c4da5bcd84fac51f7a0d61ee0c8d8181679 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:29:51 -0600 Subject: Fix regex for + --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index e068b5d2ebf..a89627384e9 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -40,7 +40,7 @@ } else { // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + - const sanitizedValue = value ? decodeURIComponent(value.replace(/[+]/g, ' ')) : value; + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; const match = gl.FilteredSearchTokenKeys.get().filter(t => key === `${t.key}_${t.param}`)[0]; if (match) { -- cgit v1.2.3 From a30fbbddfb03a63305ff1bd273d7dd98c976936a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:31:22 -0600 Subject: Reduce over-verboseness --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index a89627384e9..77a9de96c8a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -47,13 +47,11 @@ const sanitizedKey = key.slice(0, key.indexOf('_')); const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; const symbol = match.symbol; - - const preferredQuotations = '"'; - let quotationsToUse = preferredQuotations; + let quotationsToUse; if (valueHasSpace) { // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf(preferredQuotations) === -1 ? preferredQuotations : '\''; + quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; } inputValue += valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`; -- cgit v1.2.3 From 5116db243a2f1705462e792cbb71f666cfca98f0 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:34:01 -0600 Subject: Convert to single quotes --- app/assets/javascripts/lib/utils/text_utility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index e47eccc3a33..db24bcf682b 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -27,8 +27,8 @@ * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 */ // re-use canvas object for better performance - var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement("canvas")); - var context = canvas.getContext("2d"); + var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas')); + var context = canvas.getContext('2d'); context.font = font; var metrics = context.measureText(text); return metrics.width; -- cgit v1.2.3 From 78fe37b169602d898ffbd756189706559aad84f2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:37:49 -0600 Subject: Move functions into class --- .../filtered_search/filtered_search_manager.js.es6 | 164 ++++++++++----------- 1 file changed, 81 insertions(+), 83 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 77a9de96c8a..00b7dc195bb 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,76 +1,5 @@ /* eslint-disable no-param-reassign */ ((global) => { - // TODO: Encapsulate inside class? - function toggleClearSearchButton(e) { - const clearSearchButton = document.querySelector('.clear-search'); - - if (e.target.value) { - clearSearchButton.classList.remove('hidden'); - } else { - clearSearchButton.classList.add('hidden'); - } - } - - function loadSearchParamsFromURL() { - // We can trust that each param has one & since values containing & will be encoded - // Remove the first character of search as it is always ? - const params = window.location.search.slice(1).split('&'); - let inputValue = ''; - - params.forEach((p) => { - const split = p.split('='); - const key = decodeURIComponent(split[0]); - const value = split[1]; - - // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys.get() - let conditionIndex = 0; - const validCondition = gl.FilteredSearchTokenKeys.get() - .filter(v => v.conditions && v.conditions.filter((c, index) => { - // Return TokenKeys that have conditions that much the URL - if (c.url === p) { - conditionIndex = index; - } - return c.url === p; - })[0])[0]; - - if (validCondition) { - // Parse params based on rules provided in the conditions key of gl.FilteredSearchTokenKeys.get() - inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; - inputValue += ' '; - } else { - // Sanitize value since URL converts spaces into + - // Replace before decode so that we know what was originally + versus the encoded + - const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; - const match = gl.FilteredSearchTokenKeys.get().filter(t => key === `${t.key}_${t.param}`)[0]; - - if (match) { - const sanitizedKey = key.slice(0, key.indexOf('_')); - const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; - const symbol = match.symbol; - let quotationsToUse; - - if (valueHasSpace) { - // Prefer ", but use ' if required - quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; - } - - inputValue += valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`; - inputValue += ' '; - } else if (!match && key === 'search') { - inputValue += sanitizedValue; - inputValue += ' '; - } - } - }); - - // Trim the last space value - document.querySelector('.filtered-search').value = inputValue.trim(); - - if (inputValue.trim()) { - document.querySelector('.clear-search').classList.remove('hidden'); - } - } - class FilteredSearchManager { constructor() { this.tokenizer = gl.FilteredSearchTokenizer; @@ -79,7 +8,7 @@ this.dropdownManager = new gl.FilteredSearchDropdownManager(); this.bindEvents(); - loadSearchParamsFromURL(); + this.loadSearchParamsFromURL(); this.dropdownManager.setDropdown(); this.cleanupWrapper = this.cleanup.bind(this); @@ -93,12 +22,13 @@ bindEvents() { this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); + this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); this.checkForEnterWrapper = this.checkForEnter.bind(this); this.clearSearchWrapper = this.clearSearch.bind(this); this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); - this.filteredSearchInput.addEventListener('input', toggleClearSearchButton); + this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper); this.clearSearchButton.addEventListener('click', this.clearSearchWrapper); @@ -106,21 +36,12 @@ unbindEvents() { this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); - this.filteredSearchInput.removeEventListener('input', toggleClearSearchButton); + this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper); this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper); } - clearSearch(e) { - e.preventDefault(); - - this.filteredSearchInput.value = ''; - this.clearSearchButton.classList.add('hidden'); - - this.dropdownManager.resetDropdowns(); - } - checkForBackspace(e) { // 8 = Backspace Key // 46 = Delete Key @@ -141,6 +62,83 @@ } } + toggleClearSearchButton(e) { + if (e.target.value) { + this.clearSearchButton.classList.remove('hidden'); + } else { + this.clearSearchButton.classList.add('hidden'); + } + } + + clearSearch(e) { + e.preventDefault(); + + this.filteredSearchInput.value = ''; + this.clearSearchButton.classList.add('hidden'); + + this.dropdownManager.resetDropdowns(); + } + + loadSearchParamsFromURL() { + // We can trust that each param has one & since values containing & will be encoded + // Remove the first character of search as it is always ? + const params = window.location.search.slice(1).split('&'); + let inputValue = ''; + + params.forEach((p) => { + const split = p.split('='); + const key = decodeURIComponent(split[0]); + const value = split[1]; + + // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys.get() + let conditionIndex = 0; + const validCondition = gl.FilteredSearchTokenKeys.get() + .filter(v => v.conditions && v.conditions.filter((c, index) => { + // Return TokenKeys that have conditions that much the URL + if (c.url === p) { + conditionIndex = index; + } + return c.url === p; + })[0])[0]; + + if (validCondition) { + // Parse params based on rules provided in the conditions key of gl.FilteredSearchTokenKeys.get() + inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; + inputValue += ' '; + } else { + // Sanitize value since URL converts spaces into + + // Replace before decode so that we know what was originally + versus the encoded + + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; + const match = gl.FilteredSearchTokenKeys.get().filter(t => key === `${t.key}_${t.param}`)[0]; + + if (match) { + const sanitizedKey = key.slice(0, key.indexOf('_')); + const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; + const symbol = match.symbol; + let quotationsToUse; + + if (valueHasSpace) { + // Prefer ", but use ' if required + quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; + } + + inputValue += valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`; + inputValue += ' '; + } else if (!match && key === 'search') { + inputValue += sanitizedValue; + inputValue += ' '; + } + } + }); + + // Trim the last space value + this.filteredSearchInput.value = inputValue.trim(); + + if (inputValue.trim()) { + this.clearSearchButton.classList.remove('hidden'); + } + } + search() { let path = '?scope=all&utf8=✓'; -- cgit v1.2.3 From e9886b5704ae60a0e3517205de3958e9c0044a99 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 16:42:00 -0600 Subject: Convert string concatenations with an array join --- .../filtered_search/filtered_search_manager.js.es6 | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 00b7dc195bb..d0e39b6390d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -83,7 +83,7 @@ // We can trust that each param has one & since values containing & will be encoded // Remove the first character of search as it is always ? const params = window.location.search.slice(1).split('&'); - let inputValue = ''; + let inputValues = []; params.forEach((p) => { const split = p.split('='); @@ -103,8 +103,7 @@ if (validCondition) { // Parse params based on rules provided in the conditions key of gl.FilteredSearchTokenKeys.get() - inputValue += `${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`; - inputValue += ' '; + inputValues.push(`${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`); } else { // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + @@ -122,25 +121,23 @@ quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; } - inputValue += valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`; - inputValue += ' '; + inputValues.push(valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`); } else if (!match && key === 'search') { - inputValue += sanitizedValue; - inputValue += ' '; + inputValues.push(sanitizedValue); } } }); // Trim the last space value - this.filteredSearchInput.value = inputValue.trim(); + this.filteredSearchInput.value = inputValues.join(' '); - if (inputValue.trim()) { + if (inputValues.length > 0) { this.clearSearchButton.classList.remove('hidden'); } } search() { - let path = '?scope=all&utf8=✓'; + let paths = []; // Check current state const currentPath = window.location.search; @@ -158,7 +155,7 @@ currentState = separatorIndex === -1 ? remaining : remaining.slice(0, separatorIndex); } - path += `&state=${currentState}`; + paths.push(`state=${currentState}`); tokens.forEach((token) => { const match = gl.FilteredSearchTokenKeys.get().filter(t => t.key === token.key)[0]; let tokenPath = ''; @@ -177,14 +174,14 @@ tokenPath = `${token.key}_${match.param}=${encodeURIComponent(token.value)}`; } - path += `&${tokenPath}`; + paths.push(tokenPath); }); if (searchToken) { - path += `&search=${encodeURIComponent(searchToken)}`; + paths.push(`search=${encodeURIComponent(searchToken)}`); } - window.location = path; + window.location = `?scope=all&utf8=✓&${paths.join('&')}`; } } -- cgit v1.2.3 From 98cb6101ec45e8758a9e85a3a24fcde9803ece18 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 21:07:42 -0600 Subject: Refactor static data to get information from other variables instead --- app/assets/javascripts/filtered_search/dropdown_user.js.es6 | 4 ++-- .../javascripts/filtered_search/filtered_search_dropdown.js.es6 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 8bc274e0b12..69b1ec3ea04 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -15,7 +15,7 @@ project_id: this.getProjectId(), current_user: true, }, - searchValueFunction: this.getSearchInput, + searchValueFunction: this.getSearchInput.bind(this), loadingTemplate: this.loadingTemplate, }, }; @@ -37,7 +37,7 @@ } getSearchInput() { - const query = document.querySelector('.filtered-search').value; + const query = this.input.value; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); const valueWithoutColon = value.slice(1); const hasPrefix = valueWithoutColon[0] === '@'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 130e6bba341..a5d8b0969c6 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -5,7 +5,7 @@ class FilteredSearchDropdown { constructor(droplab, dropdown, input) { this.droplab = droplab; - this.hookId = 'filtered-search'; + this.hookId = input.getAttribute('data-id'); this.input = input; this.dropdown = dropdown; this.loadingTemplate = `
-- cgit v1.2.3 From 091a3e66e9f2616eefca0f3aba090063116629e2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 21:15:50 -0600 Subject: Add getParameterByName --- .../filtered_search/filtered_search_manager.js.es6 | 18 ++---------------- app/assets/javascripts/lib/utils/common_utils.js.es6 | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d0e39b6390d..2237a21ca60 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -138,24 +138,10 @@ search() { let paths = []; - - // Check current state - const currentPath = window.location.search; - const stateIndex = currentPath.indexOf('state='); - const defaultState = 'opened'; - let currentState = defaultState; - const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); - - if (stateIndex !== -1) { - // Get currentState from url params if available - const remaining = currentPath.slice(stateIndex + 'state='.length); - const separatorIndex = remaining.indexOf('&'); - - currentState = separatorIndex === -1 ? remaining : remaining.slice(0, separatorIndex); - } - + const currentState = gl.utils.getParameterByName('state') || 'opened'; paths.push(`state=${currentState}`); + tokens.forEach((token) => { const match = gl.FilteredSearchTokenKeys.get().filter(t => t.key === token.key)[0]; let tokenPath = ''; diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index b8d637a9827..f0186c1390f 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -124,6 +124,22 @@ return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; }; + gl.utils.getParameterByName = function(name) { + var url = window.location.href; + var param = name.replace(/[[\]]/g, '\\$&'); + var regex = new RegExp(`[?&]${param}(=([^&#]*)|&|#|$)`); + var results = regex.exec(url); + + if (!results) { + return null; + } + + if (!results[2]) { + return ''; + } + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + }; + gl.utils.isMetaKey = function(e) { return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; }; -- cgit v1.2.3 From bcb00bdc487ad0d0e95c4a46a7d9437dcefc4e33 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 21:19:16 -0600 Subject: Use turbolinks instead of window.location --- app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 2237a21ca60..e087d0fd45b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -167,7 +167,7 @@ paths.push(`search=${encodeURIComponent(searchToken)}`); } - window.location = `?scope=all&utf8=✓&${paths.join('&')}`; + Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); } } -- cgit v1.2.3 From 49231ccef2fb0bd0cd10d636864d1d50ea70cbdc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 21:24:55 -0600 Subject: Refactor getUrlParamsArray() --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 4 +--- app/assets/javascripts/lib/utils/common_utils.js.es6 | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index e087d0fd45b..3e57215d608 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -80,9 +80,7 @@ } loadSearchParamsFromURL() { - // We can trust that each param has one & since values containing & will be encoded - // Remove the first character of search as it is always ? - const params = window.location.search.slice(1).split('&'); + const params = gl.utils.getUrlParamsArray(); let inputValues = []; params.forEach((p) => { diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index f0186c1390f..7a18f760e1b 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -124,6 +124,12 @@ return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; }; + gl.utils.getUrlParamsArray = function () { + // We can trust that each param has one & since values containing & will be encoded + // Remove the first character of search as it is always ? + return window.location.search.slice(1).split('&'); + } + gl.utils.getParameterByName = function(name) { var url = window.location.href; var param = name.replace(/[[\]]/g, '\\$&'); -- cgit v1.2.3 From bf16e91f2494912d44bc3a52d99ab36d3b33cd47 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 22:15:31 -0600 Subject: Refactor FilteredSearchTokenKeys model --- .../filtered_search_dropdown_manager.js.es6 | 2 +- .../filtered_search/filtered_search_manager.js.es6 | 48 ++++------- .../filtered_search_token_keys.js.es6 | 97 ++++++++++++++-------- .../filtered_search/filtered_search_tokenizer.es6 | 5 +- 4 files changed, 81 insertions(+), 71 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 59166840c50..682857d1899 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -125,7 +125,7 @@ this.droplab = new DropLab(); } - const match = gl.FilteredSearchTokenKeys.get().filter(value => value.key === dropdownName.toLowerCase())[0]; + const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 3e57215d608..d7fb3a0c204 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -85,42 +85,32 @@ params.forEach((p) => { const split = p.split('='); - const key = decodeURIComponent(split[0]); + const keyParam = decodeURIComponent(split[0]); const value = split[1]; - // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys.get() - let conditionIndex = 0; - const validCondition = gl.FilteredSearchTokenKeys.get() - .filter(v => v.conditions && v.conditions.filter((c, index) => { - // Return TokenKeys that have conditions that much the URL - if (c.url === p) { - conditionIndex = index; - } - return c.url === p; - })[0])[0]; + // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys + const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p); - if (validCondition) { - // Parse params based on rules provided in the conditions key of gl.FilteredSearchTokenKeys.get() - inputValues.push(`${validCondition.key}:${validCondition.conditions[conditionIndex].keyword}`); + if (condition) { + inputValues.push(`${condition.tokenKey}:${condition.value}`); } else { // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; - const match = gl.FilteredSearchTokenKeys.get().filter(t => key === `${t.key}_${t.param}`)[0]; + const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); if (match) { - const sanitizedKey = key.slice(0, key.indexOf('_')); - const valueHasSpace = sanitizedValue.indexOf(' ') !== -1; + const sanitizedKey = keyParam.slice(0, keyParam.indexOf('_')); const symbol = match.symbol; - let quotationsToUse; + let quotationsToUse = ''; - if (valueHasSpace) { + if (sanitizedValue.indexOf(' ') !== -1) { // Prefer ", but use ' if required quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\''; } - inputValues.push(valueHasSpace ? `${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}` : `${sanitizedKey}:${symbol}${sanitizedValue}`); - } else if (!match && key === 'search') { + inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`); + } else if (!match && keyParam === 'search') { inputValues.push(sanitizedValue); } } @@ -141,21 +131,17 @@ paths.push(`state=${currentState}`); tokens.forEach((token) => { - const match = gl.FilteredSearchTokenKeys.get().filter(t => t.key === token.key)[0]; + const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(token.key, token.value.toLowerCase()); + const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); let tokenPath = ''; - if (token.wildcard && match.conditions) { - const condition = match.conditions - .filter(c => c.keyword === token.value.toLowerCase())[0]; - - if (condition) { - tokenPath = `${condition.url}`; - } + if (token.wildcard && condition) { + tokenPath = condition.url; } else if (!token.wildcard) { // Remove the wildcard token - tokenPath = `${token.key}_${match.param}=${encodeURIComponent(token.value.slice(1))}`; + tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value.slice(1))}`; } else { - tokenPath = `${token.key}_${match.param}=${encodeURIComponent(token.value)}`; + tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value)}`; } paths.push(tokenPath); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index 8d38a29a354..97eab6be8df 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -1,43 +1,68 @@ /* eslint-disable no-param-reassign */ ((global) => { + const tokenKeys = [{ + key: 'author', + type: 'string', + param: 'username', + symbol: '@', + }, { + key: 'assignee', + type: 'string', + param: 'username', + symbol: '@', + }, { + key: 'milestone', + type: 'string', + param: 'title', + symbol: '%', + }, { + key: 'label', + type: 'array', + param: 'name[]', + symbol: '~', + }]; + + const conditions = [{ + url: 'assignee_id=0', + tokenKey: 'assignee', + value: 'none', + }, { + url: 'milestone_title=No+Milestone', + tokenKey: 'milestone', + value: 'none', + }, { + url: 'milestone_title=%23upcoming', + tokenKey: 'milestone', + value: 'upcoming', + }, { + url: 'label_name[]=No+Label', + tokenKey: 'label', + value: 'none', + }]; + class FilteredSearchTokenKeys { static get() { - return [{ - key: 'author', - type: 'string', - param: 'username', - symbol: '@', - }, { - key: 'assignee', - type: 'string', - param: 'username', - symbol: '@', - conditions: [{ - keyword: 'none', - url: 'assignee_id=0', - }], - }, { - key: 'milestone', - type: 'string', - param: 'title', - symbol: '%', - conditions: [{ - keyword: 'none', - url: 'milestone_title=No+Milestone', - }, { - keyword: 'upcoming', - url: 'milestone_title=%23upcoming', - }], - }, { - key: 'label', - type: 'array', - param: 'name[]', - symbol: '~', - conditions: [{ - keyword: 'none', - url: 'label_name[]=No+Label', - }], - }]; + return tokenKeys; + } + + static searchByKey(key) { + return tokenKeys.find(tokenKey => tokenKey.key === key) || null; + } + + static searchBySymbol(symbol) { + return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; + } + + static searchByKeyParam(keyParam) { + return tokenKeys.find(tokenKey => keyParam === `${tokenKey.key}_${tokenKey.param}`) || null; + } + + static searchByConditionUrl(url) { + return conditions.find(condition => condition.url === url) || null; + } + + static searchByConditionKeyValue(key, value) { + return conditions.find(condition => condition.tokenKey === key && condition.value === value) || null; } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index ac45d3b7986..365171252a1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -73,7 +73,6 @@ let tokens = []; let searchToken = ''; let lastToken = ''; - const validTokenKeys = gl.FilteredSearchTokenKeys.get(); const inputs = input.split(' '); let searchTerms = ''; @@ -107,8 +106,8 @@ if (colonIndex !== -1) { const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i); - const keyMatch = validTokenKeys.filter(v => v.key === tokenKey)[0]; - const symbolMatch = validTokenKeys.filter(v => v.symbol === tokenSymbol)[0]; + const keyMatch = gl.FilteredSearchTokenKeys.searchByKey(tokenKey); + const symbolMatch = gl.FilteredSearchTokenKeys.searchBySymbol(tokenSymbol); const doubleQuoteOccurrences = tokenValue.split('"').length - 1; const singleQuoteOccurrences = tokenValue.split('\'').length - 1; -- cgit v1.2.3 From a26cc6b25c2602fd4a47808b8c9c48dea789c6bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 12 Dec 2016 23:16:45 -0600 Subject: Simplify if else to make code easier to understand --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d7fb3a0c204..87bcbd272ca 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -137,11 +137,12 @@ if (token.wildcard && condition) { tokenPath = condition.url; - } else if (!token.wildcard) { - // Remove the wildcard token - tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value.slice(1))}`; - } else { + } else if (token.wildcard) { + // wildcard means that the token does not have a symbol tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value)}`; + } else { + // Remove the token symbol + tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value.slice(1))}`; } paths.push(tokenPath); -- cgit v1.2.3 From 27b2204009fb8fee409df013de013146bad1bfde Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 08:51:49 -0600 Subject: Convert hasOwnProperty check to if statement --- app/assets/javascripts/dispatcher.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 5a9ee5c7d78..9a76131b87f 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -84,7 +84,7 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': - if(gl.hasOwnProperty('FilteredSearchManager')) { + if(gl.FilteredSearchManager) { new gl.FilteredSearchManager(); } Issuable.init(); -- cgit v1.2.3 From 6700b76bec2a3b6564bd9da12b580b998d767d30 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 08:52:43 -0600 Subject: Fix eslint --- .../filtered_search/dropdown_hint.js.es6 | 29 ++++++++++---------- .../filtered_search/dropdown_non_user.js.es6 | 31 ++++++++++++---------- .../filtered_search/dropdown_user.js.es6 | 14 +++++----- .../filtered_search_dropdown.js.es6 | 16 ++++++----- .../filtered_search_dropdown_manager.js.es6 | 24 +++++++++-------- .../filtered_search/filtered_search_manager.js.es6 | 15 ++++++----- .../filtered_search_token_keys.js.es6 | 11 ++++---- .../filtered_search/filtered_search_tokenizer.es6 | 8 +++--- .../javascripts/lib/utils/common_utils.js.es6 | 2 +- app/assets/javascripts/lib/utils/text_utility.js | 3 +-- 10 files changed, 81 insertions(+), 72 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index a79779e4977..b920b17d915 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -1,20 +1,18 @@ -/* eslint-disable no-param-reassign */ /*= require filtered_search/filtered_search_dropdown */ - -((global) => { +(() => { const dropdownData = [{ icon: 'fa-pencil', hint: 'author:', - tag: '<author>' - },{ + tag: '<author>', + }, { icon: 'fa-user', hint: 'assignee:', tag: '<assignee>', - },{ + }, { icon: 'fa-clock-o', hint: 'milestone:', tag: '<milestone>', - },{ + }, { icon: 'fa-tag', hint: 'label:', tag: '<label>', @@ -27,7 +25,7 @@ droplabFilter: { template: 'hint', filterFunction: this.filterMethod, - } + }, }; } @@ -41,7 +39,8 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchDropdownManager.addWordToInput(this.getSelectedTextWithoutEscaping(token)); + gl.FilteredSearchDropdownManager + .addWordToInput(this.getSelectedTextWithoutEscaping(token)); } this.dismissDropdown(); this.dispatchInputEvent(); @@ -61,15 +60,16 @@ } filterMethod(item, query) { + const updatedItem = item; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); if (value === '') { - item.droplab_hidden = false; + updatedItem.droplab_hidden = false; } else { - item.droplab_hidden = item['hint'].indexOf(value) === -1; + updatedItem.droplab_hidden = updatedItem.hint.indexOf(value) === -1; } - return item; + return updatedItem; } init() { @@ -77,5 +77,6 @@ } } - global.DropdownHint = DropdownHint; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.DropdownHint = DropdownHint; +})(); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 84abaa920d6..95133db4c04 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -1,26 +1,24 @@ -/* eslint-disable no-param-reassign */ /*= require filtered_search/filtered_search_dropdown */ - -((global) => { +(() => { class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input, endpoint, symbol) { super(droplab, dropdown, input); this.symbol = symbol; this.config = { droplabAjax: { - endpoint: endpoint, + endpoint, method: 'setData', loadingTemplate: this.loadingTemplate, }, droplabFilter: { filterFunction: this.filterWithSymbol.bind(this, this.symbol), - } + }, }; } itemClicked(e) { super.itemClicked(e, (selected) => { - const title = e.detail.selected.querySelector('.js-data-value').innerText.trim(); + const title = selected.querySelector('.js-data-value').innerText.trim(); return `${this.symbol}${this.getEscapedText(title)}`; }); } @@ -46,30 +44,35 @@ } filterWithSymbol(filterSymbol, item, query) { + const updatedItem = item; const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); const valueWithoutColon = value.slice(1).toLowerCase(); const prefix = valueWithoutColon[0]; const valueWithoutPrefix = valueWithoutColon.slice(1); - const title = item.title.toLowerCase(); + const title = updatedItem.title.toLowerCase(); // Eg. filterSymbol = ~ for labels - const matchWithoutPrefix = prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; + const matchWithoutPrefix = + prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; const match = title.indexOf(valueWithoutColon) !== -1; - item.droplab_hidden = !match && !matchWithoutPrefix; - return item; + updatedItem.droplab_hidden = !match && !matchWithoutPrefix; + return updatedItem; } renderContent(forceShowList = false) { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); + this.droplab + .changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); super.renderContent(forceShowList); } init() { - this.droplab.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); + this.droplab + .addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); } } - global.DropdownNonUser = DropdownNonUser; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.DropdownNonUser = DropdownNonUser; +})(); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 69b1ec3ea04..2ee46559e63 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -1,7 +1,5 @@ -/* eslint-disable no-param-reassign */ /*= require filtered_search/filtered_search_dropdown */ - -((global) => { +(() => { class DropdownUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { super(droplab, dropdown, input); @@ -22,9 +20,8 @@ } itemClicked(e) { - super.itemClicked(e, (selected) => { - return selected.querySelector('.dropdown-light-content').innerText.trim(); - }); + super.itemClicked(e, + selected => selected.querySelector('.dropdown-light-content').innerText.trim()); } renderContent(forceShowList = false) { @@ -51,5 +48,6 @@ } } - global.DropdownUser = DropdownUser; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.DropdownUser = DropdownUser; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index a5d8b0969c6..7ddfdca10fa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -1,5 +1,4 @@ -/* eslint-disable no-param-reassign */ -((global) => { +(() => { const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; class FilteredSearchDropdown { @@ -72,7 +71,7 @@ if (firstTimeInitialized || forceRenderContent) { this.renderContent(forceShowList); - } else if(currentHook.list.list.id !== this.dropdown.id) { + } else if (currentHook.list.list.id !== this.dropdown.id) { this.renderContent(forceShowList); } } @@ -96,10 +95,15 @@ resetFilters() { const hook = this.getCurrentHook(); const data = hook.list.data; - const results = data.map(o => o.droplab_hidden = false); + const results = data.map((o) => { + const updated = o; + updated.droplab_hidden = false; + return updated; + }); hook.list.render(results); } } - global.FilteredSearchDropdown = FilteredSearchDropdown; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.FilteredSearchDropdown = FilteredSearchDropdown; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 682857d1899..7864ebf7aa1 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -1,5 +1,4 @@ -/* eslint-disable no-param-reassign */ -((global) => { +(() => { class FilteredSearchDropdownManager { constructor() { this.tokenizer = gl.FilteredSearchTokenizer; @@ -51,7 +50,7 @@ gl: 'DropdownHint', element: document.querySelector('#js-dropdown-hint'), }, - } + }; } static addWordToInput(word, addSpace = false) { @@ -60,7 +59,7 @@ const hasExistingValue = value.length !== 0; const { lastToken } = gl.FilteredSearchTokenizer.processTokens(value); - if (lastToken.hasOwnProperty('key')) { + if ({}.hasOwnProperty.call(lastToken, 'key')) { // Spaces inside the token means that the token value will be escaped by quotes const hasQuotes = lastToken.value.indexOf(' ') !== -1; @@ -82,7 +81,8 @@ } const filterIconPadding = 27; - const offset = gl.text.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; + const offset = gl.text + .getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding; this.mapping[key].reference.setOffset(offset); } @@ -99,7 +99,7 @@ const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); // Passing glArguments to `new gl[glClass]()` - mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments)); + mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))(); } if (firstLoad) { @@ -126,12 +126,13 @@ } const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); - const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping.hasOwnProperty(match.key); + const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key + && {}.hasOwnProperty.call(this.mapping, match.key); const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { // `hint` is not listed as a tokenKey (since it is not a real `filter`) - const key = match && match.hasOwnProperty('key') ? match.key : 'hint'; + const key = match && {}.hasOwnProperty.call(match, 'key') ? match.key : 'hint'; this.load(key, firstLoad); } @@ -146,7 +147,7 @@ // Eg. token = 'label:' const { tokenKey } = this.tokenizer.parseToken(lastToken); this.loadDropdown(tokenKey); - } else if (lastToken.hasOwnProperty('key')) { + } else if ({}.hasOwnProperty.call(lastToken, 'key')) { // Token has been initialized into an object because it has a value this.loadDropdown(lastToken.key); } else { @@ -173,5 +174,6 @@ } } - global.FilteredSearchDropdownManager = FilteredSearchDropdownManager; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 87bcbd272ca..96131a673ef 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,5 +1,4 @@ -/* eslint-disable no-param-reassign */ -((global) => { +(() => { class FilteredSearchManager { constructor() { this.tokenizer = gl.FilteredSearchTokenizer; @@ -81,7 +80,7 @@ loadSearchParamsFromURL() { const params = gl.utils.getUrlParamsArray(); - let inputValues = []; + const inputValues = []; params.forEach((p) => { const split = p.split('='); @@ -125,13 +124,14 @@ } search() { - let paths = []; + const paths = []; const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); const currentState = gl.utils.getParameterByName('state') || 'opened'; paths.push(`state=${currentState}`); tokens.forEach((token) => { - const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(token.key, token.value.toLowerCase()); + const condition = gl.FilteredSearchTokenKeys + .searchByConditionKeyValue(token.key, token.value.toLowerCase()); const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); let tokenPath = ''; @@ -156,5 +156,6 @@ } } - global.FilteredSearchManager = FilteredSearchManager; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.FilteredSearchManager = FilteredSearchManager; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index 97eab6be8df..a1830d13e5f 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -1,5 +1,4 @@ -/* eslint-disable no-param-reassign */ -((global) => { +(() => { const tokenKeys = [{ key: 'author', type: 'string', @@ -62,9 +61,11 @@ } static searchByConditionKeyValue(key, value) { - return conditions.find(condition => condition.tokenKey === key && condition.value === value) || null; + return conditions + .find(condition => condition.tokenKey === key && condition.value === value) || null; } } - global.FilteredSearchTokenKeys = FilteredSearchTokenKeys; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; +})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 index 365171252a1..0507f7bbc48 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 @@ -1,5 +1,4 @@ -/* eslint-disable no-param-reassign */ -((global) => { +(() => { class FilteredSearchTokenizer { static parseToken(input) { const colonIndex = input.indexOf(':'); @@ -161,5 +160,6 @@ } } - global.FilteredSearchTokenizer = FilteredSearchTokenizer; -})(window.gl || (window.gl = {})); + window.gl = window.gl || {}; + gl.FilteredSearchTokenizer = FilteredSearchTokenizer; +})(); diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 7a18f760e1b..9f1a62bf8b1 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -128,7 +128,7 @@ // We can trust that each param has one & since values containing & will be encoded // Remove the first character of search as it is always ? return window.location.search.slice(1).split('&'); - } + }; gl.utils.getParameterByName = function(name) { var url = window.location.href; diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index db24bcf682b..c856a26ae40 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -30,8 +30,7 @@ var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas')); var context = canvas.getContext('2d'); context.font = font; - var metrics = context.measureText(text); - return metrics.width; + return context.measureText(text).width; }; gl.text.selectedText = function(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); -- cgit v1.2.3 From 3d18319e0deae15836e994088f1254b28015d188 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 09:52:23 -0600 Subject: Rename to .js.es6 --- .../filtered_search/filtered_search_tokenizer.es6 | 165 --------------------- .../filtered_search_tokenizer.js.es6 | 165 +++++++++++++++++++++ 2 files changed, 165 insertions(+), 165 deletions(-) delete mode 100644 app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 create mode 100644 app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 deleted file mode 100644 index 0507f7bbc48..00000000000 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.es6 +++ /dev/null @@ -1,165 +0,0 @@ -(() => { - class FilteredSearchTokenizer { - static parseToken(input) { - const colonIndex = input.indexOf(':'); - let tokenKey; - let tokenValue; - let tokenSymbol; - - if (colonIndex !== -1) { - tokenKey = input.slice(0, colonIndex).toLowerCase(); - tokenValue = input.slice(colonIndex + 1); - tokenSymbol = tokenValue[0]; - } - - return { - tokenKey, - tokenValue, - tokenSymbol, - } - } - - static getLastTokenObject(input) { - const token = FilteredSearchTokenizer.getLastToken(input); - const colonIndex = token.indexOf(':'); - - const key = colonIndex !== -1 ? token.slice(0, colonIndex) : ''; - const value = colonIndex !== -1 ? token.slice(colonIndex) : token; - - return { - key, - value, - } - } - - static getLastToken(input) { - let completeToken = false; - let completeQuotation = true; - let lastQuotation = ''; - let i = input.length; - - const doubleQuote = '"'; - const singleQuote = '\''; - while(!completeToken && i >= 0) { - const isDoubleQuote = input[i] === doubleQuote; - const isSingleQuote = input[i] === singleQuote; - - // If the second quotation is found - if ((lastQuotation === doubleQuote && input[i] === doubleQuote) || - (lastQuotation === singleQuote && input[i] === singleQuote)) { - completeQuotation = true; - } - - // Save the first quotation - if ((input[i] === doubleQuote && lastQuotation === '') || - (input[i] === singleQuote && lastQuotation === '')) { - lastQuotation = input[i]; - completeQuotation = false; - } - - if (completeQuotation && input[i] === ' ') { - completeToken = true; - } else { - i--; - } - } - - // Adjust by 1 because of empty space - return input.slice(i + 1); - } - - static processTokens(input) { - let tokens = []; - let searchToken = ''; - let lastToken = ''; - - const inputs = input.split(' '); - let searchTerms = ''; - let lastQuotation = ''; - let incompleteToken = false; - - // Iterate through each word (broken up by spaces) - inputs.forEach((i) => { - if (incompleteToken) { - // Continue previous token as it had an escaped - // quote in the beginning - const prevToken = tokens.last(); - prevToken.value += ` ${i}`; - - // Remove last quotation from the value - const lastQuotationRegex = new RegExp(lastQuotation, 'g'); - prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); - tokens[tokens.length - 1] = prevToken; - - // Check to see if this quotation completes the token value - if (i.indexOf(lastQuotation) !== -1) { - lastToken = tokens.last(); - incompleteToken = !incompleteToken; - } - - return; - } - - const colonIndex = i.indexOf(':'); - - if (colonIndex !== -1) { - const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i); - - const keyMatch = gl.FilteredSearchTokenKeys.searchByKey(tokenKey); - const symbolMatch = gl.FilteredSearchTokenKeys.searchBySymbol(tokenSymbol); - - const doubleQuoteOccurrences = tokenValue.split('"').length - 1; - const singleQuoteOccurrences = tokenValue.split('\'').length - 1; - - const doubleQuoteIndex = tokenValue.indexOf('"'); - const singleQuoteIndex = tokenValue.indexOf('\''); - - const doubleQuoteExist = doubleQuoteIndex !== -1; - const singleQuoteExist = singleQuoteIndex !== -1; - - const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist; - const doubleQuoteIsBeforeSingleQuote = doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; - - const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist; - const singleQuoteIsBeforeDoubleQuote = doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; - - if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) && doubleQuoteOccurrences % 2 !== 0) { - // " is found and is in front of ' (if any) - lastQuotation = '"'; - incompleteToken = true; - } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) && singleQuoteOccurrences % 2 !== 0) { - // ' is found and is in front of " (if any) - lastQuotation = '\''; - incompleteToken = true; - } - - if (keyMatch && tokenValue.length > 0) { - tokens.push({ - key: keyMatch.key, - value: tokenValue, - wildcard: symbolMatch ? false : true, - }); - lastToken = tokens.last(); - - return; - } - } - - // Add space for next term - searchTerms += `${i} `; - lastToken = i; - }, this); - - searchToken = searchTerms.trim(); - - return { - tokens, - searchToken, - lastToken, - }; - } - } - - window.gl = window.gl || {}; - gl.FilteredSearchTokenizer = FilteredSearchTokenizer; -})(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 new file mode 100644 index 00000000000..0507f7bbc48 --- /dev/null +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -0,0 +1,165 @@ +(() => { + class FilteredSearchTokenizer { + static parseToken(input) { + const colonIndex = input.indexOf(':'); + let tokenKey; + let tokenValue; + let tokenSymbol; + + if (colonIndex !== -1) { + tokenKey = input.slice(0, colonIndex).toLowerCase(); + tokenValue = input.slice(colonIndex + 1); + tokenSymbol = tokenValue[0]; + } + + return { + tokenKey, + tokenValue, + tokenSymbol, + } + } + + static getLastTokenObject(input) { + const token = FilteredSearchTokenizer.getLastToken(input); + const colonIndex = token.indexOf(':'); + + const key = colonIndex !== -1 ? token.slice(0, colonIndex) : ''; + const value = colonIndex !== -1 ? token.slice(colonIndex) : token; + + return { + key, + value, + } + } + + static getLastToken(input) { + let completeToken = false; + let completeQuotation = true; + let lastQuotation = ''; + let i = input.length; + + const doubleQuote = '"'; + const singleQuote = '\''; + while(!completeToken && i >= 0) { + const isDoubleQuote = input[i] === doubleQuote; + const isSingleQuote = input[i] === singleQuote; + + // If the second quotation is found + if ((lastQuotation === doubleQuote && input[i] === doubleQuote) || + (lastQuotation === singleQuote && input[i] === singleQuote)) { + completeQuotation = true; + } + + // Save the first quotation + if ((input[i] === doubleQuote && lastQuotation === '') || + (input[i] === singleQuote && lastQuotation === '')) { + lastQuotation = input[i]; + completeQuotation = false; + } + + if (completeQuotation && input[i] === ' ') { + completeToken = true; + } else { + i--; + } + } + + // Adjust by 1 because of empty space + return input.slice(i + 1); + } + + static processTokens(input) { + let tokens = []; + let searchToken = ''; + let lastToken = ''; + + const inputs = input.split(' '); + let searchTerms = ''; + let lastQuotation = ''; + let incompleteToken = false; + + // Iterate through each word (broken up by spaces) + inputs.forEach((i) => { + if (incompleteToken) { + // Continue previous token as it had an escaped + // quote in the beginning + const prevToken = tokens.last(); + prevToken.value += ` ${i}`; + + // Remove last quotation from the value + const lastQuotationRegex = new RegExp(lastQuotation, 'g'); + prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); + tokens[tokens.length - 1] = prevToken; + + // Check to see if this quotation completes the token value + if (i.indexOf(lastQuotation) !== -1) { + lastToken = tokens.last(); + incompleteToken = !incompleteToken; + } + + return; + } + + const colonIndex = i.indexOf(':'); + + if (colonIndex !== -1) { + const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i); + + const keyMatch = gl.FilteredSearchTokenKeys.searchByKey(tokenKey); + const symbolMatch = gl.FilteredSearchTokenKeys.searchBySymbol(tokenSymbol); + + const doubleQuoteOccurrences = tokenValue.split('"').length - 1; + const singleQuoteOccurrences = tokenValue.split('\'').length - 1; + + const doubleQuoteIndex = tokenValue.indexOf('"'); + const singleQuoteIndex = tokenValue.indexOf('\''); + + const doubleQuoteExist = doubleQuoteIndex !== -1; + const singleQuoteExist = singleQuoteIndex !== -1; + + const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist; + const doubleQuoteIsBeforeSingleQuote = doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; + + const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist; + const singleQuoteIsBeforeDoubleQuote = doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; + + if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) && doubleQuoteOccurrences % 2 !== 0) { + // " is found and is in front of ' (if any) + lastQuotation = '"'; + incompleteToken = true; + } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) && singleQuoteOccurrences % 2 !== 0) { + // ' is found and is in front of " (if any) + lastQuotation = '\''; + incompleteToken = true; + } + + if (keyMatch && tokenValue.length > 0) { + tokens.push({ + key: keyMatch.key, + value: tokenValue, + wildcard: symbolMatch ? false : true, + }); + lastToken = tokens.last(); + + return; + } + } + + // Add space for next term + searchTerms += `${i} `; + lastToken = i; + }, this); + + searchToken = searchTerms.trim(); + + return { + tokens, + searchToken, + lastToken, + }; + } + } + + window.gl = window.gl || {}; + gl.FilteredSearchTokenizer = FilteredSearchTokenizer; +})(); -- cgit v1.2.3 From 4786a9780337839844d5839fefda51430e13685e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 21:36:54 -0600 Subject: Fix es6 errors --- app/assets/javascripts/droplab/droplab.js | 101 ++++++++++++++------- .../javascripts/droplab/droplab_ajax_filter.js | 2 +- .../javascripts/lib/utils/common_utils.js.es6 | 2 +- 3 files changed, 68 insertions(+), 37 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 359cd82bbcd..94236153e41 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -1,3 +1,29 @@ +// Determine where to place this +if (typeof Object.assign != 'function') { + Object.assign = function (target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + /* eslint-disable */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.droplab = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Date: Tue, 13 Dec 2016 21:55:25 -0600 Subject: Fix eslint --- app/assets/javascripts/droplab/droplab.js | 2 +- .../filtered_search/dropdown_hint.js.es6 | 18 ++------- .../filtered_search/dropdown_non_user.js.es6 | 46 +++------------------- .../filtered_search/dropdown_user.js.es6 | 3 ++ .../filtered_search_dropdown.js.es6 | 13 +----- .../filtered_search_dropdown_manager.js.es6 | 2 + .../filtered_search/filtered_search_manager.js.es6 | 2 + .../filtered_search_tokenizer.js.es6 | 32 ++++++++------- 8 files changed, 37 insertions(+), 81 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js index 94236153e41..ed545ec8748 100644 --- a/app/assets/javascripts/droplab/droplab.js +++ b/app/assets/javascripts/droplab/droplab.js @@ -1,3 +1,4 @@ +/* eslint-disable */ // Determine where to place this if (typeof Object.assign != 'function') { Object.assign = function (target, varArgs) { // .length of function is 2 @@ -24,7 +25,6 @@ if (typeof Object.assign != 'function') { }; } -/* eslint-disable */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.droplab = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { const dropdownData = [{ icon: 'fa-pencil', @@ -24,7 +27,7 @@ this.config = { droplabFilter: { template: 'hint', - filterFunction: this.filterMethod, + filterFunction: gl.DropdownUtils.filterMethod, }, }; } @@ -59,19 +62,6 @@ this.droplab.setData(this.hookId, dropdownData); } - filterMethod(item, query) { - const updatedItem = item; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - - if (value === '') { - updatedItem.droplab_hidden = false; - } else { - updatedItem.droplab_hidden = updatedItem.hint.indexOf(value) === -1; - } - - return updatedItem; - } - init() { this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); } diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 95133db4c04..54090375c5c 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -1,4 +1,8 @@ /*= require filtered_search/filtered_search_dropdown */ + +/* global droplabAjax */ +/* global droplabFilter */ + (() => { class DropdownNonUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input, endpoint, symbol) { @@ -11,7 +15,7 @@ loadingTemplate: this.loadingTemplate, }, droplabFilter: { - filterFunction: this.filterWithSymbol.bind(this, this.symbol), + filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol), }, }; } @@ -19,48 +23,10 @@ itemClicked(e) { super.itemClicked(e, (selected) => { const title = selected.querySelector('.js-data-value').innerText.trim(); - return `${this.symbol}${this.getEscapedText(title)}`; + return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`; }); } - getEscapedText(text) { - let escapedText = text; - const hasSpace = text.indexOf(' ') !== -1; - const hasDoubleQuote = text.indexOf('"') !== -1; - - // Encapsulate value with quotes if it has spaces - // Known side effect: values's with both single and double quotes - // won't escape properly - if (hasSpace) { - if (hasDoubleQuote) { - escapedText = `'${text}'`; - } else { - // Encapsulate singleQuotes or if it hasSpace - escapedText = `"${text}"`; - } - } - - return escapedText; - } - - filterWithSymbol(filterSymbol, item, query) { - const updatedItem = item; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); - const prefix = valueWithoutColon[0]; - const valueWithoutPrefix = valueWithoutColon.slice(1); - - const title = updatedItem.title.toLowerCase(); - - // Eg. filterSymbol = ~ for labels - const matchWithoutPrefix = - prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; - const match = title.indexOf(valueWithoutColon) !== -1; - - updatedItem.droplab_hidden = !match && !matchWithoutPrefix; - return updatedItem; - } - renderContent(forceShowList = false) { this.droplab .changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 2ee46559e63..7a566907312 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -1,4 +1,7 @@ /*= require filtered_search/filtered_search_dropdown */ + +/* global droplabAjaxFilter */ + (() => { class DropdownUser extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 7ddfdca10fa..6c66a3b0613 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -28,7 +28,7 @@ itemClicked(e, getValueFunction) { const { selected } = e.detail; - const dataValueSet = this.setDataValueIfSelected(selected); + const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected); if (!dataValueSet) { const value = getValueFunction(selected); @@ -46,17 +46,6 @@ this.dropdown.style.left = `${offset}px`; } - setDataValueIfSelected(selected) { - const dataValue = selected.getAttribute('data-value'); - - if (dataValue) { - gl.FilteredSearchDropdownManager.addWordToInput(dataValue); - } - - // Return boolean based on whether it was set - return dataValue !== null; - } - renderContent(forceShowList = false) { if (forceShowList && this.getCurrentHook().list.hidden) { this.getCurrentHook().list.show(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 7864ebf7aa1..ac71b5e4434 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -1,3 +1,5 @@ +/* global DropLab */ + (() => { class FilteredSearchDropdownManager { constructor() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 96131a673ef..e5b37f1e691 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,3 +1,5 @@ +/* global Turbolinks */ + (() => { class FilteredSearchManager { constructor() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 index 0507f7bbc48..57c0e8fc359 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -16,7 +16,7 @@ tokenKey, tokenValue, tokenSymbol, - } + }; } static getLastTokenObject(input) { @@ -29,7 +29,7 @@ return { key, value, - } + }; } static getLastToken(input) { @@ -40,19 +40,19 @@ const doubleQuote = '"'; const singleQuote = '\''; - while(!completeToken && i >= 0) { + while (!completeToken && i >= 0) { const isDoubleQuote = input[i] === doubleQuote; const isSingleQuote = input[i] === singleQuote; // If the second quotation is found - if ((lastQuotation === doubleQuote && input[i] === doubleQuote) || - (lastQuotation === singleQuote && input[i] === singleQuote)) { + if ((lastQuotation === doubleQuote && isDoubleQuote) || + (lastQuotation === singleQuote && isSingleQuote)) { completeQuotation = true; } // Save the first quotation - if ((input[i] === doubleQuote && lastQuotation === '') || - (input[i] === singleQuote && lastQuotation === '')) { + if ((isDoubleQuote && lastQuotation === '') || + (isSingleQuote && lastQuotation === '')) { lastQuotation = input[i]; completeQuotation = false; } @@ -60,7 +60,7 @@ if (completeQuotation && input[i] === ' ') { completeToken = true; } else { - i--; + i -= 1; } } @@ -69,7 +69,7 @@ } static processTokens(input) { - let tokens = []; + const tokens = []; let searchToken = ''; let lastToken = ''; @@ -118,16 +118,20 @@ const singleQuoteExist = singleQuoteIndex !== -1; const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist; - const doubleQuoteIsBeforeSingleQuote = doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; + const doubleQuoteIsBeforeSingleQuote = + doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist; - const singleQuoteIsBeforeDoubleQuote = doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; + const singleQuoteIsBeforeDoubleQuote = + doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; - if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) && doubleQuoteOccurrences % 2 !== 0) { + if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) + && doubleQuoteOccurrences % 2 !== 0) { // " is found and is in front of ' (if any) lastQuotation = '"'; incompleteToken = true; - } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) && singleQuoteOccurrences % 2 !== 0) { + } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) + && singleQuoteOccurrences % 2 !== 0) { // ' is found and is in front of " (if any) lastQuotation = '\''; incompleteToken = true; @@ -137,7 +141,7 @@ tokens.push({ key: keyMatch.key, value: tokenValue, - wildcard: symbolMatch ? false : true, + wildcard: !symbolMatch, }); lastToken = tokens.last(); -- cgit v1.2.3 From 2461b9b635501fc9ae98d246e0dd6d23af555351 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 21:59:28 -0600 Subject: Fix scss lint --- app/assets/stylesheets/framework/filters.scss | 8 ++++---- app/assets/stylesheets/framework/variables.scss | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index b6c137d647a..dbe94813a93 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -39,7 +39,7 @@ padding-right: 25px; &:focus ~ .fa-filter { - color: #444; + color: $common-gray-dark; } } @@ -65,7 +65,7 @@ outline: none; &:hover .fa-times { - color: #444; + color: $common-gray-dark; } } } @@ -92,11 +92,11 @@ &:hover, &:focus { background-color: $dropdown-hover-color; - color: white; + color: $white-light; text-decoration: none; .dropdown-label-box { - border-color: white; + border-color: $white-light; border-style: solid; border-width: 2px; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index f3cb3d33d99..cf9424ea5dd 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -266,7 +266,7 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%); /* * Filtered Search */ -$dropdown-hover-color: #3B86FF; +$dropdown-hover-color: #3b86ff; /* * Buttons -- cgit v1.2.3 From 1f7659912ca73f6774c4f1b66ad4e5e48cc51068 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Dec 2016 23:30:28 -0600 Subject: Add jasmine tests to dropdown utils --- .../filtered_search/dropdown_utils.js.es6 | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 app/assets/javascripts/filtered_search/dropdown_utils.js.es6 (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 new file mode 100644 index 00000000000..3837b020fd3 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -0,0 +1,68 @@ +(() => { + class DropdownUtils { + static getEscapedText(text) { + let escapedText = text; + const hasSpace = text.indexOf(' ') !== -1; + const hasDoubleQuote = text.indexOf('"') !== -1; + + // Encapsulate value with quotes if it has spaces + // Known side effect: values's with both single and double quotes + // won't escape properly + if (hasSpace) { + if (hasDoubleQuote) { + escapedText = `'${text}'`; + } else { + // Encapsulate singleQuotes or if it hasSpace + escapedText = `"${text}"`; + } + } + + return escapedText; + } + + static filterWithSymbol(filterSymbol, item, query) { + const updatedItem = item; + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const valueWithoutColon = value.slice(1).toLowerCase(); + const prefix = valueWithoutColon[0]; + const valueWithoutPrefix = valueWithoutColon.slice(1); + + const title = updatedItem.title.toLowerCase(); + + // Eg. filterSymbol = ~ for labels + const matchWithoutPrefix = + prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; + const match = title.indexOf(valueWithoutColon) !== -1; + + updatedItem.droplab_hidden = !match && !matchWithoutPrefix; + return updatedItem; + } + + static filterMethod(item, query) { + const updatedItem = item; + const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + + if (value === '') { + updatedItem.droplab_hidden = false; + } else { + updatedItem.droplab_hidden = updatedItem.hint.indexOf(value) === -1; + } + + return updatedItem; + } + + static setDataValueIfSelected(selected) { + const dataValue = selected.getAttribute('data-value'); + + if (dataValue) { + gl.FilteredSearchDropdownManager.addWordToInput(dataValue); + } + + // Return boolean based on whether it was set + return dataValue !== null; + } + } + + window.gl = window.gl || {}; + gl.DropdownUtils = DropdownUtils; +})(); -- cgit v1.2.3 From 9408693d30cc2af7059cff2c9ddc503a92db86a6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 14 Dec 2016 13:39:03 -0600 Subject: Add webkit to flex --- app/assets/stylesheets/framework/filters.scss | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index dbe94813a93..e47511940a7 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -109,11 +109,14 @@ } .dropdown-user { + display: -webkit-flex; display: flex; } .dropdown-user-details { + display: -webkit-flex; display: flex; + -webkit-flex-direction: column; flex-direction: column; } } -- cgit v1.2.3 From cf391760f19943af59ac43495a91db4126dbeb8d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 14 Dec 2016 13:52:11 -0600 Subject: Fix HAML attributes --- app/views/shared/issuable/_search_bar.html.haml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 335552c0a26..dbef87e67cf 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -18,7 +18,7 @@ = icon('times') #js-dropdown-hint.dropdown-menu.hint-dropdown %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value': '' } + %li.filter-dropdown-item{ 'data-value' => '' } %button.btn.btn-link = icon('search') %span @@ -26,7 +26,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link - %i.fa{ 'class': '{{icon}}'} + %i.fa{ class: '{{icon}}'} %span.js-filter-hint {{hint}} %span.js-filter-tag.dropdown-light-content @@ -35,7 +35,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user - %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', width: '30' } .dropdown-user-details %span {{name}} @@ -43,14 +43,14 @@ @{{username}} #js-dropdown-assignee.dropdown-menu %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value': 'none' } + %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link No Assignee %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user - %img.avatar.avatar-inline{ 'data-src': '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', width: '30' } .dropdown-user-details %span {{name}} @@ -58,10 +58,10 @@ @{{username}} #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value': 'none' } + %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link No Milestone - %li.filter-dropdown-item{ 'data-value': 'upcoming' } + %li.filter-dropdown-item{ 'data-value' => 'upcoming' } %button.btn.btn-link Upcoming %li.divider @@ -71,14 +71,14 @@ {{title}} #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true } - %li.filter-dropdown-item{ 'data-value': 'none' } + %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link No Label %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link - %span.dropdown-label-box{ 'style': 'background: {{color}}'} + %span.dropdown-label-box{ style => 'background: {{color}}'} %span.label-title.js-data-value {{title}} .pull-right -- cgit v1.2.3 From d93ccb8e0949e345efa9a1dcf874c73f8d1975bc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 14 Dec 2016 15:34:04 -0600 Subject: Fix invalid style attribute operator --- app/views/shared/issuable/_search_bar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index dbef87e67cf..aca39941381 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -78,7 +78,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link - %span.dropdown-label-box{ style => 'background: {{color}}'} + %span.dropdown-label-box{ style: 'background: {{color}}'} %span.label-title.js-data-value {{title}} .pull-right -- cgit v1.2.3 From 657ac981acdcd2b070d838e530a2620c4db8bf04 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 09:07:34 -0600 Subject: Fix spinach tests --- app/assets/javascripts/dispatcher.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 9a76131b87f..1e9111f4718 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -84,7 +84,7 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': - if(gl.FilteredSearchManager) { + if(document.querySelector('.filtered-search') && gl.FilteredSearchManager) { new gl.FilteredSearchManager(); } Issuable.init(); -- cgit v1.2.3 From 78dd92b730063371742b6487ae2526d6cc0943b1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 11:20:05 -0600 Subject: Improve styling of hover states --- app/assets/stylesheets/framework/filters.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index e47511940a7..8b7cb245420 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -89,16 +89,22 @@ overflow-y: hidden; border-radius: 0; + .dropdown-label-box { + border-color: $white-light; + border-style: solid; + border-width: 1px; + width: 17px; + height: 17px; + } + &:hover, &:focus { background-color: $dropdown-hover-color; color: $white-light; text-decoration: none; - .dropdown-label-box { + .avatar { border-color: $white-light; - border-style: solid; - border-width: 2px; } } } -- cgit v1.2.3 From 776f1aaae4a8125f6f46ed4a095566a00ea2aa45 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 12:54:24 -0600 Subject: Add specs for filtered search token keys --- .../javascripts/filtered_search/filtered_search_token_keys.js.es6 | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index a1830d13e5f..6bd9cb06362 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -44,6 +44,10 @@ return tokenKeys; } + static getConditions() { + return conditions; + } + static searchByKey(key) { return tokenKeys.find(tokenKey => tokenKey.key === key) || null; } -- cgit v1.2.3 From 4f774c940f1bbcadaead168e6ee5dd5c54864c7f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 16:11:37 -0600 Subject: Remove if issue.boards since search bar does not display on issue boards page --- app/views/shared/issuable/_search_bar.html.haml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index aca39941381..896769768eb 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -82,20 +82,7 @@ %span.label-title.js-data-value {{title}} .pull-right - - if boards_page - #js-boards-seach.issue-boards-search - %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } - - if can?(current_user, :admin_list, @project) - .dropdown.pull-right - %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } - Create new list - .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } - - if can?(current_user, :admin_label, @project) - = render partial: "shared/issuable/label_page_create" - = dropdown_loading - - else - = render 'shared/sort_dropdown' + = render 'shared/sort_dropdown' - if @bulk_edit .issues_bulk_update.hide -- cgit v1.2.3 From d19303cbe1e9813f5fe2409908c7f89616ec5eac Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 23:08:49 -0600 Subject: Fix dropdown hint reset when changing tabs --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 34079b25846..b9f552b62b9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -59,7 +59,15 @@ renderContent() { this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); - this.droplab.setData(this.hookId, dropdownData); + + // Clone dropdownData to prevent it from being + // changed due to pass by reference + const data = []; + dropdownData.forEach((item) => { + data.push(Object.assign({}, item)); + }); + + this.droplab.setData(this.hookId, data); } init() { -- cgit v1.2.3 From 61680a2d9833cc3ef63b9e76930f47e44258f30d Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Dec 2016 23:20:31 -0600 Subject: Add selected tagName check for itemClicked --- .../javascripts/filtered_search/dropdown_hint.js.es6 | 20 +++++++++++--------- .../filtered_search/filtered_search_dropdown.js.es6 | 15 +++++++++------ 2 files changed, 20 insertions(+), 15 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index b9f552b62b9..bdcece61984 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -35,18 +35,20 @@ itemClicked(e) { const { selected } = e.detail; - if (selected.hasAttribute('data-value')) { + if (selected.tagName === 'LI') { + if (selected.hasAttribute('data-value')) { this.dismissDropdown(); - } else { - const token = selected.querySelector('.js-filter-hint').innerText.trim(); - const tag = selected.querySelector('.js-filter-tag').innerText.trim(); + } else { + const token = selected.querySelector('.js-filter-hint').innerText.trim(); + const tag = selected.querySelector('.js-filter-tag').innerText.trim(); - if (tag.length) { - gl.FilteredSearchDropdownManager - .addWordToInput(this.getSelectedTextWithoutEscaping(token)); + if (tag.length) { + gl.FilteredSearchDropdownManager + .addWordToInput(this.getSelectedTextWithoutEscaping(token)); + } + this.dismissDropdown(); + this.dispatchInputEvent(); } - this.dismissDropdown(); - this.dispatchInputEvent(); } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 6c66a3b0613..68014e27462 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -28,14 +28,17 @@ itemClicked(e, getValueFunction) { const { selected } = e.detail; - const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected); - if (!dataValueSet) { - const value = getValueFunction(selected); - gl.FilteredSearchDropdownManager.addWordToInput(value); - } + if (selected.tagName === 'LI') { + const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected); + + if (!dataValueSet) { + const value = getValueFunction(selected); + gl.FilteredSearchDropdownManager.addWordToInput(value); + } - this.dismissDropdown(); + this.dismissDropdown(); + } } setAsDropdown() { -- cgit v1.2.3 From 9c4868141273b536cc0bc7fb80a662789fe89286 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 16 Dec 2016 10:22:09 -0600 Subject: Make changes to make it more flexible for new filters --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 2 +- .../filtered_search/filtered_search_manager.js.es6 | 12 +++++++++--- .../filtered_search/filtered_search_token_keys.js.es6 | 10 +++++++++- app/assets/stylesheets/framework/filters.scss | 4 ++++ 4 files changed, 23 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index bdcece61984..7bf30143d78 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -37,7 +37,7 @@ if (selected.tagName === 'LI') { if (selected.hasAttribute('data-value')) { - this.dismissDropdown(); + this.dismissDropdown(); } else { const token = selected.querySelector('.js-filter-hint').innerText.trim(); const tag = selected.querySelector('.js-filter-tag').innerText.trim(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index e5b37f1e691..565f2347072 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -101,7 +101,8 @@ const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); if (match) { - const sanitizedKey = keyParam.slice(0, keyParam.indexOf('_')); + const indexOf = keyParam.indexOf('_'); + const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam; const symbol = match.symbol; let quotationsToUse = ''; @@ -137,14 +138,19 @@ const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); let tokenPath = ''; + let keyParam = token.key; + if (param) { + keyParam += `_${param}`; + } + if (token.wildcard && condition) { tokenPath = condition.url; } else if (token.wildcard) { // wildcard means that the token does not have a symbol - tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value)}`; + tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`; } else { // Remove the token symbol - tokenPath = `${token.key}_${param}=${encodeURIComponent(token.value.slice(1))}`; + tokenPath = `${keyParam}=${encodeURIComponent(token.value.slice(1))}`; } paths.push(tokenPath); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index 6bd9cb06362..e46373024b6 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -57,7 +57,15 @@ } static searchByKeyParam(keyParam) { - return tokenKeys.find(tokenKey => keyParam === `${tokenKey.key}_${tokenKey.param}`) || null; + return tokenKeys.find((tokenKey) => { + let tokenKeyParam = tokenKey.key; + + if (tokenKey.param) { + tokenKeyParam += `_${tokenKey.param}`; + } + + return keyParam === tokenKeyParam; + }) || null; } static searchByConditionUrl(url) { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 8b7cb245420..fee38b05023 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -89,6 +89,10 @@ overflow-y: hidden; border-radius: 0; + .fa { + width: 15px; + } + .dropdown-label-box { border-color: $white-light; border-style: solid; -- cgit v1.2.3 From 0e40c952d6d715580ed0ec891dc6f4fdc810673e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 16 Dec 2016 18:19:54 -0600 Subject: Remove unused finder variable --- app/views/shared/issuable/_search_bar.html.haml | 3 --- 1 file changed, 3 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 896769768eb..3449c1f0151 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,6 +1,3 @@ -- finder = controller.controller_name == 'issues' || controller.controller_name == 'boards' ? issues_finder : merge_requests_finder -- boards_page = controller.controller_name == 'boards' - .issues-filters .issues-details-filters.row-content-block.second-block.filtered-search-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do -- cgit v1.2.3 From e197f27f19ab7995d280f67754ea16c2629701b2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 17 Dec 2016 13:22:00 -0600 Subject: Refactor and use regex for string processing --- .../filtered_search/dropdown_hint.js.es6 | 58 +++---- .../filtered_search/dropdown_user.js.es6 | 9 +- .../filtered_search/dropdown_utils.js.es6 | 30 ++-- .../filtered_search_dropdown.js.es6 | 2 +- .../filtered_search_dropdown_manager.js.es6 | 34 ++-- .../filtered_search/filtered_search_manager.js.es6 | 14 +- .../filtered_search_tokenizer.js.es6 | 178 +++------------------ 7 files changed, 90 insertions(+), 235 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 7bf30143d78..c5ab9c52d76 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -3,31 +3,13 @@ /* global droplabFilter */ (() => { - const dropdownData = [{ - icon: 'fa-pencil', - hint: 'author:', - tag: '<author>', - }, { - icon: 'fa-user', - hint: 'assignee:', - tag: '<assignee>', - }, { - icon: 'fa-clock-o', - hint: 'milestone:', - tag: '<milestone>', - }, { - icon: 'fa-tag', - hint: 'label:', - tag: '<label>', - }]; - class DropdownHint extends gl.FilteredSearchDropdown { constructor(droplab, dropdown, input) { super(droplab, dropdown, input); this.config = { droplabFilter: { template: 'hint', - filterFunction: gl.DropdownUtils.filterMethod, + filterFunction: gl.DropdownUtils.filterHint, }, }; } @@ -43,8 +25,7 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchDropdownManager - .addWordToInput(this.getSelectedTextWithoutEscaping(token)); + gl.FilteredSearchDropdownManager.addWordToInput(token); } this.dismissDropdown(); this.dispatchInputEvent(); @@ -52,24 +33,27 @@ } } - getSelectedTextWithoutEscaping(selectedToken) { - const lastWord = this.input.value.split(' ').last(); - const lastWordIndex = selectedToken.indexOf(lastWord); - - return lastWordIndex === -1 ? selectedToken : selectedToken.slice(lastWord.length); - } - renderContent() { - this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); - - // Clone dropdownData to prevent it from being - // changed due to pass by reference - const data = []; - dropdownData.forEach((item) => { - data.push(Object.assign({}, item)); - }); + const dropdownData = [{ + icon: 'fa-pencil', + hint: 'author:', + tag: '<author>', + }, { + icon: 'fa-user', + hint: 'assignee:', + tag: '<assignee>', + }, { + icon: 'fa-clock-o', + hint: 'milestone:', + tag: '<milestone>', + }, { + icon: 'fa-tag', + hint: 'label:', + tag: '<label>', + }]; - this.droplab.setData(this.hookId, data); + this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); + this.droplab.setData(this.hookId, dropdownData); } init() { diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 7a566907312..49581e3bfbd 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -37,13 +37,10 @@ } getSearchInput() { - const query = this.input.value; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1); - const hasPrefix = valueWithoutColon[0] === '@'; - const valueWithoutPrefix = valueWithoutColon.slice(1); + const query = this.input.value.trim(); + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); - return hasPrefix ? valueWithoutPrefix : valueWithoutColon; + return lastToken.value || ''; } init() { diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 3837b020fd3..d246000ff52 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -22,30 +22,32 @@ static filterWithSymbol(filterSymbol, item, query) { const updatedItem = item; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); - const valueWithoutColon = value.slice(1).toLowerCase(); - const prefix = valueWithoutColon[0]; - const valueWithoutPrefix = valueWithoutColon.slice(1); + const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query); - const title = updatedItem.title.toLowerCase(); + if (lastToken !== searchToken) { + const value = lastToken.value.toLowerCase(); + const title = updatedItem.title.toLowerCase(); - // Eg. filterSymbol = ~ for labels - const matchWithoutPrefix = - prefix === filterSymbol && title.indexOf(valueWithoutPrefix) !== -1; - const match = title.indexOf(valueWithoutColon) !== -1; + // Eg. filterSymbol = ~ for labels + const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; + const match = title.indexOf(`${lastToken.symbol}${value}`) !== -1; + + updatedItem.droplab_hidden = !match && !matchWithoutSymbol; + } else { + updatedItem.droplab_hidden = false; + } - updatedItem.droplab_hidden = !match && !matchWithoutPrefix; return updatedItem; } - static filterMethod(item, query) { + static filterHint(item, query) { const updatedItem = item; - const { value } = gl.FilteredSearchTokenizer.getLastTokenObject(query); + const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); - if (value === '') { + if (!lastToken) { updatedItem.droplab_hidden = false; } else { - updatedItem.droplab_hidden = updatedItem.hint.indexOf(value) === -1; + updatedItem.droplab_hidden = updatedItem.hint.indexOf(lastToken.toLowerCase()) === -1; } return updatedItem; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 68014e27462..56147ad93c9 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -29,7 +29,7 @@ itemClicked(e, getValueFunction) { const { selected } = e.detail; - if (selected.tagName === 'LI') { + if (selected.tagName === 'LI' && selected.innerHTML) { const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected); if (!dataValueSet) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index ac71b5e4434..b67176267fb 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -57,17 +57,25 @@ static addWordToInput(word, addSpace = false) { const input = document.querySelector('.filtered-search'); + input.value = input.value.trim(); + const value = input.value; const hasExistingValue = value.length !== 0; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(value); + const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(value); + + // Find out what part of the token value the user has typed + // and remove it from input before appending the selected token value + if (lastToken !== searchToken) { + const lastTokenString = `${lastToken.symbol}${lastToken.value}`; - if ({}.hasOwnProperty.call(lastToken, 'key')) { // Spaces inside the token means that the token value will be escaped by quotes - const hasQuotes = lastToken.value.indexOf(' ') !== -1; + const hasQuotes = lastTokenString.indexOf(' ') !== -1; // Add 2 length to account for the length of the front and back quotes - const lengthToRemove = hasQuotes ? lastToken.value.length + 2 : lastToken.value.length; + const lengthToRemove = hasQuotes ? lastTokenString.length + 2 : lastTokenString.length; input.value = value.slice(0, -1 * (lengthToRemove)); + } else if (searchToken !== '' && word.indexOf(searchToken) !== -1) { + input.value = value.slice(0, -1 * searchToken.length); } input.value += hasExistingValue && addSpace ? ` ${word}` : word; @@ -129,27 +137,25 @@ const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key - && {}.hasOwnProperty.call(this.mapping, match.key); + && this.mapping[match.key]; const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; if (shouldOpenFilterDropdown || shouldOpenHintDropdown) { - // `hint` is not listed as a tokenKey (since it is not a real `filter`) - const key = match && {}.hasOwnProperty.call(match, 'key') ? match.key : 'hint'; + const key = match && match.key ? match.key : 'hint'; this.load(key, firstLoad); } - - gl.droplab = this.droplab; } setDropdown() { - const { lastToken } = this.tokenizer.processTokens(this.filteredSearchInput.value); + const { lastToken, searchToken } = this.tokenizer + .processTokens(this.filteredSearchInput.value); - if (typeof lastToken === 'string') { + if (lastToken === searchToken) { // Token is not fully initialized yet because it has no value // Eg. token = 'label:' - const { tokenKey } = this.tokenizer.parseToken(lastToken); - this.loadDropdown(tokenKey); - } else if ({}.hasOwnProperty.call(lastToken, 'key')) { + const split = lastToken.split(':'); + this.loadDropdown(split.length > 1 ? split[0] : ''); + } else if (lastToken) { // Token has been initialized into an object because it has a value this.loadDropdown(lastToken.key); } else { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 565f2347072..d2ea4de18aa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -136,21 +136,13 @@ const condition = gl.FilteredSearchTokenKeys .searchByConditionKeyValue(token.key, token.value.toLowerCase()); const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); + const keyParam = param ? `${token.key}_${param}` : token.key; let tokenPath = ''; - let keyParam = token.key; - if (param) { - keyParam += `_${param}`; - } - - if (token.wildcard && condition) { + if (condition) { tokenPath = condition.url; - } else if (token.wildcard) { - // wildcard means that the token does not have a symbol - tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`; } else { - // Remove the token symbol - tokenPath = `${keyParam}=${encodeURIComponent(token.value.slice(1))}`; + tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`; } paths.push(tokenPath); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 index 57c0e8fc359..14ca78e139b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -1,165 +1,39 @@ (() => { class FilteredSearchTokenizer { - static parseToken(input) { - const colonIndex = input.indexOf(':'); - let tokenKey; - let tokenValue; - let tokenSymbol; - - if (colonIndex !== -1) { - tokenKey = input.slice(0, colonIndex).toLowerCase(); - tokenValue = input.slice(colonIndex + 1); - tokenSymbol = tokenValue[0]; - } - - return { - tokenKey, - tokenValue, - tokenSymbol, - }; - } - - static getLastTokenObject(input) { - const token = FilteredSearchTokenizer.getLastToken(input); - const colonIndex = token.indexOf(':'); - - const key = colonIndex !== -1 ? token.slice(0, colonIndex) : ''; - const value = colonIndex !== -1 ? token.slice(colonIndex) : token; - - return { - key, - value, - }; - } - - static getLastToken(input) { - let completeToken = false; - let completeQuotation = true; - let lastQuotation = ''; - let i = input.length; - - const doubleQuote = '"'; - const singleQuote = '\''; - while (!completeToken && i >= 0) { - const isDoubleQuote = input[i] === doubleQuote; - const isSingleQuote = input[i] === singleQuote; - - // If the second quotation is found - if ((lastQuotation === doubleQuote && isDoubleQuote) || - (lastQuotation === singleQuote && isSingleQuote)) { - completeQuotation = true; - } - - // Save the first quotation - if ((isDoubleQuote && lastQuotation === '') || - (isSingleQuote && lastQuotation === '')) { - lastQuotation = input[i]; - completeQuotation = false; - } - - if (completeQuotation && input[i] === ' ') { - completeToken = true; - } else { - i -= 1; - } - } - - // Adjust by 1 because of empty space - return input.slice(i + 1); - } - static processTokens(input) { + const tokenRegex = /(\w+):([~%@]?)(?:"(.*?)"|'(.*?)'|(\S+))/g; const tokens = []; - let searchToken = ''; - let lastToken = ''; - - const inputs = input.split(' '); - let searchTerms = ''; - let lastQuotation = ''; - let incompleteToken = false; - - // Iterate through each word (broken up by spaces) - inputs.forEach((i) => { - if (incompleteToken) { - // Continue previous token as it had an escaped - // quote in the beginning - const prevToken = tokens.last(); - prevToken.value += ` ${i}`; - - // Remove last quotation from the value - const lastQuotationRegex = new RegExp(lastQuotation, 'g'); - prevToken.value = prevToken.value.replace(lastQuotationRegex, ''); - tokens[tokens.length - 1] = prevToken; - - // Check to see if this quotation completes the token value - if (i.indexOf(lastQuotation) !== -1) { - lastToken = tokens.last(); - incompleteToken = !incompleteToken; - } - - return; - } - - const colonIndex = i.indexOf(':'); - - if (colonIndex !== -1) { - const { tokenKey, tokenValue, tokenSymbol } = gl.FilteredSearchTokenizer.parseToken(i); - - const keyMatch = gl.FilteredSearchTokenKeys.searchByKey(tokenKey); - const symbolMatch = gl.FilteredSearchTokenKeys.searchBySymbol(tokenSymbol); - - const doubleQuoteOccurrences = tokenValue.split('"').length - 1; - const singleQuoteOccurrences = tokenValue.split('\'').length - 1; - - const doubleQuoteIndex = tokenValue.indexOf('"'); - const singleQuoteIndex = tokenValue.indexOf('\''); - - const doubleQuoteExist = doubleQuoteIndex !== -1; - const singleQuoteExist = singleQuoteIndex !== -1; - - const doubleQuoteExistOnly = doubleQuoteExist && !singleQuoteExist; - const doubleQuoteIsBeforeSingleQuote = - doubleQuoteExist && singleQuoteExist && doubleQuoteIndex < singleQuoteIndex; - - const singleQuoteExistOnly = singleQuoteExist && !doubleQuoteExist; - const singleQuoteIsBeforeDoubleQuote = - doubleQuoteExist && singleQuoteExist && singleQuoteIndex < doubleQuoteIndex; - - if ((doubleQuoteExistOnly || doubleQuoteIsBeforeSingleQuote) - && doubleQuoteOccurrences % 2 !== 0) { - // " is found and is in front of ' (if any) - lastQuotation = '"'; - incompleteToken = true; - } else if ((singleQuoteExistOnly || singleQuoteIsBeforeDoubleQuote) - && singleQuoteOccurrences % 2 !== 0) { - // ' is found and is in front of " (if any) - lastQuotation = '\''; - incompleteToken = true; - } - - if (keyMatch && tokenValue.length > 0) { - tokens.push({ - key: keyMatch.key, - value: tokenValue, - wildcard: !symbolMatch, - }); - lastToken = tokens.last(); - - return; - } + let lastToken = null; + const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { + let tokenValue = v1 || v2 || v3; + let tokenSymbol = symbol; + + if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { + tokenSymbol = tokenValue; + tokenValue = ''; } - // Add space for next term - searchTerms += `${i} `; - lastToken = i; - }, this); - - searchToken = searchTerms.trim(); + tokens.push({ + key, + value: tokenValue || '', + symbol: tokenSymbol || '', + }); + return ''; + }).replace(/\s{2,}/g, ' ').trim() || ''; + + if (tokens.length > 0) { + const last = tokens[tokens.length - 1]; + const lastString = `${last.key}:${last.symbol}${last.value}`; + lastToken = input.lastIndexOf(lastString) === + input.length - lastString.length ? last : searchToken; + } else { + lastToken = searchToken; + } return { tokens, - searchToken, lastToken, + searchToken, }; } } -- cgit v1.2.3 From f72c1bf1c930b4dcb533202204d132f42246d99f Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 17 Dec 2016 19:55:44 -0600 Subject: Fix specs --- .../javascripts/filtered_search/dropdown_utils.js.es6 | 13 +++++++++---- .../filtered_search_dropdown_manager.js.es6 | 15 +++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index d246000ff52..0c0d24d4de8 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -42,12 +42,17 @@ static filterHint(item, query) { const updatedItem = item; - const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); + lastToken = lastToken || ''; - if (!lastToken) { + if (!lastToken || query.split('').last() === ' ') { updatedItem.droplab_hidden = false; - } else { - updatedItem.droplab_hidden = updatedItem.hint.indexOf(lastToken.toLowerCase()) === -1; + } else if (lastToken) { + const split = lastToken.split(':'); + const tokenName = split[0].split(' ').last(); + + const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; + updatedItem.droplab_hidden = tokenName ? match : false; } return updatedItem; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index b67176267fb..e9f1fbf63ed 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -62,6 +62,7 @@ const value = input.value; const hasExistingValue = value.length !== 0; const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(value); + const lastSearchToken = searchToken.split(' ').last(); // Find out what part of the token value the user has typed // and remove it from input before appending the selected token value @@ -74,8 +75,8 @@ // Add 2 length to account for the length of the front and back quotes const lengthToRemove = hasQuotes ? lastTokenString.length + 2 : lastTokenString.length; input.value = value.slice(0, -1 * (lengthToRemove)); - } else if (searchToken !== '' && word.indexOf(searchToken) !== -1) { - input.value = value.slice(0, -1 * searchToken.length); + } else if (searchToken !== '' && word.indexOf(lastSearchToken) !== -1) { + input.value = value.slice(0, -1 * lastSearchToken.length); } input.value += hasExistingValue && addSpace ? ` ${word}` : word; @@ -150,11 +151,17 @@ const { lastToken, searchToken } = this.tokenizer .processTokens(this.filteredSearchInput.value); - if (lastToken === searchToken) { + if (this.filteredSearchInput.value.split('').last() === ' ') { + this.updateCurrentDropdownOffset(); + } + + if (lastToken === searchToken && lastToken !== null) { // Token is not fully initialized yet because it has no value // Eg. token = 'label:' + const split = lastToken.split(':'); - this.loadDropdown(split.length > 1 ? split[0] : ''); + const dropdownName = split[0].split(' ').last(); + this.loadDropdown(split.length > 1 ? dropdownName : ''); } else if (lastToken) { // Token has been initialized into an object because it has a value this.loadDropdown(lastToken.key); -- cgit v1.2.3 From 12753def903f265467d2cab8e19deced31daf066 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sun, 18 Dec 2016 17:04:29 -0600 Subject: Fix specs --- app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/filtered_search/dropdown_utils.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 1e9111f4718..ca2da18dc26 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -84,7 +84,7 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': - if(document.querySelector('.filtered-search') && gl.FilteredSearchManager) { + if (document.querySelector('.filtered-search') && gl.FilteredSearchManager) { new gl.FilteredSearchManager(); } Issuable.init(); diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 0c0d24d4de8..3453311bee5 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -43,7 +43,7 @@ static filterHint(item, query) { const updatedItem = item; let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query); - lastToken = lastToken || ''; + lastToken = lastToken.key || lastToken || ''; if (!lastToken || query.split('').last() === ' ') { updatedItem.droplab_hidden = false; -- cgit v1.2.3 From b0d8d742c588baacf0ba08074f23e68498297d10 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 19:52:54 +0800 Subject: Pass the arguments from where we render the partial Thread: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7345/diffs#note_19707619 --- app/views/projects/issues/index.html.haml | 2 +- app/views/shared/issuable/_search_bar.html.haml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 18e8372ecab..6585e8de1e7 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -32,7 +32,7 @@ title: "New Issue", id: "new_issue_link" do New Issue - = render 'shared/issuable/search_bar', type: :issues + = render 'shared/issuable/search_bar', type: :issues, finder: issues_finder .issues-holder = render 'issues' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 3449c1f0151..b65d523ddc8 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,3 +1,6 @@ +- type = local_assigns.fetch(:type) +- finder = local_assigns.fetch(:finder) + .issues-filters .issues-details-filters.row-content-block.second-block.filtered-search-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do -- cgit v1.2.3 From 866bb202f29b8f5ad52563dd48ae57168dc6df77 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 19:58:59 +0800 Subject: Check if it's not NONE too So that we don't have to check it again in somewhere else, and we don't really need to know if it's presented as NONE Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7345/diffs#note_20123999 --- app/finders/issuable_finder.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2afde8ece65..dfd7de6afa9 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -166,20 +166,20 @@ class IssuableFinder end def assignee_id? - params[:assignee_id].present? + params[:assignee_id].present? && params[:assignee_id] != NONE end def assignee_username? - params[:assignee_username].present? + params[:assignee_username].present? && params[:assignee_username] != NONE end def assignee return @assignee if defined?(@assignee) @assignee = - if assignee_id? && params[:assignee_id] != NONE + if assignee_id? User.find(params[:assignee_id]) - elsif assignee_username? && params[:assignee_username] != NONE + elsif assignee_username? User.find_by(username: params[:assignee_username]) else nil @@ -187,11 +187,11 @@ class IssuableFinder end def author_id? - params[:author_id].present? + params[:author_id].present? && params[:author_id] != NONE end def author_username? - params[:author_username].present? + params[:author_username].present? && params[:author_username] != NONE end def author -- cgit v1.2.3 From 464dddf4d0b54085c10ecb8b62aa7816ed7ba8a3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 20:12:59 +0800 Subject: Show no issues if author/assignee cannot be found Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7345/diffs#note_19994225 --- app/finders/issuable_finder.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index dfd7de6afa9..dce756544e7 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -178,7 +178,7 @@ class IssuableFinder @assignee = if assignee_id? - User.find(params[:assignee_id]) + User.find_by(id: params[:assignee_id]) elsif assignee_username? User.find_by(username: params[:assignee_username]) else @@ -198,9 +198,9 @@ class IssuableFinder return @author if defined?(@author) @author = - if author_id? && params[:author_id] != NONE - User.find(params[:author_id]) - elsif author_username? && params[:author_username] != NONE + if author_id? + User.find_by(id: params[:author_id]) + elsif author_username? User.find_by(username: params[:author_username]) else nil @@ -275,16 +275,20 @@ class IssuableFinder end def by_assignee(items) - if assignee_id? || assignee_username? - items = items.where(assignee_id: assignee.try(:id)) + if assignee + items = items.where(assignee_id: assignee.id) + elsif assignee_id? || assignee_username? # assignee not found + items = items.none end items end def by_author(items) - if author_id? || author_username? - items = items.where(author_id: author.try(:id)) + if author + items = items.where(author_id: author.id) + elsif author_id? || author_username? # author not found + items = items.none end items -- cgit v1.2.3 From f7f9e58092892e5bae0887aa2c0ee9f699085aad Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 22:28:18 +0800 Subject: Make sure we could query against no one We should separate the idea of not finding anyone, and the idea of against no one. --- app/finders/issuable_finder.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'app') diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index dce756544e7..5ffaf5ae0f8 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -173,6 +173,10 @@ class IssuableFinder params[:assignee_username].present? && params[:assignee_username] != NONE end + def no_assignee? + params[:assignee_id] == NONE || params[:assignee_username] == NONE + end + def assignee return @assignee if defined?(@assignee) @@ -194,6 +198,10 @@ class IssuableFinder params[:author_username].present? && params[:author_username] != NONE end + def no_author? + params[:author_id] == NONE || params[:author_username] == NONE + end + def author return @author if defined?(@author) @@ -277,6 +285,8 @@ class IssuableFinder def by_assignee(items) if assignee items = items.where(assignee_id: assignee.id) + elsif no_assignee? + items = items.where(assignee_id: nil) elsif assignee_id? || assignee_username? # assignee not found items = items.none end @@ -287,6 +297,8 @@ class IssuableFinder def by_author(items) if author items = items.where(author_id: author.id) + elsif no_author? + items = items.where(author_id: nil) elsif author_id? || author_username? # author not found items = items.none end -- cgit v1.2.3 From 0a1d8875b715431e7631051e29b47c78b6e135ce Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 06:32:42 +0000 Subject: we're actually not using issue finder here --- app/views/projects/issues/index.html.haml | 2 +- app/views/shared/issuable/_search_bar.html.haml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'app') diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 6585e8de1e7..18e8372ecab 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -32,7 +32,7 @@ title: "New Issue", id: "new_issue_link" do New Issue - = render 'shared/issuable/search_bar', type: :issues, finder: issues_finder + = render 'shared/issuable/search_bar', type: :issues .issues-holder = render 'issues' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index b65d523ddc8..8620deb4dfd 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -1,5 +1,4 @@ - type = local_assigns.fetch(:type) -- finder = local_assigns.fetch(:finder) .issues-filters .issues-details-filters.row-content-block.second-block.filtered-search-block -- cgit v1.2.3 From 896497aba37f9e83585443a108e8611e5bc58488 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 5 Jan 2017 10:57:43 -0600 Subject: Fix haml lint --- app/views/shared/issuable/_search_bar.html.haml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 8620deb4dfd..33f96a86723 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -25,7 +25,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link - %i.fa{ class: '{{icon}}'} + %i.fa{ class: "#{'{{icon}}'}" } %span.js-filter-hint {{hint}} %span.js-filter-tag.dropdown-light-content @@ -34,7 +34,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user - %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' } .dropdown-user-details %span {{name}} @@ -49,7 +49,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user - %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', width: '30' } + %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' } .dropdown-user-details %span {{name}} @@ -77,7 +77,7 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link - %span.dropdown-label-box{ style: 'background: {{color}}'} + %span.dropdown-label-box{ style: 'background: {{color}}' } %span.label-title.js-data-value {{title}} .pull-right @@ -90,9 +90,9 @@ = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do %ul %li - %a{href: "#", data: {id: "reopen"}} Open + %a{ href: "#", data: { id: "reopen" } } Open %li - %a{href: "#", data: {id: "close"}} Closed + %a{ href: "#", data: { id: "close" } } Closed .filter-item.inline = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } }) @@ -104,9 +104,9 @@ = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do %ul %li - %a{href: "#", data: {id: "subscribe"}} Subscribe + %a{ href: "#", data: { id: "subscribe" } } Subscribe %li - %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe + %a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe = hidden_field_tag 'update[issuable_ids]', [] = hidden_field_tag :state_event, params[:state_event] -- cgit v1.2.3 From 8f77b3177f1c342cc0a05c79e45e88bcda04040a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 5 Jan 2017 11:01:56 -0600 Subject: Add haml lint comment --- app/views/shared/issuable/_search_bar.html.haml | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app') diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 33f96a86723..8d7b1d616f4 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -25,6 +25,8 @@ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link + -# Encapsulate static class name `{{icon}}` inside #{} to bypass + -# haml lint's ClassAttributeWithStaticValue %i.fa{ class: "#{'{{icon}}'}" } %span.js-filter-hint {{hint}} -- cgit v1.2.3 From c349bb15b628039340054eb132201fdf4a740411 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 5 Jan 2017 13:56:23 -0600 Subject: Refactor addWordToInput --- .../filtered_search/dropdown_hint.js.es6 | 6 ++-- .../filtered_search/dropdown_non_user.js.es6 | 4 +-- .../filtered_search/dropdown_user.js.es6 | 4 +-- .../filtered_search/dropdown_utils.js.es6 | 4 +-- .../filtered_search_dropdown.js.es6 | 7 ++-- .../filtered_search_dropdown_manager.js.es6 | 41 +++++++++++----------- 6 files changed, 33 insertions(+), 33 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index c5ab9c52d76..f1e317d91cc 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -4,8 +4,8 @@ (() => { class DropdownHint extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); this.config = { droplabFilter: { template: 'hint', @@ -25,7 +25,7 @@ const tag = selected.querySelector('.js-filter-tag').innerText.trim(); if (tag.length) { - gl.FilteredSearchDropdownManager.addWordToInput(token); + gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', '')); } this.dismissDropdown(); this.dispatchInputEvent(); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 index 54090375c5c..f06c3fc9c6f 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 @@ -5,8 +5,8 @@ (() => { class DropdownNonUser extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input, endpoint, symbol) { - super(droplab, dropdown, input); + constructor(droplab, dropdown, input, filter, endpoint, symbol) { + super(droplab, dropdown, input, filter); this.symbol = symbol; this.config = { droplabAjax: { diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 index 49581e3bfbd..e80d266ae89 100644 --- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6 @@ -4,8 +4,8 @@ (() => { class DropdownUser extends gl.FilteredSearchDropdown { - constructor(droplab, dropdown, input) { - super(droplab, dropdown, input); + constructor(droplab, dropdown, input, filter) { + super(droplab, dropdown, input, filter); this.config = { droplabAjaxFilter: { endpoint: '/autocomplete/users.json', diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 3453311bee5..88b172d6fc4 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -58,11 +58,11 @@ return updatedItem; } - static setDataValueIfSelected(selected) { + static setDataValueIfSelected(filter, selected) { const dataValue = selected.getAttribute('data-value'); if (dataValue) { - gl.FilteredSearchDropdownManager.addWordToInput(dataValue); + gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue); } // Return boolean based on whether it was set diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index 56147ad93c9..886d8113f4a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -2,10 +2,11 @@ const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; class FilteredSearchDropdown { - constructor(droplab, dropdown, input) { + constructor(droplab, dropdown, input, filter) { this.droplab = droplab; this.hookId = input.getAttribute('data-id'); this.input = input; + this.filter = filter; this.dropdown = dropdown; this.loadingTemplate = `
@@ -30,11 +31,11 @@ const { selected } = e.detail; if (selected.tagName === 'LI' && selected.innerHTML) { - const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(selected); + const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected); if (!dataValueSet) { const value = getValueFunction(selected); - gl.FilteredSearchDropdownManager.addWordToInput(value); + gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value); } this.dismissDropdown(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index e9f1fbf63ed..8b385d6b642 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -55,31 +55,30 @@ }; } - static addWordToInput(word, addSpace = false) { + static addWordToInput(tokenName, tokenValue = '') { const input = document.querySelector('.filtered-search'); - input.value = input.value.trim(); + const word = `${tokenName}:${tokenValue}`; - const value = input.value; - const hasExistingValue = value.length !== 0; - const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(value); + const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(input.value); const lastSearchToken = searchToken.split(' ').last(); - - // Find out what part of the token value the user has typed - // and remove it from input before appending the selected token value - if (lastToken !== searchToken) { - const lastTokenString = `${lastToken.symbol}${lastToken.value}`; - - // Spaces inside the token means that the token value will be escaped by quotes - const hasQuotes = lastTokenString.indexOf(' ') !== -1; - - // Add 2 length to account for the length of the front and back quotes - const lengthToRemove = hasQuotes ? lastTokenString.length + 2 : lastTokenString.length; - input.value = value.slice(0, -1 * (lengthToRemove)); - } else if (searchToken !== '' && word.indexOf(lastSearchToken) !== -1) { - input.value = value.slice(0, -1 * lastSearchToken.length); + const lastInputCharacter = input.value[input.value.length - 1]; + const lastInputTrimmedCharacter = input.value.trim()[input.value.trim().length - 1]; + + // Remove the typed tokenName + if (word.indexOf(lastSearchToken) === 0 && searchToken !== '') { + // Remove spaces after the colon + if (lastInputCharacter === ' ' && lastInputTrimmedCharacter === ':') { + input.value = input.value.trim(); + } + + input.value = input.value.slice(0, -1 * lastSearchToken.length); + } else if (lastInputCharacter !== ' ') { + // Remove the existing tokenValue + const lastTokenString = `${lastToken.key}:${lastToken.symbol}${lastToken.value}`; + input.value = input.value.slice(0, -1 * lastTokenString.length); } - input.value += hasExistingValue && addSpace ? ` ${word}` : word; + input.value += word; } updateCurrentDropdownOffset() { @@ -106,7 +105,7 @@ if (!mappingKey.reference) { const dl = this.droplab; - const defaultArguments = [null, dl, element, this.filteredSearchInput]; + const defaultArguments = [null, dl, element, this.filteredSearchInput, key]; const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); // Passing glArguments to `new gl[glClass]()` -- cgit v1.2.3 From d5dee97becf193627f407815aa4013ea3c0a47a2 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Jan 2017 12:47:00 -0600 Subject: Enable filtering with multiple words --- app/assets/javascripts/filtered_search/dropdown_utils.js.es6 | 6 +++++- .../filtered_search/filtered_search_dropdown_manager.js.es6 | 2 +- .../javascripts/filtered_search/filtered_search_tokenizer.js.es6 | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index 88b172d6fc4..c27ef3042d1 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -25,8 +25,12 @@ const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query); if (lastToken !== searchToken) { - const value = lastToken.value.toLowerCase(); const title = updatedItem.title.toLowerCase(); + let value = lastToken.value.toLowerCase(); + + if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + value = value.slice(1); + } // Eg. filterSymbol = ~ for labels const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 8b385d6b642..1cd0483877a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -72,7 +72,7 @@ } input.value = input.value.slice(0, -1 * lastSearchToken.length); - } else if (lastInputCharacter !== ' ') { + } else if (lastInputCharacter !== ' ' || (lastToken && lastToken.value[lastToken.value.length - 1] === ' ')) { // Remove the existing tokenValue const lastTokenString = `${lastToken.key}:${lastToken.symbol}${lastToken.value}`; input.value = input.value.slice(0, -1 * lastTokenString.length); diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 index 14ca78e139b..60473dddead 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -1,7 +1,7 @@ (() => { class FilteredSearchTokenizer { static processTokens(input) { - const tokenRegex = /(\w+):([~%@]?)(?:"(.*?)"|'(.*?)'|(\S+))/g; + const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g; const tokens = []; let lastToken = null; const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { -- cgit v1.2.3 From 0f973c28b7f4852119181b549255308c76924c4e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Jan 2017 13:56:34 -0600 Subject: Fix specs --- .../javascripts/filtered_search/filtered_search_manager.js.es6 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index d2ea4de18aa..bd3c4240f13 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -142,7 +142,14 @@ if (condition) { tokenPath = condition.url; } else { - tokenPath = `${keyParam}=${encodeURIComponent(token.value)}`; + let tokenValue = token.value; + + if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') || + (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) { + tokenValue = tokenValue.slice(1, tokenValue.length - 1); + } + + tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`; } paths.push(tokenPath); -- cgit v1.2.3 From 3d0b0a62609d5f961d2777f497558d90c1dc039b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 6 Jan 2017 14:05:53 -0600 Subject: Add symbols --- app/assets/javascripts/filtered_search/dropdown_hint.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index f1e317d91cc..63c20f57520 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -37,19 +37,19 @@ const dropdownData = [{ icon: 'fa-pencil', hint: 'author:', - tag: '<author>', + tag: '<@author>', }, { icon: 'fa-user', hint: 'assignee:', - tag: '<assignee>', + tag: '<@assignee>', }, { icon: 'fa-clock-o', hint: 'milestone:', - tag: '<milestone>', + tag: '<%milestone>', }, { icon: 'fa-tag', hint: 'label:', - tag: '<label>', + tag: '<~label>', }]; this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); -- cgit v1.2.3 From 1bfdad5c4707c9dac243cebaf4666fee77f17891 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 9 Jan 2017 11:33:17 -0600 Subject: Code review changes --- app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/droplab/droplab_ajax.js | 2 +- app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index ca2da18dc26..99a34651639 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -84,7 +84,7 @@ break; case 'projects:merge_requests:index': case 'projects:issues:index': - if (document.querySelector('.filtered-search') && gl.FilteredSearchManager) { + if (gl.FilteredSearchManager) { new gl.FilteredSearchManager(); } Issuable.init(); diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index ebb518eeef4..926e53e696f 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -39,7 +39,7 @@ require('../window')(function(w){ var loadingTemplate = document.createElement('div'); loadingTemplate.innerHTML = config.loadingTemplate; - loadingTemplate.setAttribute('data-loading-template', true); + loadingTemplate.setAttribute('data-loading-template', ''); this.listTemplate = dynamicList.outerHTML; dynamicList.outerHTML = loadingTemplate.outerHTML; diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 index 60473dddead..cf53845a48b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -1,6 +1,8 @@ (() => { class FilteredSearchTokenizer { static processTokens(input) { + // Regex extracts `(token):(symbol)(value)` + // Values that start with a double quote must end in a double quote (same for single) const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g; const tokens = []; let lastToken = null; -- cgit v1.2.3 From 4ec8eb9abefb1a2abed9b5cbee4292325b3c22f6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 9 Jan 2017 12:15:10 -0600 Subject: Fix javascript error for when there are no issues --- .../filtered_search/filtered_search_manager.js.es6 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index bd3c4240f13..ffd0d7e9cba 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -3,17 +3,20 @@ (() => { class FilteredSearchManager { constructor() { - this.tokenizer = gl.FilteredSearchTokenizer; this.filteredSearchInput = document.querySelector('.filtered-search'); this.clearSearchButton = document.querySelector('.clear-search'); - this.dropdownManager = new gl.FilteredSearchDropdownManager(); - this.bindEvents(); - this.loadSearchParamsFromURL(); - this.dropdownManager.setDropdown(); + if (this.filteredSearchInput) { + this.tokenizer = gl.FilteredSearchTokenizer; + this.dropdownManager = new gl.FilteredSearchDropdownManager(); - this.cleanupWrapper = this.cleanup.bind(this); - document.addEventListener('page:fetch', this.cleanupWrapper); + this.bindEvents(); + this.loadSearchParamsFromURL(); + this.dropdownManager.setDropdown(); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('page:fetch', this.cleanupWrapper); + } } cleanup() { -- cgit v1.2.3 From c0287e69c6717fe9de5ff0b804f952410e453ef8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 9 Jan 2017 15:43:17 -0600 Subject: Fix indentation --- app/assets/javascripts/droplab/droplab_ajax.js | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index 926e53e696f..c8850f121d7 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -45,22 +45,23 @@ require('../window')(function(w){ dynamicList.outerHTML = loadingTemplate.outerHTML; } - this._loadUrlData(config.endpoint).then(function(d) { - if (config.loadingTemplate) { - var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); + this._loadUrlData(config.endpoint) + .then(function(d) { + if (config.loadingTemplate) { + var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); - if (dataLoadingTemplate) { - dataLoadingTemplate.outerHTML = self.listTemplate; + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } } - } - hook.list[config.method].call(hook.list, d); - }).catch(function(e) { - if(e.message) { - console.error(e.message, e.stack); // eslint-disable-line no-console - } else { - console.error(e); // eslint-disable-line no-console - } - }); + hook.list[config.method].call(hook.list, d); + }).catch(function(e) { + if(e.message) { + console.error(e.message, e.stack); // eslint-disable-line no-console + } else { + console.error(e); // eslint-disable-line no-console + } + }); }, destroy: function() { -- cgit v1.2.3 From e0e855b5f49bc8efc3ca69aa83ea28d6becb53cc Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 9 Jan 2017 16:23:51 -0600 Subject: Fix code review suggestions --- app/assets/javascripts/droplab/droplab_ajax.js | 10 +++++----- app/assets/javascripts/lib/utils/common_utils.js.es6 | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index c8850f121d7..f20610b3811 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -3,6 +3,10 @@ /* global droplab */ require('../window')(function(w){ + function droplabAjaxException(message) { + this.message = message; + } + w.droplabAjax = { _loadUrlData: function _loadUrlData(url) { return new Promise(function(resolve, reject) { @@ -56,11 +60,7 @@ require('../window')(function(w){ } hook.list[config.method].call(hook.list, d); }).catch(function(e) { - if(e.message) { - console.error(e.message, e.stack); // eslint-disable-line no-console - } else { - console.error(e); // eslint-disable-line no-console - } + throw new droplabAjaxException(e.message || e); }); }, diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 8aa78f407e5..3e2c75d3cc6 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -169,6 +169,8 @@ w.gl.utils.getParameterByName = (name) => { const url = window.location.href; name = name.replace(/[[\]]/g, '\\$&'); + // Finds the value associated to the name + // Example, state=open where state is the name and open is the value const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); const results = regex.exec(url); if (!results) return null; -- cgit v1.2.3 From 757bc8ecf43159243d9a0f45f27844f6572e60ac Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 9 Jan 2017 17:41:53 -0600 Subject: Remove duplicate method --- app/assets/javascripts/lib/utils/common_utils.js.es6 | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 3e2c75d3cc6..0c6a3cc3170 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -130,22 +130,6 @@ return window.location.search.slice(1).split('&'); }; - gl.utils.getParameterByName = function(name) { - var url = window.location.href; - var param = name.replace(/[[\]]/g, '\\$&'); - var regex = new RegExp('[?&]' + param + '(=([^&#]*)|&|#|$)'); - var results = regex.exec(url); - - if (!results) { - return null; - } - - if (!results[2]) { - return ''; - } - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }; - gl.utils.isMetaKey = function(e) { return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; }; @@ -169,8 +153,6 @@ w.gl.utils.getParameterByName = (name) => { const url = window.location.href; name = name.replace(/[[\]]/g, '\\$&'); - // Finds the value associated to the name - // Example, state=open where state is the name and open is the value const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); const results = regex.exec(url); if (!results) return null; -- cgit v1.2.3 From 044a195b1e8ca854e67f8e2782bc69c345bf0df6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 10 Jan 2017 20:51:57 -0500 Subject: Add comments to issuable finder --- app/finders/issuable_finder.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app') diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 5ffaf5ae0f8..1576fc80a6b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -174,6 +174,7 @@ class IssuableFinder end def no_assignee? + # Assignee_id takes precedence over assignee_username params[:assignee_id] == NONE || params[:assignee_username] == NONE end @@ -199,6 +200,7 @@ class IssuableFinder end def no_author? + # author_id takes precedence over author_username params[:author_id] == NONE || params[:author_username] == NONE end -- cgit v1.2.3 From ce9d3ee599f816ba20d57eae16b4139cabecdb1a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 11 Jan 2017 20:47:42 -0500 Subject: Backend review --- app/assets/javascripts/droplab/droplab_ajax_filter.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index ae316f881c8..af163f76851 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -22,7 +22,8 @@ require('../window')(function(w){ debounceTrigger: function debounceTrigger(e) { var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93]; var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1; - var focusEvent = false; + var focusEvent = e.type === 'focus'; + if (invalidKeyPressed || this.loading) { return; } @@ -31,10 +32,6 @@ require('../window')(function(w){ clearTimeout(this.timeout); } - if (e.type === 'focus') { - focusEvent = true; - } - this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200); }, @@ -66,7 +63,7 @@ require('../window')(function(w){ searchValue = ''; } - if (searchValue === config.searchKey) { + if (config.searchKey === searchValue) { return this.list.show(); } -- cgit v1.2.3