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

Add a basic search typeahead

Summary:
This needs a bunch of refinement but pretty much works. Currently shows only users and applications. Plans:

- Show actual search results too.
- Clean up the datasource endpoint so it's less of a mess.
- Make other typeaheads look more like this one.
- Improve sorting.
- Make object names hit the named objects as the first match.

Test Plan: Will attach screenshots.

Reviewers: btrahan, vrana, chad

Reviewed By: vrana

CC: aran

Maniphest Tasks: T1569

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

+260 -25
+16 -1
src/__celerity_resource_map__.php
··· 1439 1439 ), 1440 1440 'disk' => '/rsrc/js/application/core/behavior-oncopy.js', 1441 1441 ), 1442 + 'javelin-behavior-phabricator-search-typeahead' => 1443 + array( 1444 + 'uri' => '/res/9ceffb09/rsrc/js/application/core/behavior-search-typeahead.js', 1445 + 'type' => 'js', 1446 + 'requires' => 1447 + array( 1448 + 0 => 'javelin-behavior', 1449 + 1 => 'javelin-typeahead-ondemand-source', 1450 + 2 => 'javelin-typeahead', 1451 + 3 => 'javelin-dom', 1452 + 4 => 'javelin-uri', 1453 + 5 => 'javelin-stratcom', 1454 + ), 1455 + 'disk' => '/rsrc/js/application/core/behavior-search-typeahead.js', 1456 + ), 1442 1457 'javelin-behavior-phabricator-tooltips' => 1443 1458 array( 1444 1459 'uri' => '/res/49f92a92/rsrc/js/application/core/behavior-tooltip.js', ··· 2263 2278 ), 2264 2279 'phabricator-main-menu-view' => 2265 2280 array( 2266 - 'uri' => '/res/795788ca/rsrc/css/application/base/main-menu-view.css', 2281 + 'uri' => '/res/5bae3234/rsrc/css/application/base/main-menu-view.css', 2267 2282 'type' => 'css', 2268 2283 'requires' => 2269 2284 array(
+22 -2
src/applications/base/PhabricatorApplication.php
··· 30 30 31 31 32 32 public function getName() { 33 - return substr(__CLASS__, strlen('PhabricatorApplication')); 33 + return substr(get_class($this), strlen('PhabricatorApplication')); 34 + } 35 + 36 + public function getShortDescription() { 37 + return $this->getName().' Application'; 34 38 } 35 39 36 40 public function isEnabled() { 37 41 return true; 38 42 } 39 43 44 + public function getPHID() { 45 + return 'PHID-APPS-'.get_class($this); 46 + } 40 47 41 - /* -( Application Information )-------------------------------------------- */ 48 + public function getTypeaheadURI() { 49 + return $this->getBaseURI(); 50 + } 51 + 52 + public function getBaseURI() { 53 + return null; 54 + } 55 + 56 + public function getIconURI() { 57 + return PhabricatorUser::getDefaultProfileImageURI(); 58 + } 59 + 60 + 61 + /* -( URI Routing )-------------------------------------------------------- */ 42 62 43 63 44 64 public function getRoutes() {
+8
src/applications/differential/application/PhabricatorApplicationDifferential.php
··· 24 24 ); 25 25 } 26 26 27 + public function getBaseURI() { 28 + return '/differential/'; 29 + } 30 + 31 + public function getShortDescription() { 32 + return 'Code Review Application'; 33 + } 34 + 27 35 } 28 36
+66 -3
src/applications/typeahead/controller/PhabricatorTypeaheadCommonDatasourceController.php
··· 28 28 $request = $this->getRequest(); 29 29 $query = $request->getStr('q'); 30 30 31 + $need_rich_data = false; 32 + 31 33 $need_users = false; 34 + $need_applications = false; 32 35 $need_all_users = false; 33 36 $need_lists = false; 34 37 $need_projs = false; ··· 38 41 $need_arcanist_projects = false; 39 42 $need_noproject = false; 40 43 switch ($this->type) { 44 + case 'mainsearch': 45 + $need_users = true; 46 + $need_applications = true; 47 + $need_rich_data = true; 48 + break; 41 49 case 'searchowner': 42 50 $need_users = true; 43 51 $need_upforgrabs = true; ··· 78 86 case 'arcanistprojects': 79 87 $need_arcanist_projects = true; 80 88 break; 89 + } 81 90 82 - } 91 + // TODO: We transfer these fields without keys as an opitimization, but this 92 + // function is hard to read as a result. Until we can sort it out, here's 93 + // what the position arguments mean: 94 + // 95 + // 0: (required) name to match against what the user types 96 + // 1: (optional) URI 97 + // 2: (required) PHID 98 + // 3: (optional) priority matching string 99 + // 4: (optional) display name [overrides position 0] 100 + // 5: (optional) display type 101 + // 6: (optional) image URI 83 102 84 103 $data = array(); 85 104 ··· 106 125 'userName', 107 126 'realName', 108 127 'phid'); 128 + 129 + if ($need_rich_data) { 130 + $columns[] = 'profileImagePHID'; 131 + } 132 + 109 133 if ($query) { 110 134 $conn_r = id(new PhabricatorUser())->establishConnection('r'); 111 135 $ids = queryfx_all( 112 136 $conn_r, 113 - 'SELECT DISTINCT userID FROM %T WHERE token LIKE %>', 137 + 'SELECT DISTINCT userID FROM %T WHERE token LIKE %> OR 1 = 1', 114 138 PhabricatorUser::NAMETOKEN_TABLE, 115 139 $query); 116 140 $ids = ipull($ids, 'userID'); ··· 125 149 } else { 126 150 $users = id(new PhabricatorUser())->loadColumns($columns); 127 151 } 152 + 153 + if ($need_rich_data) { 154 + $phids = mpull($users, 'getPHID'); 155 + $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); 156 + } 157 + 128 158 foreach ($users as $user) { 129 159 if (!$need_all_users) { 130 160 if ($user->getIsSystemAgent()) { ··· 134 164 continue; 135 165 } 136 166 } 137 - $data[] = array( 167 + $spec = array( 138 168 $user->getUsername().' ('.$user->getRealName().')', 139 169 '/p/'.$user->getUsername(), 140 170 $user->getPHID(), 141 171 $user->getUsername(), 172 + null, 173 + 'User', 142 174 ); 175 + if ($need_rich_data) { 176 + $spec[] = $handles[$user->getPHID()]->getImageURI(); 177 + } 178 + $data[] = $spec; 143 179 } 144 180 } 145 181 ··· 198 234 null, 199 235 $proj->getPHID(), 200 236 ); 237 + } 238 + } 239 + 240 + if ($need_applications) { 241 + $applications = PhabricatorApplication::getAllInstalledApplications(); 242 + foreach ($applications as $application) { 243 + $uri = $application->getTypeaheadURI(); 244 + if (!$uri) { 245 + continue; 246 + } 247 + $data[] = array( 248 + $application->getName().' '.$application->getShortDescription(), 249 + $uri, 250 + $application->getPHID(), 251 + $application->getName(), 252 + $application->getName(), 253 + $application->getShortDescription(), 254 + $application->getIconURI(), 255 + ); 256 + } 257 + } 258 + 259 + if (!$need_rich_data) { 260 + foreach ($data as $key => $info) { 261 + unset($data[$key][4]); 262 + unset($data[$key][5]); 263 + unset($data[$key][6]); 201 264 } 202 265 } 203 266
+18 -4
src/view/page/menu/PhabricatorMainMenuSearchView.php
··· 42 42 public function render() { 43 43 $user = $this->user; 44 44 45 + $target_id = celerity_generate_unique_node_id(); 45 46 $search_id = $this->getID(); 46 47 47 48 $input = phutil_render_tag( ··· 49 50 array( 50 51 'type' => 'text', 51 52 'name' => 'query', 52 - 'id' => $search_id, 53 + 'id' => $search_id, 54 + 'autocomplete' => 'off', 53 55 )); 54 56 55 57 $scope = $this->scope; 56 58 59 + $target = javelin_render_tag( 60 + 'div', 61 + array( 62 + 'id' => $target_id, 63 + 'class' => 'phabricator-main-menu-search-target', 64 + ), 65 + ''); 66 + 57 67 Javelin::initBehavior( 58 - 'placeholder', 68 + 'phabricator-search-typeahead', 59 69 array( 60 - 'id' => $search_id, 61 - 'text' => PhabricatorSearchScope::getScopePlaceholder($scope), 70 + 'id' => $target_id, 71 + 'input' => $search_id, 72 + 'src' => '/typeahead/common/mainsearch/', 73 + 'limit' => 10, 74 + 'placeholder' => PhabricatorSearchScope::getScopePlaceholder($scope), 62 75 )); 63 76 64 77 $scope_input = phutil_render_tag( ··· 79 92 $input. 80 93 '<button>Search</button>'. 81 94 $scope_input. 95 + $target. 82 96 '</div>'); 83 97 84 98 $group = new PhabricatorMainMenuGroupView();
+78 -15
webroot/rsrc/css/application/base/main-menu-view.css
··· 14 14 background: #33393d; 15 15 position: relative; 16 16 box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25); 17 - overflow: hidden; 18 17 height: 44px; 19 18 } 20 19 ··· 49 48 50 49 */ 51 50 51 + .phabricator-main-menu-group { 52 + height: 44px; 53 + position: relative; 54 + } 55 + 52 56 .device-desktop .phabricator-main-menu-group { 53 57 display: inline-block; 54 58 text-align: left; 55 - height: 44px; 56 59 } 57 60 58 61 .device-tablet .phabricator-main-menu-group, 59 62 .device-phone .phabricator-main-menu-group { 60 - clear: both; 61 63 width: 100%; 62 - overflow: hidden; 63 - border-bottom: 1px solid #33393d; 64 64 display: block; 65 65 } 66 66 67 + .device-tablet .phabricator-main-menu-group + .phabricator-main-menu-group, 68 + .device-phone .phabricator-main-menu-group + .phabricator-main-menu-group { 69 + margin-top: 1px; 70 + } 71 + 67 72 68 73 /* - Logo ---------------------------------------------------------------------- 69 74 ··· 72 77 73 78 */ 74 79 75 - .phabricator-main-menu-group-logo { 80 + .device-desktop .phabricator-main-menu-group-logo { 76 81 float: left; 77 82 } 78 83 ··· 144 149 margin: 9px; 145 150 display: inline-block; 146 151 background-repeat: no-repeat; 147 - position: relative; 148 - overflow: hidden; 149 152 } 150 153 151 154 .device-desktop .phabricator-main-menu-icon-label { ··· 156 159 .device-phone .phabricator-main-menu-icon-label { 157 160 font-weight: bold; 158 161 color: #ffffff; 159 - margin-left: 40px; 162 + position: absolute; 163 + display: block; 160 164 height: 26px; 161 - margin: 15px 9px 3px 60px; 162 - display: block; 165 + padding: 15px 0 3px; 166 + left: 60px; 167 + right: 0px; 168 + top: 0px; 163 169 } 164 170 165 171 .device-tablet .phabricator-main-menu-icon, 166 172 .device-phone .phabricator-main-menu-icon { 167 - font-weight: bold; 168 - color: white; 169 - text-decoration: none; 170 - border: 0; 171 173 margin-left: 24px; 172 174 position: absolute; 173 175 } ··· 189 191 height: 24px; 190 192 } 191 193 194 + .phabricator-main-menu-search-target { 195 + position: absolute; 196 + top: 46px; 197 + } 198 + 199 + .device-desktop .phabricator-main-menu-search-target { 200 + width: 320px; 201 + margin-left: -150px; 202 + } 203 + 204 + .device-tablet .phabricator-main-menu-search-target, 205 + .device-phone .phabricator-main-menu-search-target { 206 + width: 100%; 207 + margin-left: -25px; 208 + 209 + } 210 + 192 211 .device-desktop .phabricator-main-menu-search-container { 193 212 margin: 0 8px 0 50px; 194 213 } ··· 236 255 top: 11px; 237 256 right: 6px; 238 257 } 258 + 259 + .phabricator-main-menu-search-target div.jx-typeahead-results { 260 + border-radius: 4px; 261 + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.35); 262 + border: 1px solid #33393d; 263 + } 264 + 265 + .phabricator-main-menu-search-target div.jx-typeahead-results a.jx-result { 266 + border: 0; 267 + } 268 + 269 + .phabricator-main-menu-search-target div.jx-typeahead-results a.focused, 270 + .phabricator-main-menu-search-target div.jx-typeahead-results a:hover { 271 + background: #3875d7; 272 + } 273 + 274 + .phabricator-main-search-typeahead-result { 275 + display: block; 276 + padding: 4px 4px 4px 38px; 277 + background-position: 4px 4px; 278 + background-size: 25px 25px; 279 + background-repeat: no-repeat; 280 + } 281 + 282 + .phabricator-main-search-typeahead-result .result-name { 283 + display: block; 284 + font-weight: bold; 285 + color: #444444; 286 + } 287 + 288 + .focused .phabricator-main-search-typeahead-result .result-name, 289 + a:hover .phabricator-main-search-typeahead-result .result-name { 290 + color: #eeeeee; 291 + } 292 + 293 + .phabricator-main-search-typeahead-result .result-type { 294 + color: #888888; 295 + } 296 + 297 + .focused .phabricator-main-search-typeahead-result .result-type, 298 + a:hover .phabricator-main-search-typeahead-result .result-type { 299 + color: #dddddd; 300 + } 301 + 239 302 240 303 241 304 /* - Collapsible ---------------------------------------------------------------
+52
webroot/rsrc/js/application/core/behavior-search-typeahead.js
··· 1 + /** 2 + * @provides javelin-behavior-phabricator-search-typeahead 3 + * @requires javelin-behavior 4 + * javelin-typeahead-ondemand-source 5 + * javelin-typeahead 6 + * javelin-dom 7 + * javelin-uri 8 + * javelin-stratcom 9 + */ 10 + 11 + JX.behavior('phabricator-search-typeahead', function(config) { 12 + 13 + var datasource = new JX.TypeaheadOnDemandSource(config.src); 14 + 15 + function transform(object) { 16 + var attr = { 17 + className: 'phabricator-main-search-typeahead-result' 18 + } 19 + 20 + if (object[6]) { 21 + attr.style = {backgroundImage: 'url('+object[6]+')'}; 22 + } 23 + 24 + var render = JX.$N( 25 + 'span', 26 + attr, 27 + [ 28 + JX.$N('span', {className: 'result-name'}, object[4] || object[0]), 29 + JX.$N('span', {className: 'result-type'}, object[5]) 30 + ]); 31 + 32 + return { 33 + name : object[0], 34 + display : render, 35 + uri : object[1], 36 + id : object[2] 37 + }; 38 + } 39 + 40 + datasource.setTransformer(transform); 41 + 42 + var typeahead = new JX.Typeahead(JX.$(config.id), JX.$(config.input)); 43 + typeahead.setDatasource(datasource); 44 + typeahead.setPlaceholder(config.placeholder); 45 + 46 + typeahead.listen('choose', function(r) { 47 + JX.$U(r.href).go(); 48 + JX.Stratcom.context().kill(); 49 + }); 50 + 51 + typeahead.start(); 52 + });