diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '3a97c8b9', - 'core.pkg.js' => '1f5f365a', + 'core.pkg.js' => '5813273d', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => 'f83532f8', @@ -458,7 +458,7 @@ 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', - 'rsrc/js/core/Prefab.js' => 'a15cbd65', + 'rsrc/js/core/Prefab.js' => 'e67df814', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '9e54692d', 'rsrc/js/core/Title.js' => 'df5e11d2', @@ -507,7 +507,7 @@ 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'c5f5e42f', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '2b735afc', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', 'rsrc/js/phuix/PHUIXFormControl.js' => '8fba1997', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', @@ -760,7 +760,7 @@ 'phabricator-notification-menu-css' => 'f31c0bde', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => 'a15cbd65', + 'phabricator-prefab' => 'e67df814', 'phabricator-remarkup-css' => 'b748dc17', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', @@ -836,7 +836,7 @@ 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', - 'phuix-autocomplete' => 'c5f5e42f', + 'phuix-autocomplete' => '2b735afc', 'phuix-dropdown-menu' => 'bd4c8dca', 'phuix-form-control-view' => '8fba1997', 'phuix-icon-view' => 'bff6884b', @@ -1023,6 +1023,12 @@ 'javelin-install', 'javelin-util', ), + '2b735afc' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '2b8de964' => array( 'javelin-install', 'javelin-util', @@ -1589,18 +1595,6 @@ 'javelin-dom', 'javelin-reactor-dom', ), - 'a15cbd65' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), 'a16ec1c6' => array( 'javelin-install', 'javelin-dom', @@ -1799,12 +1793,6 @@ 'javelin-dom', 'javelin-vector', ), - 'c5f5e42f' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'c6f720ff' => array( 'javelin-install', 'javelin-dom', @@ -1977,6 +1965,18 @@ 'javelin-workflow', 'javelin-magical-init', ), + 'e67df814' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), 'e6e25838' => array( 'javelin-install', ), diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -99,81 +99,8 @@ datasource = new JX.TypeaheadPreloadedSource(config.src); } - // Sort results so that the viewing user always comes up first; after - // that, prefer unixname matches to realname matches. - - var sort_handler = function(value, list, cmp) { - var priority_hits = {}; - var self_hits = {}; - - var tokens = this.tokenize(value); - - for (var ii = 0; ii < list.length; ii++) { - var item = list[ii]; - - for (var jj = 0; jj < tokens.length; jj++) { - if (item.name.indexOf(tokens[jj]) === 0) { - priority_hits[item.id] = true; - } - } - - if (!item.priority) { - continue; - } - - if (config.username && item.priority == config.username) { - self_hits[item.id] = true; - } - - for (var hh = 0; hh < tokens.length; hh++) { - if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { - priority_hits[item.id] = true; - } - } - } - - list.sort(function(u, v) { - if (self_hits[u.id] != self_hits[v.id]) { - return self_hits[v.id] ? 1 : -1; - } - - // If one result is open and one is closed, show the open result - // first. The "!" tricks here are becaused closed values are display - // strings, so the value is either `null` or some truthy string. If - // we compare the values directly, we'll apply this rule to two - // objects which are both closed but for different reasons, like - // "Archived" and "Disabled". - - var u_open = !u.closed; - var v_open = !v.closed; - - if (u_open != v_open) { - if (u_open) { - return -1; - } else { - return 1; - } - } - - if (priority_hits[u.id] != priority_hits[v.id]) { - return priority_hits[v.id] ? 1 : -1; - } - - // Sort users ahead of other result types. - if (u.priorityType != v.priorityType) { - if (u.priorityType == 'user') { - return -1; - } - if (v.priorityType == 'user') { - return 1; - } - } - - return cmp(u, v); - }); - }; - - datasource.setSortHandler(JX.bind(datasource, sort_handler)); + datasource.setSortHandler( + JX.bind(datasource, JX.Prefab.sortHandler, config)); datasource.setFilterHandler(JX.Prefab.filterClosedResults); datasource.setTransformer(JX.Prefab.transformDatasourceResults); @@ -251,6 +178,80 @@ }; }, + sortHandler: function(config, value, list, cmp) { + // Sort results so that the viewing user always comes up first; after + // that, prefer unixname matches to realname matches. + var priority_hits = {}; + var self_hits = {}; + + var tokens = this.tokenize(value); + + for (var ii = 0; ii < list.length; ii++) { + var item = list[ii]; + + for (var jj = 0; jj < tokens.length; jj++) { + if (item.name.indexOf(tokens[jj]) === 0) { + priority_hits[item.id] = true; + } + } + + if (!item.priority) { + continue; + } + + if (config.username && item.priority == config.username) { + self_hits[item.id] = true; + } + + for (var hh = 0; hh < tokens.length; hh++) { + if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { + priority_hits[item.id] = true; + } + } + } + + list.sort(function(u, v) { + if (self_hits[u.id] != self_hits[v.id]) { + return self_hits[v.id] ? 1 : -1; + } + + // If one result is open and one is closed, show the open result + // first. The "!" tricks here are becaused closed values are display + // strings, so the value is either `null` or some truthy string. If + // we compare the values directly, we'll apply this rule to two + // objects which are both closed but for different reasons, like + // "Archived" and "Disabled". + + var u_open = !u.closed; + var v_open = !v.closed; + + if (u_open != v_open) { + if (u_open) { + return -1; + } else { + return 1; + } + } + + if (priority_hits[u.id] != priority_hits[v.id]) { + return priority_hits[v.id] ? 1 : -1; + } + + // Sort users ahead of other result types. + if (u.priorityType != v.priorityType) { + if (u.priorityType == 'user') { + return -1; + } + if (v.priorityType == 'user') { + return 1; + } + } + + return cmp(u, v); + }); + }, + + /** * Filter callback for tokenizers and typeaheads which filters out closed * or disabled objects unless they are the only options. diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -60,8 +60,14 @@ var device = JX.bind(this, this._ondevice); JX.Stratcom.listen('phabricator-device-change', null, device); - // When the user clicks away from the textarea, deactivate. - var deactivate = JX.bind(this, this._deactivate); + // When the user clicks away from the textarea, deactivate. However, we + // don't want to deactivate if we're blurring because they clicked an + // option in the dropdown, so put a timeout on the deactivation. This + // will let the click run first if they did actually click a result. + var deactivate = JX.bind(this, function() { + setTimeout(JX.bind(this, this._deactivate), 10); + }); + JX.DOM.listen(area, 'blur', null, deactivate); }, @@ -134,6 +140,9 @@ JX.bind(this, this._onresults, code)); datasource.setTransformer(JX.bind(this, this._transformresult)); + datasource.setSortHandler( + JX.bind(datasource, JX.Prefab.sortHandler, {})); + datasource.setFilterHandler(JX.Prefab.filterClosedResults); this._datasources[code] = datasource; }