@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Sort "closed" results (like disabled users) to the bottom of typeaheads, but don't hide them completely

Summary:
Fixes T12538. Instead of hiding "closed" results unless only closed results match, show closed results but sort them to the bottom.

This fixes the actual issue in T12538, and I think this is probably the correct/best behavior for the global search.

It also makes all other typeaheads use this behavior. They currently have a "bug" where enabled user `abcd` makes it impossible to select disabled user `abc`. This manifests in some real cases, where enabled function `members(abc)` makes it impossible to disabled user `abc` in some function tokenizers.

If ths feels worse, we could go back to filtering in the simpler cases and introduce a rule like "show closed results if only closed results would be shown OR if query is an exact match for the disabled result", but that gets dicier because "exact match" is a fuzzy concept.

(There are a lot of other minor bad behaviors that this doesn't try to fix.)

Test Plan:
Enabled project "instabug" no longer prevents bot user "instabug" from being shown:

{F4903843}

Disabled user "mmaven" is sorted below enabled user "mmclewis", in defiance of the otherwise alphabetical order. There's no visual cue that this user is disabled because of T6906.

{F4903845}

Same as above, but this source renders "disabled" in a more obvious way:

{F4903848}

Function selecting members of active project `members(instabug)` no longer prevents selection of bot user `instabug`:

{F4903849}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12538

Differential Revision: https://secure.phabricator.com/D17695

+75 -123
+36 -36
resources/celerity/map.php
··· 10 10 'conpherence.pkg.css' => '437d3b5a', 11 11 'conpherence.pkg.js' => '281b1a73', 12 12 'core.pkg.css' => 'b2ad82f4', 13 - 'core.pkg.js' => 'fbc1c380', 13 + 'core.pkg.js' => 'bf3b5cdf', 14 14 'darkconsole.pkg.js' => 'e7393ebb', 15 15 'differential.pkg.css' => '90b30783', 16 16 'differential.pkg.js' => 'ddfeb49b', ··· 472 472 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', 473 473 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 474 474 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 475 - 'rsrc/js/core/Prefab.js' => '8d40ae75', 475 + 'rsrc/js/core/Prefab.js' => 'c5af80a2', 476 476 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 477 477 'rsrc/js/core/TextAreaUtils.js' => '320810c8', 478 478 'rsrc/js/core/Title.js' => '485aaa6c', ··· 510 510 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 511 511 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 512 512 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', 513 - 'rsrc/js/core/behavior-search-typeahead.js' => '06c32383', 513 + 'rsrc/js/core/behavior-search-typeahead.js' => '0f2a0820', 514 514 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 515 515 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 516 516 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', ··· 528 528 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 529 529 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 530 530 'rsrc/js/phuix/PHUIXActionView.js' => 'b3465b9b', 531 - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd5b2abf3', 531 + 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'd713a2c5', 532 532 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50', 533 533 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', 534 534 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ··· 666 666 'javelin-behavior-phabricator-oncopy' => '2926fff2', 667 667 'javelin-behavior-phabricator-remarkup-assist' => '0ca788bd', 668 668 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 669 - 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 669 + 'javelin-behavior-phabricator-search-typeahead' => '0f2a0820', 670 670 'javelin-behavior-phabricator-show-older-transactions' => '94c65b72', 671 671 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 672 672 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', ··· 792 792 'phabricator-notification-menu-css' => '6a697e43', 793 793 'phabricator-object-selector-css' => '85ee8ce6', 794 794 'phabricator-phtize' => 'd254d646', 795 - 'phabricator-prefab' => '8d40ae75', 795 + 'phabricator-prefab' => 'c5af80a2', 796 796 'phabricator-remarkup-css' => '17c0fb37', 797 797 'phabricator-search-results-css' => 'f87d23ad', 798 798 'phabricator-shaped-request' => '7cbe244b', ··· 885 885 'phui-workpanel-view-css' => 'a3a63478', 886 886 'phuix-action-list-view' => 'b5c256b8', 887 887 'phuix-action-view' => 'b3465b9b', 888 - 'phuix-autocomplete' => 'd5b2abf3', 888 + 'phuix-autocomplete' => 'd713a2c5', 889 889 'phuix-dropdown-menu' => '8018ee50', 890 890 'phuix-form-control-view' => '83e03671', 891 891 'phuix-icon-view' => 'bff6884b', ··· 943 943 'javelin-stratcom', 944 944 'javelin-workflow', 945 945 ), 946 - '06c32383' => array( 947 - 'javelin-behavior', 948 - 'javelin-typeahead-ondemand-source', 949 - 'javelin-typeahead', 950 - 'javelin-dom', 951 - 'javelin-uri', 952 - 'javelin-util', 953 - 'javelin-stratcom', 954 - 'phabricator-prefab', 955 - 'phuix-icon-view', 956 - ), 957 946 '0825c27a' => array( 958 947 'javelin-behavior', 959 948 'javelin-dom', ··· 998 987 'javelin-vector', 999 988 'phuix-autocomplete', 1000 989 'javelin-mask', 990 + ), 991 + '0f2a0820' => array( 992 + 'javelin-behavior', 993 + 'javelin-typeahead-ondemand-source', 994 + 'javelin-typeahead', 995 + 'javelin-dom', 996 + 'javelin-uri', 997 + 'javelin-util', 998 + 'javelin-stratcom', 999 + 'phabricator-prefab', 1000 + 'phuix-icon-view', 1001 1001 ), 1002 1002 '0f764c35' => array( 1003 1003 'javelin-install', ··· 1588 1588 'javelin-stratcom', 1589 1589 'javelin-install', 1590 1590 ), 1591 - '8d40ae75' => array( 1592 - 'javelin-install', 1593 - 'javelin-util', 1594 - 'javelin-dom', 1595 - 'javelin-typeahead', 1596 - 'javelin-tokenizer', 1597 - 'javelin-typeahead-preloaded-source', 1598 - 'javelin-typeahead-ondemand-source', 1599 - 'javelin-dom', 1600 - 'javelin-stratcom', 1601 - 'javelin-util', 1602 - ), 1603 1591 '8fadb715' => array( 1604 1592 'javelin-install', 1605 1593 'javelin-util', ··· 1955 1943 'c587b80f' => array( 1956 1944 'javelin-install', 1957 1945 ), 1946 + 'c5af80a2' => array( 1947 + 'javelin-install', 1948 + 'javelin-util', 1949 + 'javelin-dom', 1950 + 'javelin-typeahead', 1951 + 'javelin-tokenizer', 1952 + 'javelin-typeahead-preloaded-source', 1953 + 'javelin-typeahead-ondemand-source', 1954 + 'javelin-dom', 1955 + 'javelin-stratcom', 1956 + 'javelin-util', 1957 + ), 1958 1958 'c7ccd872' => array( 1959 1959 'phui-fontkit-css', 1960 1960 ), ··· 2055 2055 'javelin-uri', 2056 2056 'phabricator-notification', 2057 2057 ), 2058 - 'd5b2abf3' => array( 2059 - 'javelin-install', 2060 - 'javelin-dom', 2061 - 'phuix-icon-view', 2062 - 'phabricator-prefab', 2063 - ), 2064 2058 'd6a7e717' => array( 2065 2059 'multirow-row-manager', 2066 2060 'javelin-install', ··· 2068 2062 'javelin-dom', 2069 2063 'javelin-stratcom', 2070 2064 'javelin-json', 2065 + 'phabricator-prefab', 2066 + ), 2067 + 'd713a2c5' => array( 2068 + 'javelin-install', 2069 + 'javelin-dom', 2070 + 'phuix-icon-view', 2071 2071 'phabricator-prefab', 2072 2072 ), 2073 2073 'd7a74243' => array(
-32
webroot/rsrc/js/core/Prefab.js
··· 101 101 102 102 datasource.setSortHandler( 103 103 JX.bind(datasource, JX.Prefab.sortHandler, config)); 104 - datasource.setFilterHandler(JX.Prefab.filterClosedResults); 105 104 datasource.setTransformer(JX.Prefab.transformDatasourceResults); 106 105 107 106 var typeahead = new JX.Typeahead( ··· 255 254 }); 256 255 }, 257 256 258 - 259 - /** 260 - * Filter callback for tokenizers and typeaheads which filters out closed 261 - * or disabled objects unless they are the only options. 262 - */ 263 - filterClosedResults: function(value, list) { 264 - // Look for any open result. 265 - var has_open = false; 266 - var ii; 267 - for (ii = 0; ii < list.length; ii++) { 268 - if (!list[ii].closed) { 269 - has_open = true; 270 - break; 271 - } 272 - } 273 - 274 - if (!has_open) { 275 - // Everything is closed, so just use it as-is. 276 - return list; 277 - } 278 - 279 - // Otherwise, only display the open results. 280 - var results = []; 281 - for (ii = 0; ii < list.length; ii++) { 282 - if (!list[ii].closed) { 283 - results.push(list[ii]); 284 - } 285 - } 286 - 287 - return results; 288 - }, 289 257 290 258 /** 291 259 * Transform results from a wire format into a usable format in a standard
+39 -54
webroot/rsrc/js/core/behavior-search-typeahead.js
··· 52 52 53 53 datasource.setTransformer(transform); 54 54 55 - // Sort handler that orders results by type (e.g., applications, users) 56 - // and then selects for good matches on the "priority" substrings if they 57 - // exist (for instance, username matches are preferred over real name 58 - // matches, and application name matches are preferred over application 59 - // flavor text matches). 60 - 61 55 var sort_handler = function(value, list, cmp) { 62 - var priority_hits = {}; 63 - var type_priority = { 64 - 'jump' : 1, 65 - 'apps' : 2, 66 - 'proj' : 3, 67 - 'user' : 4, 68 - 'repo' : 5, 69 - 'symb' : 6 70 - }; 71 - 72 - var tokens = this.tokenize(value); 56 + // First, sort all the results normally. 57 + JX.bind(this, JX.Prefab.sortHandler, {}, value, list, cmp)(); 73 58 59 + // Now we're going to apply some special rules to order results by type, 60 + // so applications always appear near the top, then users, etc. 74 61 var ii; 75 - for (ii = 0; ii < list.length; ii++) { 76 - var item = list[ii]; 77 62 78 - for (var jj = 0; jj < tokens.length; jj++) { 79 - if (item.name.indexOf(tokens[jj]) === 0) { 80 - priority_hits[item.id] = true; 81 - } 82 - } 63 + var type_order = [ 64 + 'jump', 65 + 'apps', 66 + 'proj', 67 + 'user', 68 + 'repo', 69 + 'symb', 70 + 'misc' 71 + ]; 83 72 84 - if (!item.priority) { 85 - continue; 86 - } 87 - 88 - for (var hh = 0; hh < tokens.length; hh++) { 89 - if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { 90 - priority_hits[item.id] = true; 91 - } 92 - } 73 + var type_map = {}; 74 + for (ii = 0; ii < type_order.length; ii++) { 75 + type_map[type_order[ii]] = true; 93 76 } 94 77 95 - list.sort(function(u, v) { 96 - var u_type = type_priority[u.priorityType] || 999; 97 - var v_type = type_priority[v.priorityType] || 999; 78 + var buckets = {}; 79 + for (ii = 0; ii < list.length; ii++) { 80 + var item = list[ii]; 98 81 99 - if (u_type != v_type) { 100 - return u_type - v_type; 82 + var type = item.priorityType; 83 + if (!type_map.hasOwnProperty(type)) { 84 + type = 'misc'; 101 85 } 102 86 103 - if (priority_hits[u.id] != priority_hits[v.id]) { 104 - return priority_hits[v.id] ? 1 : -1; 87 + if (!buckets.hasOwnProperty(type)) { 88 + buckets[type] = []; 105 89 } 106 90 107 - return cmp(u, v); 108 - }); 91 + buckets[type].push(item); 92 + } 109 93 110 94 // If we have more results than fit, limit each type of result to 3, so 111 95 // we show 3 applications, then 3 users, etc. For jump items, we show only 112 96 // one result. 113 - var type_count = 0; 114 - var current_type = null; 115 - for (ii = 0; ii < list.length; ii++) { 116 - if (list[ii].type != current_type) { 117 - current_type = list[ii].type; 118 - type_count = 1; 119 - } else { 120 - type_count++; 97 + 98 + var jj; 99 + var results = []; 100 + for (ii = 0; ii < type_order.length; ii++) { 101 + var current_type = type_order[ii]; 102 + var type_list = buckets[current_type] || []; 103 + for (jj = 0; jj < type_list.length; jj++) { 121 104 122 105 // Skip this item if: 123 106 // - it's a jump nav item, and we already have at least one jump ··· 125 108 // - we have more items than will fit in the typeahead, and this 126 109 // is the 4..Nth result of its type. 127 110 128 - var skip = ((current_type == 'jump') && (type_count > 1)) || 111 + var skip = ((current_type == 'jump') && (jj > 1)) || 129 112 ((list.length > config.limit) && (type_count > 3)); 130 113 if (skip) { 131 - list.splice(ii, 1); 132 - ii--; 114 + continue; 133 115 } 116 + 117 + results.push(type_list[jj]); 134 118 } 135 119 } 136 120 121 + // Replace the list in place with the results. 122 + list.splice.apply(list, [0, list.length].concat(results)); 137 123 }; 138 124 139 125 datasource.setSortHandler(JX.bind(datasource, sort_handler)); 140 - datasource.setFilterHandler(JX.Prefab.filterClosedResults); 141 126 datasource.setMaximumResultCount(config.limit); 142 127 143 128 var typeahead = new JX.Typeahead(JX.$(config.id), JX.$(config.input));
-1
webroot/rsrc/js/phuix/PHUIXAutocomplete.js
··· 153 153 datasource.setTransformer(JX.bind(this, this._transformresult)); 154 154 datasource.setSortHandler( 155 155 JX.bind(datasource, JX.Prefab.sortHandler, {})); 156 - datasource.setFilterHandler(JX.Prefab.filterClosedResults); 157 156 158 157 this._datasources[code] = datasource; 159 158 }