diff options
Diffstat (limited to 'polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js')
-rw-r--r-- | polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js | 127 |
1 files changed, 76 insertions, 51 deletions
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js index 4452b04880..de015348f7 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js @@ -14,11 +14,12 @@ (function() { 'use strict'; - // Possible static search options for auto complete. - var SEARCH_OPERATORS = [ + // Possible static search options for auto complete, without negations. + const SEARCH_OPERATORS = [ 'added:', 'age:', 'age:1week', // Give an example age + 'assignee:', 'author:', 'branch:', 'bug:', @@ -39,20 +40,26 @@ 'has:edit', 'has:star', 'has:stars', + 'has:unresolved', + 'hashtag:', 'intopic:', 'is:', 'is:abandoned', + 'is:assigned', 'is:closed', - 'is:draft', + 'is:ignored', 'is:mergeable', 'is:merged', 'is:open', 'is:owner', 'is:pending', + 'is:private', 'is:reviewed', 'is:reviewer', 'is:starred', + 'is:submittable', 'is:watched', + 'is:wip', 'label:', 'message:', 'owner:', @@ -71,7 +78,6 @@ 'status:', 'status:abandoned', 'status:closed', - 'status:draft', 'status:merged', 'status:open', 'status:pending', @@ -80,24 +86,26 @@ 'tr:', ]; - var SELF_EXPRESSION = 'self'; + // All of the ops, with corresponding negations. + const SEARCH_OPERATORS_WITH_NEGATIONS = + SEARCH_OPERATORS.concat(SEARCH_OPERATORS.map(op => `-${op}`)); - var MAX_AUTOCOMPLETE_RESULTS = 10; + const SELF_EXPRESSION = 'self'; + const ME_EXPRESSION = 'me'; - var TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g; + const MAX_AUTOCOMPLETE_RESULTS = 10; + + const TOKENIZE_REGEX = /(?:[^\s"]+|"[^"]*")+\s*/g; Polymer({ is: 'gr-search-bar', behaviors: [ + Gerrit.AnonymousNameBehavior, Gerrit.KeyboardShortcutBehavior, Gerrit.URLEncodingBehavior, ], - listeners: { - 'searchButton.tap': '_preventDefaultAndNavigateToInputVal', - }, - keyBindings: { '/': '_handleForwardSlashKey', }, @@ -111,22 +119,33 @@ }, keyEventTarget: { type: Object, - value: function() { return document.body; }, + value() { return document.body; }, }, query: { type: Function, - value: function() { + value() { return this._getSearchSuggestions.bind(this); }, }, _inputVal: String, + _threshold: { + type: Number, + value: 1, + }, + _config: Object, }, - _valueChanged: function(value) { + attached() { + this.$.restAPI.getConfig().then(cfg => { + this._config = cfg; + }); + }, + + _valueChanged(value) { this._inputVal = value; }, - _handleInputCommit: function(e) { + _handleInputCommit(e) { this._preventDefaultAndNavigateToInputVal(e); }, @@ -138,9 +157,9 @@ * * @param {!Event} e */ - _preventDefaultAndNavigateToInputVal: function(e) { + _preventDefaultAndNavigateToInputVal(e) { e.preventDefault(); - var target = Polymer.dom(e).rootTarget; + const target = Polymer.dom(e).rootTarget; // If the target is the #searchInput or has a sub-input component, that // is what holds the focus as opposed to the target from the DOM event. if (target.$.input) { @@ -153,6 +172,10 @@ } }, + _accountOrAnon(name) { + return this.getUserName(this._config, name, false); + }, + /** * Fetch from the API the predicted accounts. * @param {string} predicate - The first part of the search term, e.g. @@ -162,22 +185,26 @@ * @return {!Promise} This returns a promise that resolves to an array of * strings. */ - _fetchAccounts: function(predicate, expression) { + _fetchAccounts(predicate, expression) { if (expression.length === 0) { return Promise.resolve([]); } return this.$.restAPI.getSuggestedAccounts( expression, MAX_AUTOCOMPLETE_RESULTS) - .then(function(accounts) { + .then(accounts => { if (!accounts) { return []; } - return accounts.map(function(acct) { - return predicate + ':"' + acct.name + ' <' + acct.email + '>"'; - }); - }).then(function(accounts) { + return accounts.map(acct => acct.email ? + `${predicate}:${acct.email}` : + `${predicate}:"${this._accountOrAnon(acct)}"`); + }).then(accounts => { // When the expression supplied is a beginning substring of 'self', // add it as an autocomplete option. - return SELF_EXPRESSION.indexOf(expression) === 0 ? - accounts.concat([predicate + ':' + SELF_EXPRESSION]) : - accounts; + if (SELF_EXPRESSION.startsWith(expression)) { + return accounts.concat([predicate + ':' + SELF_EXPRESSION]); + } else if (ME_EXPRESSION.startsWith(expression)) { + return accounts.concat([predicate + ':' + ME_EXPRESSION]); + } else { + return accounts; + } }); }, @@ -190,15 +217,15 @@ * @return {!Promise} This returns a promise that resolves to an array of * strings. */ - _fetchGroups: function(predicate, expression) { + _fetchGroups(predicate, expression) { if (expression.length === 0) { return Promise.resolve([]); } return this.$.restAPI.getSuggestedGroups( expression, MAX_AUTOCOMPLETE_RESULTS) - .then(function(groups) { + .then(groups => { if (!groups) { return []; } - var keys = Object.keys(groups); - return keys.map(function(key) { return predicate + ':' + key; }); + const keys = Object.keys(groups); + return keys.map(key => predicate + ':' + key); }); }, @@ -211,14 +238,14 @@ * @return {!Promise} This returns a promise that resolves to an array of * strings. */ - _fetchProjects: function(predicate, expression) { + _fetchProjects(predicate, expression) { return this.$.restAPI.getSuggestedProjects( expression, MAX_AUTOCOMPLETE_RESULTS) - .then(function(projects) { + .then(projects => { if (!projects) { return []; } - var keys = Object.keys(projects); - return keys.map(function(key) { return predicate + ':' + key; }); + const keys = Object.keys(projects); + return keys.map(key => predicate + ':' + key); }); }, @@ -229,11 +256,11 @@ * @return {!Promise} This returns a promise that resolves to an array of * strings. */ - _fetchSuggestions: function(input) { + _fetchSuggestions(input) { // Split the input on colon to get a two part predicate/expression. - var splitInput = input.split(':'); - var predicate = splitInput[0]; - var expression = splitInput[1] || ''; + const splitInput = input.split(':'); + const predicate = splitInput[0]; + const expression = splitInput[1] || ''; // Switch on the predicate to determine what to autocomplete. switch (predicate) { case 'ownerin': @@ -258,10 +285,8 @@ return this._fetchAccounts(predicate, expression); default: - return Promise.resolve(SEARCH_OPERATORS - .filter(function(operator) { - return operator.indexOf(input) !== -1; - })); + return Promise.resolve(SEARCH_OPERATORS_WITH_NEGATIONS + .filter(operator => operator.includes(input))); } }, @@ -271,19 +296,19 @@ * @return {!Promise} This returns a promise that resolves to an array of * strings. */ - _getSearchSuggestions: function(input) { + _getSearchSuggestions(input) { // Allow spaces within quoted terms. - var tokens = input.match(TOKENIZE_REGEX); - var trimmedInput = tokens[tokens.length - 1].toLowerCase(); + const tokens = input.match(TOKENIZE_REGEX); + const trimmedInput = tokens[tokens.length - 1].toLowerCase(); return this._fetchSuggestions(trimmedInput) - .then(function(operators) { + .then(operators => { if (!operators || !operators.length) { return []; } return operators // Prioritize results that start with the input. - .sort(function(a, b) { - var aContains = a.toLowerCase().indexOf(trimmedInput); - var bContains = b.toLowerCase().indexOf(trimmedInput); + .sort((a, b) => { + const aContains = a.toLowerCase().indexOf(trimmedInput); + const bContains = b.toLowerCase().indexOf(trimmedInput); if (aContains === bContains) { return a.localeCompare(b); } @@ -298,7 +323,7 @@ // Return only the first {MAX_AUTOCOMPLETE_RESULTS} results. .slice(0, MAX_AUTOCOMPLETE_RESULTS - 1) // Map to an object to play nice with gr-autocomplete. - .map(function(operator) { + .map(operator => { return { name: operator, value: operator, @@ -307,7 +332,7 @@ }); }, - _handleForwardSlashKey: function(e) { + _handleForwardSlashKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } |