From efec7e086d12d72e4c4a5f43b46210b922dd94e4 Mon Sep 17 00:00:00 2001 From: Dennis Tang Date: Tue, 7 Aug 2018 09:59:01 +0000 Subject: Resolve "Frontend for clarifying the usefulness of the search bar" --- app/assets/javascripts/gl_dropdown.js | 588 +++++++++++++-------- app/assets/javascripts/search_autocomplete.js | 263 +++++---- app/assets/stylesheets/framework/gitlab_theme.scss | 32 -- app/assets/stylesheets/framework/variables.scss | 3 +- app/assets/stylesheets/pages/search.scss | 72 ++- app/helpers/search_helper.rb | 20 +- app/views/layouts/_search.html.haml | 8 +- ...clarifying-the-usefulness-of-the-search-bar.yml | 5 + doc/user/search/img/issues_mrs_shortcut.png | Bin 34115 -> 61888 bytes doc/user/search/img/project_search.png | Bin 41900 -> 89002 bytes locale/gitlab.pot | 27 + .../search/user_uses_header_search_field_spec.rb | 4 - .../fixtures/search_autocomplete.html.haml | 6 +- 13 files changed, 598 insertions(+), 430 deletions(-) create mode 100644 changelogs/unreleased/36409-frontend-for-clarifying-the-usefulness-of-the-search-bar.yml diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 8d231e6c405..cbc05b229cb 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, one-var-declaration-per-line, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */ +/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, one-var-declaration-per-line, max-len, vars-on-top, wrap-iife, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */ /* global fuzzaldrinPlus */ import $ from 'jquery'; @@ -19,32 +19,42 @@ GitLabDropdownInput = (function() { this.fieldName = this.options.fieldName || 'field-name'; $inputContainer = this.input.parent(); $clearButton = $inputContainer.find('.js-dropdown-input-clear'); - $clearButton.on('click', (function(_this) { - // Clear click - return function(e) { - e.preventDefault(); - e.stopPropagation(); - return _this.input.val('').trigger('input').focus(); - }; - })(this)); + $clearButton.on( + 'click', + (function(_this) { + // Clear click + return function(e) { + e.preventDefault(); + e.stopPropagation(); + return _this.input + .val('') + .trigger('input') + .focus(); + }; + })(this), + ); this.input - .on('keydown', function (e) { - var keyCode = e.which; - if (keyCode === 13 && !options.elIsInput) { - e.preventDefault(); - } - }) - .on('input', function(e) { - var val = e.currentTarget.value || _this.options.inputFieldName; - val = val.split(' ').join('-') // replaces space with dash - .replace(/[^a-zA-Z0-9 -]/g, '').toLowerCase() // replace non alphanumeric - .replace(/(-)\1+/g, '-'); // replace repeated dashes - _this.cb(_this.options.fieldName, val, {}, true); - _this.input.closest('.dropdown') - .find('.dropdown-toggle-text') - .text(val); - }); + .on('keydown', function(e) { + var keyCode = e.which; + if (keyCode === 13 && !options.elIsInput) { + e.preventDefault(); + } + }) + .on('input', function(e) { + var val = e.currentTarget.value || _this.options.inputFieldName; + val = val + .split(' ') + .join('-') // replaces space with dash + .replace(/[^a-zA-Z0-9 -]/g, '') + .toLowerCase() // replace non alphanumeric + .replace(/(-)\1+/g, '-'); // replace repeated dashes + _this.cb(_this.options.fieldName, val, {}, true); + _this.input + .closest('.dropdown') + .find('.dropdown-toggle-text') + .text(val); + }); } GitLabDropdownInput.prototype.onInput = function(cb) { @@ -61,7 +71,7 @@ GitLabDropdownFilter = (function() { ARROW_KEY_CODES = [38, 40]; - HAS_VALUE_CLASS = "has-value"; + HAS_VALUE_CLASS = 'has-value'; function GitLabDropdownFilter(input, options) { var $clearButton, $inputContainer, ref, timeout; @@ -70,44 +80,59 @@ GitLabDropdownFilter = (function() { this.filterInputBlur = (ref = this.options.filterInputBlur) != null ? ref : true; $inputContainer = this.input.parent(); $clearButton = $inputContainer.find('.js-dropdown-input-clear'); - $clearButton.on('click', (function(_this) { - // Clear click - return function(e) { - e.preventDefault(); - e.stopPropagation(); - return _this.input.val('').trigger('input').focus(); - }; - })(this)); + $clearButton.on( + 'click', + (function(_this) { + // Clear click + return function(e) { + e.preventDefault(); + e.stopPropagation(); + return _this.input + .val('') + .trigger('input') + .focus(); + }; + })(this), + ); // Key events - timeout = ""; + timeout = ''; this.input - .on('keydown', function (e) { + .on('keydown', function(e) { var keyCode = e.which; if (keyCode === 13 && !options.elIsInput) { e.preventDefault(); } }) - .on('input', function() { - if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { - $inputContainer.addClass(HAS_VALUE_CLASS); - } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { - $inputContainer.removeClass(HAS_VALUE_CLASS); - } - // Only filter asynchronously only if option remote is set - if (this.options.remote) { - clearTimeout(timeout); - return timeout = setTimeout(function() { - $inputContainer.parent().addClass('is-loading'); - - return this.options.query(this.input.val(), function(data) { - $inputContainer.parent().removeClass('is-loading'); - return this.options.callback(data); - }.bind(this)); - }.bind(this), 250); - } else { - return this.filter(this.input.val()); - } - }.bind(this)); + .on( + 'input', + function() { + if (this.input.val() !== '' && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { + $inputContainer.addClass(HAS_VALUE_CLASS); + } else if (this.input.val() === '' && $inputContainer.hasClass(HAS_VALUE_CLASS)) { + $inputContainer.removeClass(HAS_VALUE_CLASS); + } + // Only filter asynchronously only if option remote is set + if (this.options.remote) { + clearTimeout(timeout); + return (timeout = setTimeout( + function() { + $inputContainer.parent().addClass('is-loading'); + + return this.options.query( + this.input.val(), + function(data) { + $inputContainer.parent().removeClass('is-loading'); + return this.options.callback(data); + }.bind(this), + ); + }.bind(this), + 250, + )); + } else { + return this.filter(this.input.val()); + } + }.bind(this), + ); } GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) { @@ -120,7 +145,7 @@ GitLabDropdownFilter = (function() { this.options.onFilter(search_text); } data = this.options.data(); - if ((data != null) && !this.options.filterByText) { + if (data != null && !this.options.filterByText) { results = data; if (search_text !== '') { // When data is an array of objects therefore [object Array] e.g. @@ -130,7 +155,7 @@ GitLabDropdownFilter = (function() { // ] if (_.isArray(data)) { results = fuzzaldrinPlus.filter(data, search_text, { - key: this.options.keys + key: this.options.keys, }); } else { // If data is grouped therefore an [object Object]. e.g. @@ -149,7 +174,7 @@ GitLabDropdownFilter = (function() { for (key in data) { group = data[key]; tmp = fuzzaldrinPlus.filter(group, search_text, { - key: this.options.keys + key: this.options.keys, }); if (tmp.length) { results[key] = tmp.map(function(item) { @@ -180,7 +205,10 @@ GitLabDropdownFilter = (function() { elements.show().removeClass('option-hidden'); } - elements.parent().find('.dropdown-menu-empty-item').toggleClass('hidden', elements.is(':visible')); + elements + .parent() + .find('.dropdown-menu-empty-item') + .toggleClass('hidden', elements.is(':visible')); } }; @@ -194,23 +222,26 @@ GitLabDropdownRemote = (function() { } GitLabDropdownRemote.prototype.execute = function() { - if (typeof this.dataEndpoint === "string") { + if (typeof this.dataEndpoint === 'string') { return this.fetchData(); - } else if (typeof this.dataEndpoint === "function") { + } else if (typeof this.dataEndpoint === 'function') { if (this.options.beforeSend) { this.options.beforeSend(); } - return this.dataEndpoint("", (function(_this) { - // Fetch the data by calling the data funcfion - return function(data) { - if (_this.options.success) { - _this.options.success(data); - } - if (_this.options.beforeSend) { - return _this.options.beforeSend(); - } - }; - })(this)); + return this.dataEndpoint( + '', + (function(_this) { + // Fetch the data by calling the data funcfion + return function(data) { + if (_this.options.success) { + _this.options.success(data); + } + if (_this.options.beforeSend) { + return _this.options.beforeSend(); + } + }; + })(this), + ); } }; @@ -220,33 +251,41 @@ GitLabDropdownRemote = (function() { } // Fetch the data through ajax if the data is a string - return axios.get(this.dataEndpoint) - .then(({ data }) => { - if (this.options.success) { - return this.options.success(data); - } - }); + return axios.get(this.dataEndpoint).then(({ data }) => { + if (this.options.success) { + return this.options.success(data); + } + }); }; return GitLabDropdownRemote; })(); GitLabDropdown = (function() { - var ACTIVE_CLASS, FILTER_INPUT, NO_FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex; + var ACTIVE_CLASS, + FILTER_INPUT, + NO_FILTER_INPUT, + INDETERMINATE_CLASS, + LOADING_CLASS, + PAGE_TWO_CLASS, + NON_SELECTABLE_CLASSES, + SELECTABLE_CLASSES, + CURSOR_SELECT_SCROLL_PADDING, + currentIndex; - LOADING_CLASS = "is-loading"; + LOADING_CLASS = 'is-loading'; - PAGE_TWO_CLASS = "is-page-two"; + PAGE_TWO_CLASS = 'is-page-two'; - ACTIVE_CLASS = "is-active"; + ACTIVE_CLASS = 'is-active'; - INDETERMINATE_CLASS = "is-indeterminate"; + INDETERMINATE_CLASS = 'is-indeterminate'; currentIndex = -1; NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item'; - SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)"; + SELECTABLE_CLASSES = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ', .option-hidden)'; CURSOR_SELECT_SCROLL_PADDING = 5; @@ -263,15 +302,15 @@ GitLabDropdown = (function() { this.opened = this.opened.bind(this); this.shouldPropagate = this.shouldPropagate.bind(this); self = this; - selector = $(this.el).data("target"); + selector = $(this.el).data('target'); this.dropdown = selector != null ? $(selector) : $(this.el).parent(); // Set Defaults this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT); this.noFilterInput = this.options.noFilterInput || this.getElement(NO_FILTER_INPUT); this.highlight = !!this.options.highlight; - this.filterInputBlur = this.options.filterInputBlur != null - ? this.options.filterInputBlur - : true; + this.icon = !!this.options.icon; + this.filterInputBlur = + this.options.filterInputBlur != null ? this.options.filterInputBlur : true; // If no input is passed create a default one self = this; // If selector was passed @@ -296,11 +335,17 @@ GitLabDropdown = (function() { _this.fullData = data; _this.parseData(_this.fullData); _this.focusTextInput(); - if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') { + if ( + _this.options.filterable && + _this.filter && + _this.filter.input && + _this.filter.input.val() && + _this.filter.input.val().trim() !== '' + ) { return _this.filter.input.trigger('input'); } }; - // Remote data + // Remote data })(this), instance: this, }); @@ -325,7 +370,7 @@ GitLabDropdown = (function() { return function() { selector = '.dropdown-content li:not(' + NON_SELECTABLE_CLASSES + ')'; if (_this.dropdown.find('.dropdown-toggle-page').length) { - selector = ".dropdown-page-one " + selector; + selector = '.dropdown-page-one ' + selector; } return $(selector, this.instance.dropdown); }; @@ -341,80 +386,97 @@ GitLabDropdown = (function() { if (_this.filterInput.val() !== '') { selector = SELECTABLE_CLASSES; if (_this.dropdown.find('.dropdown-toggle-page').length) { - selector = ".dropdown-page-one " + selector; + selector = '.dropdown-page-one ' + selector; } if ($(_this.el).is('input')) { currentIndex = -1; } else { - $(selector, _this.dropdown).first().find('a').addClass('is-focused'); + $(selector, _this.dropdown) + .first() + .find('a') + .addClass('is-focused'); currentIndex = 0; } } }; - })(this) + })(this), }); } // Event listeners - this.dropdown.on("shown.bs.dropdown", this.opened); - this.dropdown.on("hidden.bs.dropdown", this.hidden); - $(this.el).on("update.label", this.updateLabel); - this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate); - this.dropdown.on('keyup', (function(_this) { - return function(e) { - // Escape key - if (e.which === 27) { - return $('.dropdown-menu-close', _this.dropdown).trigger('click'); - } - }; - })(this)); - this.dropdown.on('blur', 'a', (function(_this) { - return function(e) { - var $dropdownMenu, $relatedTarget; - if (e.relatedTarget != null) { - $relatedTarget = $(e.relatedTarget); - $dropdownMenu = $relatedTarget.closest('.dropdown-menu'); - if ($dropdownMenu.length === 0) { - return _this.dropdown.removeClass('show'); + this.dropdown.on('shown.bs.dropdown', this.opened); + this.dropdown.on('hidden.bs.dropdown', this.hidden); + $(this.el).on('update.label', this.updateLabel); + this.dropdown.on('click', '.dropdown-menu, .dropdown-menu-close', this.shouldPropagate); + this.dropdown.on( + 'keyup', + (function(_this) { + return function(e) { + // Escape key + if (e.which === 27) { + return $('.dropdown-menu-close', _this.dropdown).trigger('click'); } - } - }; - })(this)); - if (this.dropdown.find(".dropdown-toggle-page").length) { - this.dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on("click", (function(_this) { + }; + })(this), + ); + this.dropdown.on( + 'blur', + 'a', + (function(_this) { return function(e) { - e.preventDefault(); - e.stopPropagation(); - return _this.togglePage(); + var $dropdownMenu, $relatedTarget; + if (e.relatedTarget != null) { + $relatedTarget = $(e.relatedTarget); + $dropdownMenu = $relatedTarget.closest('.dropdown-menu'); + if ($dropdownMenu.length === 0) { + return _this.dropdown.removeClass('show'); + } + } }; - })(this)); + })(this), + ); + if (this.dropdown.find('.dropdown-toggle-page').length) { + this.dropdown.find('.dropdown-toggle-page, .dropdown-menu-back').on( + 'click', + (function(_this) { + return function(e) { + e.preventDefault(); + e.stopPropagation(); + return _this.togglePage(); + }; + })(this), + ); } if (this.options.selectable) { - selector = ".dropdown-content a"; - if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one .dropdown-content a"; + selector = '.dropdown-content a'; + if (this.dropdown.find('.dropdown-toggle-page').length) { + selector = '.dropdown-page-one .dropdown-content a'; } - this.dropdown.on("click", selector, function(e) { - var $el, selected, selectedObj, isMarking; - $el = $(e.currentTarget); - selected = self.rowClicked($el); - selectedObj = selected ? selected[0] : null; - isMarking = selected ? selected[1] : null; - if (this.options.clicked) { - this.options.clicked.call(this, { - selectedObj, - $el, - e, - isMarking, - }); - } + this.dropdown.on( + 'click', + selector, + function(e) { + var $el, selected, selectedObj, isMarking; + $el = $(e.currentTarget); + selected = self.rowClicked($el); + selectedObj = selected ? selected[0] : null; + isMarking = selected ? selected[1] : null; + if (this.options.clicked) { + this.options.clicked.call(this, { + selectedObj, + $el, + e, + isMarking, + }); + } - // Update label right after all modifications in dropdown has been done - if (this.options.toggleLabel) { - this.updateLabel(selectedObj, $el, this); - } + // Update label right after all modifications in dropdown has been done + if (this.options.toggleLabel) { + this.updateLabel(selectedObj, $el, this); + } - $el.trigger('blur'); - }.bind(this)); + $el.trigger('blur'); + }.bind(this), + ); } } @@ -452,10 +514,15 @@ GitLabDropdown = (function() { html = []; for (name in data) { groupData = data[name]; - html.push(this.renderItem({ - header: name - // Add header for each group - }, name)); + html.push( + this.renderItem( + { + header: name, + // Add header for each group + }, + name, + ), + ); this.renderData(groupData, name).map(function(item) { return html.push(item); }); @@ -474,20 +541,25 @@ GitLabDropdown = (function() { if (group == null) { group = false; } - return data.map((function(_this) { - return function(obj, index) { - return _this.renderItem(obj, group, index); - }; - })(this)); + return data.map( + (function(_this) { + return function(obj, index) { + return _this.renderItem(obj, group, index); + }; + })(this), + ); }; GitLabDropdown.prototype.shouldPropagate = function(e) { var $target; if (this.options.multiSelect || this.options.shouldPropagate === false) { $target = $(e.target); - if ($target && !$target.hasClass('dropdown-menu-close') && - !$target.hasClass('dropdown-menu-close-icon') && - !$target.data('isLink')) { + if ( + $target && + !$target.hasClass('dropdown-menu-close') && + !$target.hasClass('dropdown-menu-close-icon') && + !$target.data('isLink') + ) { e.stopPropagation(); return false; } else { @@ -497,9 +569,11 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.filteredFullData = function() { - return this.fullData.filter(r => typeof r === 'object' - && !Object.prototype.hasOwnProperty.call(r, 'beforeDivider') - && !Object.prototype.hasOwnProperty.call(r, 'header') + return this.fullData.filter( + r => + typeof r === 'object' && + !Object.prototype.hasOwnProperty.call(r, 'beforeDivider') && + !Object.prototype.hasOwnProperty.call(r, 'header'), ); }; @@ -522,11 +596,16 @@ GitLabDropdown = (function() { // matches the correct layout const inputValue = this.filterInput.val(); if (this.fullData && hasMultiSelect && this.options.processData && inputValue.length === 0) { - this.options.processData.call(this.options, inputValue, this.filteredFullData(), this.parseData.bind(this)); + this.options.processData.call( + this.options, + inputValue, + this.filteredFullData(), + this.parseData.bind(this), + ); } contentHtml = $('.dropdown-content', this.dropdown).html(); - if (this.remote && contentHtml === "") { + if (this.remote && contentHtml === '') { this.remote.execute(); } else { this.focusTextInput(); @@ -555,11 +634,11 @@ GitLabDropdown = (function() { var $input; this.resetRows(); this.removeArrayKeyEvent(); - $input = this.dropdown.find(".dropdown-input-field"); + $input = this.dropdown.find('.dropdown-input-field'); if (this.options.filterable) { $input.blur(); } - if (this.dropdown.find(".dropdown-toggle-page").length) { + if (this.dropdown.find('.dropdown-toggle-page').length) { $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); } if (this.options.hidden) { @@ -601,7 +680,7 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.clearMenu = function() { var selector; selector = '.dropdown-content'; - if (this.dropdown.find(".dropdown-toggle-page").length) { + if (this.dropdown.find('.dropdown-toggle-page').length) { if (this.options.containerSelector) { selector = this.options.containerSelector; } else { @@ -619,7 +698,7 @@ GitLabDropdown = (function() { value = this.options.id ? this.options.id(data) : data.id; if (value) { - value = value.toString().replace(/'/g, '\\\''); + value = value.toString().replace(/'/g, "\\'"); } } @@ -676,21 +755,27 @@ GitLabDropdown = (function() { text = data.text != null ? data.text : ''; } if (this.highlight) { - text = this.highlightTextMatches(text, this.filterInput.val()); + text = data.template + ? this.highlightTemplate(text, data.template) + : this.highlightTextMatches(text, this.filterInput.val()); } // Create the list item & the link var link = document.createElement('a'); link.href = url; - if (this.highlight) { + if (this.icon) { + text = `${text}`; + link.classList.add('d-flex', 'align-items-center'); + link.innerHTML = data.icon ? data.icon + text : text; + } else if (this.highlight) { link.innerHTML = text; } else { link.textContent = text; } if (selected) { - link.className = 'is-active'; + link.classList.add('is-active'); } if (group) { @@ -703,17 +788,24 @@ GitLabDropdown = (function() { return html; }; + GitLabDropdown.prototype.highlightTemplate = function(text, template) { + return `"${_.escape(text)}" ${template}`; + }; + GitLabDropdown.prototype.highlightTextMatches = function(text, term) { const occurrences = fuzzaldrinPlus.match(text, term); const { indexOf } = []; - return text.split('').map(function(character, i) { - if (indexOf.call(occurrences, i) !== -1) { - return "" + character + ""; - } else { - return character; - } - }).join(''); + return text + .split('') + .map(function(character, i) { + if (indexOf.call(occurrences, i) !== -1) { + return '' + character + ''; + } else { + return character; + } + }) + .join(''); }; GitLabDropdown.prototype.noResults = function() { @@ -748,13 +840,15 @@ GitLabDropdown = (function() { } field = []; - value = this.options.id - ? this.options.id(selectedObject, el) - : selectedObject.id; + value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); } else if (value != null) { - field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); + field = this.dropdown + .parent() + .find( + "input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, "\\'") + "']", + ); } if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) { @@ -780,9 +874,12 @@ GitLabDropdown = (function() { } else { isMarking = true; if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) { - this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); + this.dropdown.find('.' + ACTIVE_CLASS).removeClass(ACTIVE_CLASS); if (!isInput) { - this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); + this.dropdown + .parent() + .find("input[name='" + fieldName + "']") + .remove(); } } if (field && field.length && value == null) { @@ -823,13 +920,16 @@ GitLabDropdown = (function() { $('input[name="' + fieldName + '"]').remove(); } - $input = $('').attr('type', 'hidden').attr('name', fieldName).val(value); + $input = $('') + .attr('type', 'hidden') + .attr('name', fieldName) + .val(value); if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } if (this.options.multiSelect) { - Object.keys(selectedObject).forEach((attribute) => { + Object.keys(selectedObject).forEach(attribute => { $input.attr(`data-${attribute}`, selectedObject[attribute]); }); } @@ -844,13 +944,13 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.selectRowAtIndex = function(index) { var $el, selector; // If we pass an option index - if (typeof index !== "undefined") { - selector = SELECTABLE_CLASSES + ":eq(" + index + ") a"; + if (typeof index !== 'undefined') { + selector = SELECTABLE_CLASSES + ':eq(' + index + ') a'; } else { - selector = ".dropdown-content .is-focused"; + selector = '.dropdown-content .is-focused'; } - if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one " + selector; + if (this.dropdown.find('.dropdown-toggle-page').length) { + selector = '.dropdown-page-one ' + selector; } // simulate a click on the first link $el = $(selector, this.dropdown); @@ -867,44 +967,47 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.addArrowKeyEvent = function() { var $input, ARROW_KEY_CODES, selector; ARROW_KEY_CODES = [38, 40]; - $input = this.dropdown.find(".dropdown-input-field"); + $input = this.dropdown.find('.dropdown-input-field'); selector = SELECTABLE_CLASSES; - if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one " + selector; - } - return $('body').on('keydown', (function(_this) { - return function(e) { - var $listItems, PREV_INDEX, currentKeyCode; - currentKeyCode = e.which; - if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) { - e.preventDefault(); - e.stopImmediatePropagation(); - PREV_INDEX = currentIndex; - $listItems = $(selector, _this.dropdown); - // if @options.filterable - // $input.blur() - if (currentKeyCode === 40) { - // Move down - if (currentIndex < ($listItems.length - 1)) { - currentIndex += 1; + if (this.dropdown.find('.dropdown-toggle-page').length) { + selector = '.dropdown-page-one ' + selector; + } + return $('body').on( + 'keydown', + (function(_this) { + return function(e) { + var $listItems, PREV_INDEX, currentKeyCode; + currentKeyCode = e.which; + if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) { + e.preventDefault(); + e.stopImmediatePropagation(); + PREV_INDEX = currentIndex; + $listItems = $(selector, _this.dropdown); + // if @options.filterable + // $input.blur() + if (currentKeyCode === 40) { + // Move down + if (currentIndex < $listItems.length - 1) { + currentIndex += 1; + } + } else if (currentKeyCode === 38) { + // Move up + if (currentIndex > 0) { + currentIndex -= 1; + } } - } else if (currentKeyCode === 38) { - // Move up - if (currentIndex > 0) { - currentIndex -= 1; + if (currentIndex !== PREV_INDEX) { + _this.highlightRowAtIndex($listItems, currentIndex); } + return false; } - if (currentIndex !== PREV_INDEX) { - _this.highlightRowAtIndex($listItems, currentIndex); + if (currentKeyCode === 13 && currentIndex !== -1) { + e.preventDefault(); + _this.selectRowAtIndex(); } - return false; - } - if (currentKeyCode === 13 && currentIndex !== -1) { - e.preventDefault(); - _this.selectRowAtIndex(); - } - }; - })(this)); + }; + })(this), + ); }; GitLabDropdown.prototype.removeArrayKeyEvent = function() { @@ -917,12 +1020,25 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { - var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop; + var $dropdownContent, + $listItem, + dropdownContentBottom, + dropdownContentHeight, + dropdownContentTop, + dropdownScrollTop, + listItemBottom, + listItemHeight, + listItemTop; + + if (!$listItems) { + $listItems = $(SELECTABLE_CLASSES, this.dropdown); + } + // Remove the class for the previously focused row $('.is-focused', this.dropdown).removeClass('is-focused'); // Update the class for the row at the specific index $listItem = $listItems.eq(index); - $listItem.find('a:first-child').addClass("is-focused"); + $listItem.find('a:first-child').addClass('is-focused'); // Dropdown content scroll area $dropdownContent = $listItem.closest('.dropdown-content'); dropdownScrollTop = $dropdownContent.scrollTop(); @@ -936,15 +1052,19 @@ GitLabDropdown = (function() { if (!index) { // Scroll the dropdown content to the top $dropdownContent.scrollTop(0); - } else if (index === ($listItems.length - 1)) { + } else if (index === $listItems.length - 1) { // Scroll the dropdown content to the bottom $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); - } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { + } else if (listItemBottom > dropdownContentBottom + dropdownScrollTop) { // Scroll the dropdown content down - $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); - } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { + $dropdownContent.scrollTop( + listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING, + ); + } else if (listItemTop < dropdownContentTop + dropdownScrollTop) { // Scroll the dropdown content up - return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); + return $dropdownContent.scrollTop( + listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING, + ); } }; @@ -965,7 +1085,9 @@ GitLabDropdown = (function() { toggleText = this.options.updateLabel; } - return $(this.el).find(".dropdown-toggle-text").text(toggleText); + return $(this.el) + .find('.dropdown-toggle-text') + .text(toggleText); }; GitLabDropdown.prototype.clearField = function(field, isInput) { diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 72a2c7ca101..aec09b8bc0a 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -1,9 +1,18 @@ -/* eslint-disable no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, consistent-return, object-shorthand, prefer-template, quotes, class-methods-use-this, no-lonely-if, no-else-return, vars-on-top, max-len */ +/* eslint-disable no-return-assign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top, max-len */ import $ from 'jquery'; +import { escape, throttle } from 'underscore'; +import { s__, sprintf } from '~/locale'; +import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper'; import axios from './lib/utils/axios_utils'; import DropdownUtils from './filtered_search/dropdown_utils'; -import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils'; +import { + isInGroupsPage, + isInProjectPage, + getGroupSlug, + getProjectSlug, + spriteIcon, +} from './lib/utils/common_utils'; /** * Search input in top navigation bar. @@ -52,6 +61,7 @@ function setSearchOptions() { if ($dashboardOptionsDataEl.length) { gl.dashboardOptions = { + name: s__('SearchAutocomplete|All GitLab'), issuesPath: $dashboardOptionsDataEl.data('issuesPath'), mrPath: $dashboardOptionsDataEl.data('mrPath'), }; @@ -69,8 +79,8 @@ export default class SearchAutocomplete { this.projectRef = projectRef || (this.optsEl.data('autocompleteProjectRef') || ''); this.dropdown = this.wrap.find('.dropdown'); this.dropdownToggle = this.wrap.find('.js-dropdown-search-toggle'); + this.dropdownMenu = this.dropdown.find('.dropdown-menu'); this.dropdownContent = this.dropdown.find('.dropdown-content'); - this.locationBadgeEl = this.getElement('.location-badge'); this.scopeInputEl = this.getElement('#scope'); this.searchInput = this.getElement('.search-input'); this.projectInputEl = this.getElement('#search_project_id'); @@ -78,6 +88,7 @@ export default class SearchAutocomplete { this.searchCodeInputEl = this.getElement('#search_code'); this.repositoryInputEl = this.getElement('#repository_ref'); this.clearInput = this.getElement('.js-clear-input'); + this.scrollFadeInitialized = false; this.saveOriginalState(); // Only when user is logged in @@ -98,17 +109,18 @@ export default class SearchAutocomplete { this.onSearchInputFocus = this.onSearchInputFocus.bind(this); this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); + this.setScrollFade = this.setScrollFade.bind(this); } getElement(selector) { return this.wrap.find(selector); } saveOriginalState() { - return this.originalState = this.serializeState(); + return (this.originalState = this.serializeState()); } saveTextLength() { - return this.lastTextLength = this.searchInput.val().length; + return (this.lastTextLength = this.searchInput.val().length); } createAutocomplete() { @@ -117,6 +129,7 @@ export default class SearchAutocomplete { filterable: true, filterRemote: true, highlight: true, + icon: true, enterCallback: false, filterInput: 'input#search', search: { @@ -154,60 +167,87 @@ export default class SearchAutocomplete { this.loadingSuggestions = true; - return axios.get(this.autocompletePath, { - params: { - project_id: this.projectId, - project_ref: this.projectRef, - term: term, - }, - }).then((response) => { - // Hide dropdown menu if no suggestions returns - if (!response.data.length) { - this.disableAutocomplete(); - return; - } + return axios + .get(this.autocompletePath, { + params: { + project_id: this.projectId, + project_ref: this.projectRef, + term: term, + }, + }) + .then(response => { + // Hide dropdown menu if no suggestions returns + if (!response.data.length) { + this.disableAutocomplete(); + return; + } - const data = []; - // List results - let firstCategory = true; - let lastCategory; - for (let i = 0, len = response.data.length; i < len; i += 1) { - const suggestion = response.data[i]; - // Add group header before list each group - if (lastCategory !== suggestion.category) { - if (!firstCategory) { - data.push('separator'); - } - if (firstCategory) { - firstCategory = false; + const data = []; + // List results + let firstCategory = true; + let lastCategory; + for (let i = 0, len = response.data.length; i < len; i += 1) { + const suggestion = response.data[i]; + // Add group header before list each group + if (lastCategory !== suggestion.category) { + if (!firstCategory) { + data.push('separator'); + } + if (firstCategory) { + firstCategory = false; + } + data.push({ + header: suggestion.category, + }); + lastCategory = suggestion.category; } data.push({ - header: suggestion.category, + id: `${suggestion.category.toLowerCase()}-${suggestion.id}`, + icon: this.getAvatar(suggestion), + category: suggestion.category, + text: suggestion.label, + url: suggestion.url, }); - lastCategory = suggestion.category; } - data.push({ - id: `${suggestion.category.toLowerCase()}-${suggestion.id}`, - category: suggestion.category, - text: suggestion.label, - url: suggestion.url, - }); - } - // Add option to proceed with the search - if (data.length) { - data.push('separator'); - data.push({ - text: `Result name contains "${term}"`, - url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, - }); - } + // Add option to proceed with the search + if (data.length) { + const icon = spriteIcon('search', 's16 inline-search-icon'); + let template; - callback(data); + if (this.projectInputEl.val()) { + template = s__('SearchAutocomplete|in this project'); + } + if (this.groupInputEl.val()) { + template = s__('SearchAutocomplete|in this group'); + } - this.loadingSuggestions = false; - }).catch(() => { - this.loadingSuggestions = false; - }); + data.unshift('separator'); + data.unshift({ + icon, + text: term, + template: s__('SearchAutocomplete|in all GitLab'), + url: `/search?search=${term}`, + }); + + if (template) { + data.unshift({ + icon, + text: term, + template, + url: `/search?search=${term}&project_id=${this.projectInputEl.val()}&group_id=${this.groupInputEl.val()}`, + }); + } + } + + callback(data); + + this.loadingSuggestions = false; + this.highlightFirstRow(); + this.setScrollFade(); + }) + .catch(() => { + this.loadingSuggestions = false; + }); } getCategoryContents() { @@ -236,21 +276,21 @@ export default class SearchAutocomplete { const issueItems = [ { - text: 'Issues assigned to me', + text: s__('SearchAutocomplete|Issues assigned to me'), url: `${issuesPath}/?assignee_id=${userId}`, }, { - text: "Issues I've created", + text: s__("SearchAutocomplete|Issues I've created"), url: `${issuesPath}/?author_id=${userId}`, }, ]; const mergeRequestItems = [ { - text: 'Merge requests assigned to me', + text: s__('SearchAutocomplete|Merge requests assigned to me'), url: `${mrPath}/?assignee_id=${userId}`, }, { - text: "Merge requests I've created", + text: s__("SearchAutocomplete|Merge requests I've created"), url: `${mrPath}/?author_id=${userId}`, }, ]; @@ -259,7 +299,7 @@ export default class SearchAutocomplete { if (issuesDisabled) { items = baseItems.concat(mergeRequestItems); } else { - items = baseItems.concat(...issueItems, 'separator', ...mergeRequestItems); + items = baseItems.concat(...issueItems, ...mergeRequestItems); } return items; } @@ -272,8 +312,6 @@ export default class SearchAutocomplete { search_code: this.searchCodeInputEl.val(), repository_ref: this.repositoryInputEl.val(), scope: this.scopeInputEl.val(), - // Location badge - _location: this.locationBadgeEl.text(), }; } @@ -283,10 +321,12 @@ export default class SearchAutocomplete { this.searchInput.on('focus', this.onSearchInputFocus); this.searchInput.on('blur', this.onSearchInputBlur); this.clearInput.on('click', this.onClearInputClick); - this.locationBadgeEl.on('click', () => this.searchInput.focus()); + this.dropdownContent.on('scroll', throttle(this.setScrollFade, 250)); } enableAutocomplete() { + this.setScrollFade(); + // No need to enable anything if user is not logged in if (!gon.current_user_id) { return; @@ -308,10 +348,6 @@ export default class SearchAutocomplete { onSearchInputKeyUp(e) { switch (e.keyCode) { case KEYCODE.BACKSPACE: - // when trying to remove the location badge - if (this.lastTextLength === 0 && this.badgePresent()) { - this.removeLocationBadge(); - } // When removing the last character and no badge is present if (this.lastTextLength === 1) { this.disableAutocomplete(); @@ -372,37 +408,13 @@ export default class SearchAutocomplete { } } - addLocationBadge(item) { - var badgeText, category, value; - category = item.category != null ? item.category + ": " : ''; - value = item.value != null ? item.value : ''; - badgeText = "" + category + value; - this.locationBadgeEl.text(badgeText).show(); - return this.wrap.addClass('has-location-badge'); - } - - hasLocationBadge() { - return this.wrap.is('.has-location-badge'); - } - restoreOriginalState() { var i, input, inputs, len; inputs = Object.keys(this.originalState); for (i = 0, len = inputs.length; i < len; i += 1) { input = inputs[i]; - this.getElement("#" + input).val(this.originalState[input]); + this.getElement('#' + input).val(this.originalState[input]); } - if (this.originalState._location === '') { - return this.locationBadgeEl.hide(); - } else { - return this.addLocationBadge({ - value: this.originalState._location, - }); - } - } - - badgePresent() { - return this.locationBadgeEl.length; } resetSearchState() { @@ -411,22 +423,11 @@ export default class SearchAutocomplete { results = []; for (i = 0, len = inputs.length; i < len; i += 1) { input = inputs[i]; - // _location isnt a input - if (input === '_location') { - break; - } - results.push(this.getElement("#" + input).val('')); + results.push(this.getElement('#' + input).val('')); } return results; } - removeLocationBadge() { - this.locationBadgeEl.hide(); - this.resetSearchState(); - this.wrap.removeClass('has-location-badge'); - return this.disableAutocomplete(); - } - disableAutocomplete() { if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('show')) { this.searchInput.addClass('disabled'); @@ -444,23 +445,57 @@ export default class SearchAutocomplete { onClick(item, $el, e) { if (window.location.pathname.indexOf(item.url) !== -1) { if (!e.metaKey) e.preventDefault(); - if (!this.badgePresent) { - if (item.category === 'Projects') { - this.projectInputEl.val(item.id); - this.addLocationBadge({ - value: 'This project', - }); - } - if (item.category === 'Groups') { - this.groupInputEl.val(item.id); - this.addLocationBadge({ - value: 'This group', - }); - } + if (item.category === 'Projects') { + this.projectInputEl.val(item.id); + } + if (item.category === 'Groups') { + this.groupInputEl.val(item.id); } $el.removeClass('is-active'); this.disableAutocomplete(); return this.searchInput.val('').focus(); } } + + highlightFirstRow() { + this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0); + } + + getAvatar(item) { + if (!Object.hasOwnProperty.call(item, 'avatar_url')) { + return false; + } + + const { label, id } = item; + const avatarUrl = item.avatar_url; + const avatar = avatarUrl + ? `` + : `
${getIdenticonTitle( + escape(label), + )}
`; + + return avatar; + } + + isScrolledUp() { + const el = this.dropdownContent[0]; + const currentPosition = this.contentClientHeight + el.scrollTop; + + return currentPosition < this.maxPosition; + } + + initScrollFade() { + const el = this.dropdownContent[0]; + this.scrollFadeInitialized = true; + + this.contentClientHeight = el.clientHeight; + this.maxPosition = el.scrollHeight; + this.dropdownMenu.addClass('dropdown-content-faded-mask'); + } + + setScrollFade() { + this.initScrollFade(); + + this.dropdownMenu.toggleClass('fade-out', !this.isScrolledUp()); + } } diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index dff6bce370f..50ebc6d0dd1 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -3,7 +3,6 @@ */ @mixin gitlab-theme( - $location-badge-color, $search-and-nav-links, $active-tab-border, $border-and-box-shadow, @@ -119,12 +118,6 @@ } } - .location-badge { - color: $location-badge-color; - background-color: rgba($search-and-nav-links, 0.1); - border-right: 1px solid $sidebar-text; - } - .search-input::placeholder { color: rgba($search-and-nav-links, 0.8); } @@ -141,10 +134,6 @@ background-color: $white-light; } - .location-badge { - color: $gl-text-color; - } - .search-input-wrap { .search-icon { fill: rgba($search-and-nav-links, 0.8); @@ -200,7 +189,6 @@ body { &.ui-indigo { @include gitlab-theme( - $indigo-100, $indigo-200, $indigo-500, $indigo-700, @@ -212,7 +200,6 @@ body { &.ui-light-indigo { @include gitlab-theme( - $indigo-100, $indigo-200, $indigo-500, $indigo-500, @@ -224,7 +211,6 @@ body { &.ui-blue { @include gitlab-theme( - $theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, @@ -236,7 +222,6 @@ body { &.ui-light-blue { @include gitlab-theme( - $theme-light-blue-100, $theme-light-blue-200, $theme-light-blue-500, $theme-light-blue-500, @@ -248,7 +233,6 @@ body { &.ui-green { @include gitlab-theme( - $theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, @@ -260,7 +244,6 @@ body { &.ui-light-green { @include gitlab-theme( - $theme-green-100, $theme-green-200, $theme-green-500, $theme-green-500, @@ -272,7 +255,6 @@ body { &.ui-red { @include gitlab-theme( - $theme-red-100, $theme-red-200, $theme-red-500, $theme-red-700, @@ -284,7 +266,6 @@ body { &.ui-light-red { @include gitlab-theme( - $theme-light-red-100, $theme-light-red-200, $theme-light-red-500, $theme-light-red-500, @@ -296,7 +277,6 @@ body { &.ui-dark { @include gitlab-theme( - $theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, @@ -308,7 +288,6 @@ body { &.ui-light { @include gitlab-theme( - $theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, @@ -357,10 +336,6 @@ body { &:hover { background-color: $white-light; box-shadow: inset 0 0 0 1px $blue-200; - - .location-badge { - box-shadow: inset 0 0 0 1px $blue-200; - } } } @@ -373,13 +348,6 @@ body { color: $gl-text-color; } } - - .location-badge { - color: $theme-gray-700; - box-shadow: inset 0 0 0 1px $border-color; - background-color: $nav-badge-bg; - border-right: 0; - } } .nav-sidebar li.active { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 56940a7564a..4db9efff6ee 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -467,7 +467,8 @@ $award-emoji-positive-add-lines: #bb9c13; */ $search-input-border-color: rgba($blue-400, 0.8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; -$search-input-width: 220px; +$search-input-width: 240px; +$search-input-active-width: 320px; $location-badge-active-bg: $blue-500; $location-icon-color: #e7e9ed; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 2d66f336076..60b280fd12e 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -1,3 +1,6 @@ +$search-dropdown-max-height: 400px; +$search-avatar-size: 16px; + .search-results { .search-result-row { border-bottom: 1px solid $border-color; @@ -24,8 +27,9 @@ box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); } -input[type="checkbox"]:hover { - box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%), 0 0 0 1px lighten($search-input-focus-shadow-color, 20%); +input[type='checkbox']:hover { + box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%), + 0 0 0 1px lighten($search-input-focus-shadow-color, 20%); } .search { @@ -40,24 +44,15 @@ input[type="checkbox"]:hover { height: 32px; border: 0; border-radius: $border-radius-default; - transition: border-color ease-in-out $default-transition-duration, background-color ease-in-out $default-transition-duration; + transition: border-color ease-in-out $default-transition-duration, + background-color ease-in-out $default-transition-duration, + width ease-in-out $default-transition-duration; &:hover { box-shadow: none; } } - .location-badge { - white-space: nowrap; - height: 32px; - font-size: 12px; - margin: -4px 4px -4px -4px; - line-height: 25px; - padding: 4px 8px; - border-radius: $border-radius-default 0 0 $border-radius-default; - transition: border-color ease-in-out $default-transition-duration; - } - .search-input { border: 0; font-size: 14px; @@ -104,17 +99,28 @@ input[type="checkbox"]:hover { } .dropdown-header { - text-transform: uppercase; - font-size: 11px; + // Necessary because glDropdown doesn't support a second style of headers + font-weight: $gl-font-weight-bold; + // .dropdown-menu li has 1px side padding + padding: $gl-padding-8 17px; + color: $gl-text-color; + font-size: $gl-font-size; + line-height: 16px; } // Custom dropdown positioning .dropdown-menu { left: -5px; + max-height: $search-dropdown-max-height; + overflow: auto; + + @include media-breakpoint-up(xl) { + width: $search-input-active-width; + } } .dropdown-content { - max-height: none; + max-height: $search-dropdown-max-height - 18px; } } @@ -124,6 +130,10 @@ input[type="checkbox"]:hover { border-color: $dropdown-input-focus-border; box-shadow: none; + @include media-breakpoint-up(xl) { + width: $search-input-active-width; + } + .search-input-wrap { .search-icon, .clear-icon { @@ -141,12 +151,6 @@ input[type="checkbox"]:hover { color: $gl-text-color-tertiary; } } - - .location-badge { - transition: all $default-transition-duration; - background-color: $nav-badge-bg; - border-color: $border-color; - } } &.has-value { @@ -160,10 +164,24 @@ input[type="checkbox"]:hover { } } - &.has-location-badge { - .search-input-wrap { - width: 68%; - } + .inline-search-icon { + position: relative; + margin-right: 4px; + color: $gl-text-color-secondary; + } + + .identicon, + .search-item-avatar { + flex-basis: $search-avatar-size; + flex-shrink: 0; + margin-right: 4px; + } + + .search-item-avatar { + width: $search-avatar-size; + height: $search-avatar-size; + border-radius: 50%; + border: 1px solid $avatar-border; } } diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index cadb88ba632..98074a4c0c5 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -82,16 +82,16 @@ module SearchHelper ref = @ref || @project.repository.root_ref [ - { category: "Current Project", label: "Files", url: project_tree_path(@project, ref) }, - { category: "Current Project", label: "Commits", url: project_commits_path(@project, ref) }, - { category: "Current Project", label: "Network", url: project_network_path(@project, ref) }, - { category: "Current Project", label: "Graph", url: project_graph_path(@project, ref) }, - { category: "Current Project", label: "Issues", url: project_issues_path(@project) }, - { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) }, - { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) }, - { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) }, - { category: "Current Project", label: "Members", url: project_project_members_path(@project) }, - { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) } + { category: "In this project", label: "Files", url: project_tree_path(@project, ref) }, + { category: "In this project", label: "Commits", url: project_commits_path(@project, ref) }, + { category: "In this project", label: "Network", url: project_network_path(@project, ref) }, + { category: "In this project", label: "Graph", url: project_graph_path(@project, ref) }, + { category: "In this project", label: "Issues", url: project_issues_path(@project) }, + { category: "In this project", label: "Merge Requests", url: project_merge_requests_path(@project) }, + { category: "In this project", label: "Milestones", url: project_milestones_path(@project) }, + { category: "In this project", label: "Snippets", url: project_snippets_path(@project) }, + { category: "In this project", label: "Members", url: project_project_members_path(@project) }, + { category: "In this project", label: "Wiki", url: project_wikis_path(@project) } ] else [] diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 556ad8cf306..9a7a67cfa83 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -6,21 +6,19 @@ - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) } - if @project && @project.persisted? - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? } -.search.search-form{ class: "#{'has-location-badge' if label.present?}" } +.search.search-form = form_tag search_path, method: :get, class: 'form-inline' do |f| .search-input-container - - if label.present? - .location-badge= label .search-input-wrap .dropdown{ data: { url: search_autocomplete_path } } - = search_field_tag 'search', nil, placeholder: _('Search'), + = search_field_tag 'search', nil, placeholder: _('Search or jump to…'), class: 'search-input dropdown-menu-toggle no-outline js-search-dashboard-options', spellcheck: false, tabindex: '1', autocomplete: 'off', data: { issues_path: issues_dashboard_path, mr_path: merge_requests_dashboard_path }, - aria: { label: _('Search') } + aria: { label: _('Search or jump to…') } %button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } } .dropdown-menu.dropdown-select = dropdown_content do diff --git a/changelogs/unreleased/36409-frontend-for-clarifying-the-usefulness-of-the-search-bar.yml b/changelogs/unreleased/36409-frontend-for-clarifying-the-usefulness-of-the-search-bar.yml new file mode 100644 index 00000000000..efa13c9ab3c --- /dev/null +++ b/changelogs/unreleased/36409-frontend-for-clarifying-the-usefulness-of-the-search-bar.yml @@ -0,0 +1,5 @@ +--- +title: UX improvements to top nav search bar +merge_request: 20537 +author: +type: changed diff --git a/doc/user/search/img/issues_mrs_shortcut.png b/doc/user/search/img/issues_mrs_shortcut.png index 6380b337b54..cf43df98aa0 100644 Binary files a/doc/user/search/img/issues_mrs_shortcut.png and b/doc/user/search/img/issues_mrs_shortcut.png differ diff --git a/doc/user/search/img/project_search.png b/doc/user/search/img/project_search.png index 3150b40de29..0b76d7d6038 100644 Binary files a/doc/user/search/img/project_search.png and b/doc/user/search/img/project_search.png differ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c159cddcc60..53275aac6b3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4606,12 +4606,39 @@ msgstr "" msgid "Search milestones" msgstr "" +msgid "Search or jump to…" +msgstr "" + msgid "Search project" msgstr "" msgid "Search users" msgstr "" +msgid "SearchAutocomplete|All GitLab" +msgstr "" + +msgid "SearchAutocomplete|Issues I've created" +msgstr "" + +msgid "SearchAutocomplete|Issues assigned to me" +msgstr "" + +msgid "SearchAutocomplete|Merge requests I've created" +msgstr "" + +msgid "SearchAutocomplete|Merge requests assigned to me" +msgstr "" + +msgid "SearchAutocomplete|in all GitLab" +msgstr "" + +msgid "SearchAutocomplete|in this group" +msgstr "" + +msgid "SearchAutocomplete|in this project" +msgstr "" + msgid "Seconds before reseting failure information" msgstr "" diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index a9128104b87..af38f77c0c6 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -62,10 +62,6 @@ describe 'User uses header search field' do end end - it 'contains location badge' do - expect(page).to have_selector('.has-location-badge') - end - context 'when clicking the search field', :js do before do page.find('#search').click diff --git a/spec/javascripts/fixtures/search_autocomplete.html.haml b/spec/javascripts/fixtures/search_autocomplete.html.haml index 0421ed2182f..4aa54da9411 100644 --- a/spec/javascripts/fixtures/search_autocomplete.html.haml +++ b/spec/javascripts/fixtures/search_autocomplete.html.haml @@ -1,8 +1,6 @@ -.search.search-form.has-location-badge - %form.navbar-form +.search.search-form + %form.form-inline .search-input-container - %div.location-badge - This project .search-input-wrap .dropdown %input#search.search-input.dropdown-menu-toggle -- cgit v1.2.1