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

When a ProfileMenu has a link item that adds URI parameters, highlight it when clicked

Summary:
Depends on D20352. Fixes T12949. If a user adds a link (for example, to a workboard) that takes you to the same page but with some URI parameters, we'd prefer to highlight the "link" item instead of the default "workboard" item when you click it.

For example, you add a `/query/assigned/` "link" item to a workboard, called "Click This To Show Tasks Assigned To Me On This Workboard", i.e. filter the current view.

This is a pretty reasonable thing to want to do. When you click it, we'd like to highlight that item to show that you've activated the "Assigned to Me" filter you added.

However, we currently highlight the thing actually serving the content, i.e. the "Workboard" item.

Instead:

- When picking what to highlight, look through all the items for one with a link to the current request URI.
- If we find one or more, pick the one that would be the default.
- Otherwise, pick the first one.

This means that you can have several items like "?a=1", "?a=2", etc., and we will highlight them correctly when you click them.

This actual patch has some questionable bits (see some discussion in T13275), but I'd like to wait for stronger motivation to refactor it more extensively.

Test Plan:
- On a portal, added a `?a=1` link. Saw it highlight properly when clikced.
- On a workboard, added a link to the board itself with a different filter. Saw it highlight appropriately when clicked.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T12949

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

+125 -20
+5
src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php
··· 54 54 ->setProfileObject($favorites) 55 55 ->setCustomPHID($viewer->getPHID()); 56 56 57 + $controller = $this->getController(); 58 + if ($controller) { 59 + $menu_engine->setController($controller); 60 + } 61 + 57 62 $filter_view = $menu_engine->buildNavigation(); 58 63 59 64 $menu_view = $filter_view->getMenu();
+1
src/applications/home/controller/PhabricatorHomeController.php
··· 31 31 32 32 $engine = id(new PhabricatorHomeProfileMenuEngine()) 33 33 ->setViewer($viewer) 34 + ->setController($this) 34 35 ->setProfileObject($home) 35 36 ->setCustomPHID($viewer->getPHID()); 36 37
+1 -11
src/applications/project/controller/PhabricatorProjectBoardController.php
··· 1 1 <?php 2 2 3 3 abstract class PhabricatorProjectBoardController 4 - extends PhabricatorProjectController { 5 - 6 - protected function getProfileMenu() { 7 - $menu = parent::getProfileMenu(); 8 - 9 - $menu->selectFilter(PhabricatorProject::ITEM_WORKBOARD); 10 - $menu->addClass('project-board-nav'); 11 - 12 - return $menu; 13 - } 14 - } 4 + extends PhabricatorProjectController {}
+13 -3
src/applications/project/controller/PhabricatorProjectBoardViewController.php
··· 172 172 return $content; 173 173 } 174 174 175 - $nav = $this->getProfileMenu(); 176 - $nav->selectFilter(PhabricatorProject::ITEM_WORKBOARD); 175 + $nav = $this->newWorkboardProfileMenu(); 177 176 178 177 $crumbs = $this->buildApplicationCrumbs(); 179 178 $crumbs->addTextCrumb(pht('Workboard')); ··· 720 719 ->appendChild($board) 721 720 ->addClass('project-board-wrapper'); 722 721 723 - $nav = $this->getProfileMenu(); 722 + $nav = $this->newWorkboardProfileMenu(); 724 723 725 724 $divider = id(new PHUIListItemView()) 726 725 ->setType(PHUIListItemView::TYPE_DIVIDER); ··· 1502 1501 'to enable it. Only users who can edit this project can restore '. 1503 1502 'the workboard.')) 1504 1503 ->addCancelButton($profile_uri); 1504 + } 1505 + 1506 + private function newWorkboardProfileMenu() { 1507 + $default_item = id(new PhabricatorProfileMenuItemConfiguration()) 1508 + ->setBuiltinKey(PhabricatorProject::ITEM_WORKBOARD); 1509 + 1510 + $menu = parent::getProfileMenu($default_item); 1511 + 1512 + $menu->addClass('project-board-nav'); 1513 + 1514 + return $menu; 1505 1515 } 1506 1516 1507 1517 }
+2 -2
src/applications/project/controller/PhabricatorProjectController.php
··· 97 97 return $menu; 98 98 } 99 99 100 - protected function getProfileMenu() { 100 + protected function getProfileMenu($default_item = null) { 101 101 if (!$this->profileMenu) { 102 102 $engine = $this->getProfileMenuEngine(); 103 103 if ($engine) { 104 - $this->profileMenu = $engine->buildNavigation(); 104 + $this->profileMenu = $engine->buildNavigation($default_item); 105 105 } 106 106 } 107 107
+103 -4
src/applications/search/engine/PhabricatorProfileMenuEngine.php
··· 183 183 break; 184 184 } 185 185 186 - $navigation = $this->buildNavigation(); 186 + $navigation = $this->buildNavigation($selected_item); 187 187 188 188 $crumbs = $controller->buildApplicationCrumbsForEditEngine(); 189 189 ··· 223 223 switch ($item_action) { 224 224 case 'view': 225 225 if ($selected_item) { 226 - $navigation->selectFilter($selected_item->getDefaultMenuItemKey()); 227 - 228 226 try { 229 227 $content = $this->buildItemViewContent($selected_item); 230 228 } catch (Exception $ex) { ··· 335 333 return $page; 336 334 } 337 335 338 - public function buildNavigation() { 336 + public function buildNavigation( 337 + PhabricatorProfileMenuItemConfiguration $selected_item = null) { 338 + 339 339 if ($this->navigation) { 340 340 return $this->navigation; 341 341 } ··· 397 397 } 398 398 399 399 $nav->selectFilter(null); 400 + 401 + $navigation_items = $nav->getMenu()->getItems(); 402 + $select_key = $this->pickHighlightedMenuItem( 403 + $navigation_items, 404 + $selected_item); 405 + $nav->selectFilter($select_key); 400 406 401 407 $this->navigation = $nav; 402 408 return $this->navigation; ··· 1365 1371 } 1366 1372 1367 1373 return null; 1374 + } 1375 + 1376 + private function pickHighlightedMenuItem( 1377 + array $items, 1378 + PhabricatorProfileMenuItemConfiguration $selected_item = null) { 1379 + 1380 + assert_instances_of($items, 'PHUIListItemView'); 1381 + 1382 + $default_key = null; 1383 + if ($selected_item) { 1384 + $default_key = $selected_item->getDefaultMenuItemKey(); 1385 + } 1386 + 1387 + $controller = $this->getController(); 1388 + 1389 + // In some rare cases, when like building the "Favorites" menu on a 1390 + // 404 page, we may not have a controller. Just accept whatever default 1391 + // behavior we'd otherwise end up with. 1392 + if (!$controller) { 1393 + return $default_key; 1394 + } 1395 + 1396 + $request = $controller->getRequest(); 1397 + 1398 + // See T12949. If one of the menu items is a link to the same URI that 1399 + // the page was accessed with, we want to highlight that item. For example, 1400 + // this allows you to add links to a menu that apply filters to a 1401 + // workboard. 1402 + 1403 + $matches = array(); 1404 + foreach ($items as $item) { 1405 + $href = $item->getHref(); 1406 + if ($this->isMatchForRequestURI($request, $href)) { 1407 + $matches[] = $item; 1408 + } 1409 + } 1410 + 1411 + foreach ($matches as $match) { 1412 + if ($match->getKey() === $default_key) { 1413 + return $default_key; 1414 + } 1415 + } 1416 + 1417 + if ($matches) { 1418 + return head($matches)->getKey(); 1419 + } 1420 + 1421 + return $default_key; 1422 + } 1423 + 1424 + private function isMatchForRequestURI(AphrontRequest $request, $item_uri) { 1425 + $request_uri = $request->getAbsoluteRequestURI(); 1426 + $item_uri = new PhutilURI($item_uri); 1427 + 1428 + // If the request URI and item URI don't have matching paths, they 1429 + // do not match. 1430 + if ($request_uri->getPath() !== $item_uri->getPath()) { 1431 + return false; 1432 + } 1433 + 1434 + // If the request URI and item URI don't have matching parameters, they 1435 + // also do not match. We're specifically trying to let "?filter=X" work 1436 + // on Workboards, among other use cases, so this is important. 1437 + $request_params = $request_uri->getQueryParamsAsPairList(); 1438 + $item_params = $item_uri->getQueryParamsAsPairList(); 1439 + if ($request_params !== $item_params) { 1440 + return false; 1441 + } 1442 + 1443 + // If the paths and parameters match, the item domain must be: empty; or 1444 + // match the request domain; or match the production domain. 1445 + 1446 + $request_domain = $request_uri->getDomain(); 1447 + 1448 + $production_uri = PhabricatorEnv::getProductionURI('/'); 1449 + $production_domain = id(new PhutilURI($production_uri)) 1450 + ->getDomain(); 1451 + 1452 + $allowed_domains = array( 1453 + '', 1454 + $request_domain, 1455 + $production_domain, 1456 + ); 1457 + $allowed_domains = array_fuse($allowed_domains); 1458 + 1459 + $item_domain = $item_uri->getDomain(); 1460 + $item_domain = (string)$item_domain; 1461 + 1462 + if (isset($allowed_domains[$item_domain])) { 1463 + return true; 1464 + } 1465 + 1466 + return false; 1368 1467 } 1369 1468 1370 1469 }