@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.

Fix blur and sort behavior for autocomplete

Summary:
Ref T10163.

- If you click a result, we get a blur before your click hits, and deactivate before the click can work. Instead, wait before responding to blur.
- Use the standard sort handler which puts unixnames over human names. Also use the standard filter which deals with disabled users not matching unless they're the only match.

Test Plan:
- Clicked a result, got a replacement.
- Named myself "dog dog", typed "@dog", user "@dog" was now first match despite me being "@admin".
- Used normal typeaheads to make sure I didn't break sort handler.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10163

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

+110 -100
+23 -23
resources/celerity/map.php
··· 8 8 return array( 9 9 'names' => array( 10 10 'core.pkg.css' => '3a97c8b9', 11 - 'core.pkg.js' => '1f5f365a', 11 + 'core.pkg.js' => '5813273d', 12 12 'darkconsole.pkg.js' => 'e7393ebb', 13 13 'differential.pkg.css' => '2de124c9', 14 14 'differential.pkg.js' => 'f83532f8', ··· 458 458 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 459 459 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 460 460 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 461 - 'rsrc/js/core/Prefab.js' => 'a15cbd65', 461 + 'rsrc/js/core/Prefab.js' => 'e67df814', 462 462 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 463 463 'rsrc/js/core/TextAreaUtils.js' => '9e54692d', 464 464 'rsrc/js/core/Title.js' => 'df5e11d2', ··· 507 507 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 508 508 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 509 509 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 510 - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'c5f5e42f', 510 + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '2b735afc', 511 511 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', 512 512 'rsrc/js/phuix/PHUIXFormControl.js' => '8fba1997', 513 513 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ··· 760 760 'phabricator-notification-menu-css' => 'f31c0bde', 761 761 'phabricator-object-selector-css' => '85ee8ce6', 762 762 'phabricator-phtize' => 'd254d646', 763 - 'phabricator-prefab' => 'a15cbd65', 763 + 'phabricator-prefab' => 'e67df814', 764 764 'phabricator-remarkup-css' => 'b748dc17', 765 765 'phabricator-search-results-css' => '7dea472c', 766 766 'phabricator-shaped-request' => '7cbe244b', ··· 836 836 'phui-workpanel-view-css' => 'adec7699', 837 837 'phuix-action-list-view' => 'b5c256b8', 838 838 'phuix-action-view' => '8cf6d262', 839 - 'phuix-autocomplete' => 'c5f5e42f', 839 + 'phuix-autocomplete' => '2b735afc', 840 840 'phuix-dropdown-menu' => 'bd4c8dca', 841 841 'phuix-form-control-view' => '8fba1997', 842 842 'phuix-icon-view' => 'bff6884b', ··· 1023 1023 'javelin-install', 1024 1024 'javelin-util', 1025 1025 ), 1026 + '2b735afc' => array( 1027 + 'javelin-install', 1028 + 'javelin-dom', 1029 + 'phuix-icon-view', 1030 + 'phabricator-prefab', 1031 + ), 1026 1032 '2b8de964' => array( 1027 1033 'javelin-install', 1028 1034 'javelin-util', ··· 1589 1595 'javelin-dom', 1590 1596 'javelin-reactor-dom', 1591 1597 ), 1592 - 'a15cbd65' => array( 1593 - 'javelin-install', 1594 - 'javelin-util', 1595 - 'javelin-dom', 1596 - 'javelin-typeahead', 1597 - 'javelin-tokenizer', 1598 - 'javelin-typeahead-preloaded-source', 1599 - 'javelin-typeahead-ondemand-source', 1600 - 'javelin-dom', 1601 - 'javelin-stratcom', 1602 - 'javelin-util', 1603 - ), 1604 1598 'a16ec1c6' => array( 1605 1599 'javelin-install', 1606 1600 'javelin-dom', ··· 1799 1793 'javelin-dom', 1800 1794 'javelin-vector', 1801 1795 ), 1802 - 'c5f5e42f' => array( 1803 - 'javelin-install', 1804 - 'javelin-dom', 1805 - 'phuix-icon-view', 1806 - 'phabricator-prefab', 1807 - ), 1808 1796 'c6f720ff' => array( 1809 1797 'javelin-install', 1810 1798 'javelin-dom', ··· 1976 1964 'javelin-json', 1977 1965 'javelin-workflow', 1978 1966 'javelin-magical-init', 1967 + ), 1968 + 'e67df814' => array( 1969 + 'javelin-install', 1970 + 'javelin-util', 1971 + 'javelin-dom', 1972 + 'javelin-typeahead', 1973 + 'javelin-tokenizer', 1974 + 'javelin-typeahead-preloaded-source', 1975 + 'javelin-typeahead-ondemand-source', 1976 + 'javelin-dom', 1977 + 'javelin-stratcom', 1978 + 'javelin-util', 1979 1979 ), 1980 1980 'e6e25838' => array( 1981 1981 'javelin-install',
+76 -75
webroot/rsrc/js/core/Prefab.js
··· 99 99 datasource = new JX.TypeaheadPreloadedSource(config.src); 100 100 } 101 101 102 - // Sort results so that the viewing user always comes up first; after 103 - // that, prefer unixname matches to realname matches. 104 - 105 - var sort_handler = function(value, list, cmp) { 106 - var priority_hits = {}; 107 - var self_hits = {}; 108 - 109 - var tokens = this.tokenize(value); 110 - 111 - for (var ii = 0; ii < list.length; ii++) { 112 - var item = list[ii]; 113 - 114 - for (var jj = 0; jj < tokens.length; jj++) { 115 - if (item.name.indexOf(tokens[jj]) === 0) { 116 - priority_hits[item.id] = true; 117 - } 118 - } 119 - 120 - if (!item.priority) { 121 - continue; 122 - } 123 - 124 - if (config.username && item.priority == config.username) { 125 - self_hits[item.id] = true; 126 - } 127 - 128 - for (var hh = 0; hh < tokens.length; hh++) { 129 - if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { 130 - priority_hits[item.id] = true; 131 - } 132 - } 133 - } 134 - 135 - list.sort(function(u, v) { 136 - if (self_hits[u.id] != self_hits[v.id]) { 137 - return self_hits[v.id] ? 1 : -1; 138 - } 139 - 140 - // If one result is open and one is closed, show the open result 141 - // first. The "!" tricks here are becaused closed values are display 142 - // strings, so the value is either `null` or some truthy string. If 143 - // we compare the values directly, we'll apply this rule to two 144 - // objects which are both closed but for different reasons, like 145 - // "Archived" and "Disabled". 146 - 147 - var u_open = !u.closed; 148 - var v_open = !v.closed; 149 - 150 - if (u_open != v_open) { 151 - if (u_open) { 152 - return -1; 153 - } else { 154 - return 1; 155 - } 156 - } 157 - 158 - if (priority_hits[u.id] != priority_hits[v.id]) { 159 - return priority_hits[v.id] ? 1 : -1; 160 - } 161 - 162 - // Sort users ahead of other result types. 163 - if (u.priorityType != v.priorityType) { 164 - if (u.priorityType == 'user') { 165 - return -1; 166 - } 167 - if (v.priorityType == 'user') { 168 - return 1; 169 - } 170 - } 171 - 172 - return cmp(u, v); 173 - }); 174 - }; 175 - 176 - datasource.setSortHandler(JX.bind(datasource, sort_handler)); 102 + datasource.setSortHandler( 103 + JX.bind(datasource, JX.Prefab.sortHandler, config)); 177 104 datasource.setFilterHandler(JX.Prefab.filterClosedResults); 178 105 datasource.setTransformer(JX.Prefab.transformDatasourceResults); 179 106 ··· 250 177 tokenizer: tokenizer 251 178 }; 252 179 }, 180 + 181 + sortHandler: function(config, value, list, cmp) { 182 + // Sort results so that the viewing user always comes up first; after 183 + // that, prefer unixname matches to realname matches. 184 + var priority_hits = {}; 185 + var self_hits = {}; 186 + 187 + var tokens = this.tokenize(value); 188 + 189 + for (var ii = 0; ii < list.length; ii++) { 190 + var item = list[ii]; 191 + 192 + for (var jj = 0; jj < tokens.length; jj++) { 193 + if (item.name.indexOf(tokens[jj]) === 0) { 194 + priority_hits[item.id] = true; 195 + } 196 + } 197 + 198 + if (!item.priority) { 199 + continue; 200 + } 201 + 202 + if (config.username && item.priority == config.username) { 203 + self_hits[item.id] = true; 204 + } 205 + 206 + for (var hh = 0; hh < tokens.length; hh++) { 207 + if (item.priority.substr(0, tokens[hh].length) == tokens[hh]) { 208 + priority_hits[item.id] = true; 209 + } 210 + } 211 + } 212 + 213 + list.sort(function(u, v) { 214 + if (self_hits[u.id] != self_hits[v.id]) { 215 + return self_hits[v.id] ? 1 : -1; 216 + } 217 + 218 + // If one result is open and one is closed, show the open result 219 + // first. The "!" tricks here are becaused closed values are display 220 + // strings, so the value is either `null` or some truthy string. If 221 + // we compare the values directly, we'll apply this rule to two 222 + // objects which are both closed but for different reasons, like 223 + // "Archived" and "Disabled". 224 + 225 + var u_open = !u.closed; 226 + var v_open = !v.closed; 227 + 228 + if (u_open != v_open) { 229 + if (u_open) { 230 + return -1; 231 + } else { 232 + return 1; 233 + } 234 + } 235 + 236 + if (priority_hits[u.id] != priority_hits[v.id]) { 237 + return priority_hits[v.id] ? 1 : -1; 238 + } 239 + 240 + // Sort users ahead of other result types. 241 + if (u.priorityType != v.priorityType) { 242 + if (u.priorityType == 'user') { 243 + return -1; 244 + } 245 + if (v.priorityType == 'user') { 246 + return 1; 247 + } 248 + } 249 + 250 + return cmp(u, v); 251 + }); 252 + }, 253 + 253 254 254 255 /** 255 256 * Filter callback for tokenizers and typeaheads which filters out closed
+11 -2
webroot/rsrc/js/phuix/PHUIXAutocomplete.js
··· 60 60 var device = JX.bind(this, this._ondevice); 61 61 JX.Stratcom.listen('phabricator-device-change', null, device); 62 62 63 - // When the user clicks away from the textarea, deactivate. 64 - var deactivate = JX.bind(this, this._deactivate); 63 + // When the user clicks away from the textarea, deactivate. However, we 64 + // don't want to deactivate if we're blurring because they clicked an 65 + // option in the dropdown, so put a timeout on the deactivation. This 66 + // will let the click run first if they did actually click a result. 67 + var deactivate = JX.bind(this, function() { 68 + setTimeout(JX.bind(this, this._deactivate), 10); 69 + }); 70 + 65 71 JX.DOM.listen(area, 'blur', null, deactivate); 66 72 }, 67 73 ··· 134 140 JX.bind(this, this._onresults, code)); 135 141 136 142 datasource.setTransformer(JX.bind(this, this._transformresult)); 143 + datasource.setSortHandler( 144 + JX.bind(datasource, JX.Prefab.sortHandler, {})); 145 + datasource.setFilterHandler(JX.Prefab.filterClosedResults); 137 146 138 147 this._datasources[code] = datasource; 139 148 }