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

On Dashboard tab panels in edit mode, make the "Tab Name" and the "Dropdown Edit Caret" into different links

Summary:
Ref T13272. In edit mode, tab panels now have a dropdown menu. However, this sort of overrlaps with the actual action of clicking the tab to select it.

Separate these into different click targets so that "select tab X" and "open dropdown menu for X" are different operations.

This is more work than it appears because:

- We have an "action icon" already, used when you put a dashboard on a portal/home to create an "Edit" link. It makes sense to attach dropdowns to this, but it has some hard-coded stuff.
- In applications with a "Create <thing>" in the crumbs (like Maniphest), we may use a dropdown menu if there are multiple create forms available. However, this menu renders in a weird way by reading all the properties out of an actual "View" object and building something else.
- The "list of tabs" stuff shares code with different "list of tabs" navigation used by Diffusion and Instances.

..but I think I fixed everything and didn't break anything.

Test Plan:
- Clicked "select tab" and "open dropdown menu" as separate actions.
- Viewed Diffusion, Maniphest with multiple create forms, Instances.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13272

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

+135 -113
+13 -13
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => '3c8a0668', 11 11 'conpherence.pkg.js' => '020aebcf', 12 - 'core.pkg.css' => 'dacb981b', 13 - 'core.pkg.js' => 'c783d8f6', 12 + 'core.pkg.css' => '9d654dff', 13 + 'core.pkg.js' => '350acda5', 14 14 'differential.pkg.css' => '8d8360fb', 15 15 'differential.pkg.js' => '67e02996', 16 16 'diffusion.pkg.css' => '42c75c37', ··· 134 134 'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e', 135 135 'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'f14f2422', 136 136 'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46', 137 - 'rsrc/css/phui/phui-action-list.css' => '8862282e', 137 + 'rsrc/css/phui/phui-action-list.css' => '48a45c51', 138 138 'rsrc/css/phui/phui-action-panel.css' => '6c386cbf', 139 139 'rsrc/css/phui/phui-badge.css' => '666e25ad', 140 140 'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d', ··· 164 164 'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4', 165 165 'rsrc/css/phui/phui-left-right.css' => '68513c34', 166 166 'rsrc/css/phui/phui-lightbox.css' => '4ebf22da', 167 - 'rsrc/css/phui/phui-list.css' => '470b1adb', 167 + 'rsrc/css/phui/phui-list.css' => '734a1039', 168 168 'rsrc/css/phui/phui-object-box.css' => 'f434b6be', 169 169 'rsrc/css/phui/phui-pager.css' => 'd022c7ad', 170 170 'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8', ··· 374 374 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '09ecf50c', 375 375 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092', 376 376 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 377 - 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '9b1cbd76', 377 + 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '8d4490a2', 378 378 'rsrc/js/application/diff/DiffChangeset.js' => 'd0a85a85', 379 379 'rsrc/js/application/diff/DiffChangesetList.js' => '04023d82', 380 380 'rsrc/js/application/diff/DiffInline.js' => 'a4a14a94', ··· 597 597 'javelin-behavior-dashboard-async-panel' => '09ecf50c', 598 598 'javelin-behavior-dashboard-move-panels' => '076bd092', 599 599 'javelin-behavior-dashboard-query-panel-select' => '1e413dc9', 600 - 'javelin-behavior-dashboard-tab-panel' => '9b1cbd76', 600 + 'javelin-behavior-dashboard-tab-panel' => '8d4490a2', 601 601 'javelin-behavior-day-view' => '727a5a61', 602 602 'javelin-behavior-desktop-notifications-control' => '070679fe', 603 603 'javelin-behavior-detect-timezone' => '78bc5d94', ··· 757 757 'path-typeahead' => 'ad486db3', 758 758 'people-picture-menu-item-css' => 'fe8e07cf', 759 759 'people-profile-css' => '2ea2daa1', 760 - 'phabricator-action-list-view-css' => '8862282e', 760 + 'phabricator-action-list-view-css' => '48a45c51', 761 761 'phabricator-busy' => '5202e831', 762 762 'phabricator-chatlog-css' => 'abdc76ee', 763 763 'phabricator-content-source-view-css' => 'cdf0d579', ··· 847 847 'phui-invisible-character-view-css' => 'c694c4a4', 848 848 'phui-left-right-css' => '68513c34', 849 849 'phui-lightbox-css' => '4ebf22da', 850 - 'phui-list-view-css' => '470b1adb', 850 + 'phui-list-view-css' => '734a1039', 851 851 'phui-object-box-css' => 'f434b6be', 852 852 'phui-oi-big-ui-css' => 'fa74cc35', 853 853 'phui-oi-color-css' => 'b517bfa0', ··· 1644 1644 'phabricator-shaped-request', 1645 1645 'conpherence-thread-manager', 1646 1646 ), 1647 + '8d4490a2' => array( 1648 + 'javelin-behavior', 1649 + 'javelin-dom', 1650 + 'javelin-stratcom', 1651 + ), 1647 1652 '8e0aa661' => array( 1648 1653 'javelin-install', 1649 1654 'javelin-dom', ··· 1735 1740 '9aae2b66' => array( 1736 1741 'javelin-install', 1737 1742 'javelin-util', 1738 - ), 1739 - '9b1cbd76' => array( 1740 - 'javelin-behavior', 1741 - 'javelin-dom', 1742 - 'javelin-stratcom', 1743 1743 ), 1744 1744 '9cec214e' => array( 1745 1745 'javelin-behavior',
+8 -51
src/applications/dashboard/controller/panel/PhabricatorDashboardPanelViewController.php
··· 19 19 return new Aphront404Response(); 20 20 } 21 21 22 + $can_edit = PhabricatorPolicyFilter::hasCapability( 23 + $viewer, 24 + $panel, 25 + PhabricatorPolicyCapability::CAN_EDIT); 26 + 22 27 $title = $panel->getMonogram().' '.$panel->getName(); 23 28 $crumbs = $this->buildApplicationCrumbs(); 24 29 $crumbs->addTextCrumb( ··· 29 34 30 35 $header = $this->buildHeaderView($panel); 31 36 $curtain = $this->buildCurtainView($panel); 32 - $properties = $this->buildPropertyView($panel); 33 37 34 38 $timeline = $this->buildTransactionTimeline( 35 39 $panel, ··· 40 44 ->setPanel($panel) 41 45 ->setPanelPHID($panel->getPHID()) 42 46 ->setParentPanelPHIDs(array()) 47 + ->setEditMode(true) 43 48 ->renderPanel(); 44 49 45 50 $preview = id(new PHUIBoxView()) ··· 50 55 ->setHeader($header) 51 56 ->setCurtain($curtain) 52 57 ->setMainColumn(array( 53 - $properties, 58 + $rendered_panel, 54 59 $timeline, 55 - )) 56 - ->setFooter($rendered_panel); 60 + )); 57 61 58 62 return $this->newPage() 59 63 ->setTitle($title) ··· 122 126 ->setWorkflow(true)); 123 127 124 128 return $curtain; 125 - } 126 - 127 - private function buildPropertyView(PhabricatorDashboardPanel $panel) { 128 - $viewer = $this->getViewer(); 129 - 130 - $properties = id(new PHUIPropertyListView()) 131 - ->setUser($viewer); 132 - 133 - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( 134 - $viewer, 135 - $panel); 136 - 137 - $panel_type = $panel->getImplementation(); 138 - if ($panel_type) { 139 - $type_name = $panel_type->getPanelTypeName(); 140 - } else { 141 - $type_name = phutil_tag( 142 - 'em', 143 - array(), 144 - nonempty($panel->getPanelType(), pht('null'))); 145 - } 146 - 147 - $properties->addProperty( 148 - pht('Panel Type'), 149 - $type_name); 150 - 151 - $properties->addProperty( 152 - pht('Editable By'), 153 - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); 154 - 155 - $dashboard_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 156 - $panel->getPHID(), 157 - PhabricatorDashboardPanelHasDashboardEdgeType::EDGECONST); 158 - 159 - $does_not_appear = pht( 160 - 'This panel does not appear on any dashboards.'); 161 - 162 - $properties->addProperty( 163 - pht('Appears On'), 164 - $dashboard_phids 165 - ? $viewer->renderHandleList($dashboard_phids) 166 - : phutil_tag('em', array(), $does_not_appear)); 167 - 168 - return id(new PHUIObjectBoxView()) 169 - ->setHeaderText(pht('Details')) 170 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 171 - ->addPropertyList($properties); 172 129 } 173 130 174 131 }
+7 -1
src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php
··· 15 15 private $dashboardID; 16 16 private $movable = true; 17 17 private $panelHandle; 18 + private $editMode; 18 19 19 20 public function setDashboardID($id) { 20 21 $this->dashboardID = $id; ··· 44 45 } 45 46 46 47 public function isEditMode() { 47 - return ($this->getHeaderMode() === self::HEADER_MODE_EDIT); 48 + return $this->editMode; 49 + } 50 + 51 + public function setEditMode($mode) { 52 + $this->editMode = $mode; 53 + return $this; 48 54 } 49 55 50 56 /**
+1
src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php
··· 76 76 ->setPanelPHID($panel_phid) 77 77 ->setParentPanelPHIDs(array()) 78 78 ->setHeaderMode($h_mode) 79 + ->setEditMode($is_editable) 79 80 ->setPanelHandle($handles[$panel_phid]); 80 81 81 82 $panel = idx($panels, $panel_phid);
+6 -2
src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php
··· 166 166 ->setHref($details_uri) 167 167 ->setDisabled(!$subpanel)); 168 168 169 - $tab_view->setDropdownMenu($dropdown_menu); 169 + $tab_view 170 + ->setActionIcon('fa-caret-down', '#') 171 + ->setDropdownMenu($dropdown_menu); 170 172 } 171 173 172 174 $list->addMenuItem($tab_view); ··· 193 195 $list->addMenuItem( 194 196 id(new PHUIListItemView()) 195 197 ->setHref('#') 198 + ->setDisabled(true) 196 199 ->setSelected(false) 197 - ->setName(pht('Add Tab...')) 200 + ->setName(pht("\xC2\xB7 \xC2\xB7 \xC2\xB7")) 201 + ->setActionIcon('fa-caret-down', '#') 198 202 ->setDropdownMenu($actions)); 199 203 } 200 204
+7 -1
src/view/phui/PHUICrumbsView.php
··· 50 50 51 51 $action_view = null; 52 52 if ($this->actions) { 53 + // TODO: This block of code takes "PHUIListItemView" objects and turns 54 + // them into some weird abomination by reading most of their properties 55 + // out. Some day, this workflow should render the items and CSS should 56 + // resytle them in place without needing a wholly separate set of 57 + // DOM nodes. 58 + 53 59 $actions = array(); 54 60 foreach ($this->actions as $action) { 55 - if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { 61 + if ($action->getType() == PHUIListItemView::TYPE_DIVIDER) { 56 62 $actions[] = phutil_tag( 57 63 'span', 58 64 array(
+55 -27
src/view/phui/PHUIListItemView.php
··· 35 35 private $actionIconHref; 36 36 private $count; 37 37 private $rel; 38 - private $hasDropdown; 38 + private $dropdownMenu; 39 39 40 40 public function setOpenInNewWindow($open_in_new_window) { 41 41 $this->openInNewWindow = $open_in_new_window; ··· 65 65 } 66 66 67 67 public function setDropdownMenu(PhabricatorActionListView $actions) { 68 - Javelin::initBehavior('phui-dropdown-menu'); 69 68 70 - $this->addSigil('phui-dropdown-menu'); 71 - $this->setMetadata($actions->getDropdownMenuMetadata()); 72 - $this->hasDropdown = true; 69 + $this->dropdownMenu = $actions; 70 + 71 + // TODO: "PHUICrumbsView" currently creates a bad copy of list items 72 + // by reading some of their properties. To survive this copy step, we 73 + // need to mutate "$this" immediately or the "Create Object" dropdown 74 + // when multiple create forms exist breaks. 75 + 76 + if (!$this->actionIcon) { 77 + Javelin::initBehavior('phui-dropdown-menu'); 78 + $this->addSigil('phui-dropdown-menu'); 79 + $this->setMetadata($actions->getDropdownMenuMetadata()); 80 + } 73 81 74 82 return $this; 75 83 } ··· 237 245 $classes[] = 'phui-list-item-has-action-icon'; 238 246 } 239 247 240 - if ($this->hasDropdown) { 248 + if ($this->dropdownMenu) { 241 249 $classes[] = 'dropdown'; 250 + if (!$this->actionIcon) { 251 + throw new Exception( 252 + pht( 253 + 'List item views can not currently render a dropdown without '. 254 + 'an action icon, because no application uses one. Clean up '. 255 + 'PHUICrumbsView, then add this capability.')); 256 + } 242 257 } 243 258 244 259 return array( 245 - 'class' => implode(' ', $classes), 260 + 'class' => $classes, 246 261 ); 247 262 } 248 263 ··· 345 360 $classes[] = 'phui-list-item-indented'; 346 361 } 347 362 348 - $action_link = null; 349 - if ($this->actionIcon) { 350 - $action_icon = id(new PHUIIconView()) 351 - ->setIcon($this->actionIcon) 352 - ->addClass('phui-list-item-action-icon'); 353 - $action_link = phutil_tag( 354 - 'a', 355 - array( 356 - 'href' => $this->actionIconHref, 357 - 'class' => 'phui-list-item-action-href', 358 - ), 359 - $action_icon); 360 - } 363 + $action_link = $this->newActionIconView(); 361 364 362 365 $count = null; 363 366 if ($this->count) { ··· 369 372 $this->count); 370 373 } 371 374 372 - if ($this->hasDropdown) { 373 - $caret = phutil_tag('span', array('class' => 'caret'), ''); 374 - } else { 375 - $caret = null; 376 - } 377 - 378 375 $icons = $this->getIcons(); 379 376 380 377 $list_item = javelin_tag( ··· 393 390 $icons, 394 391 $this->renderChildren(), 395 392 $name, 396 - $caret, 397 393 $count, 398 394 )); 399 395 400 396 return array($list_item, $action_link); 397 + } 398 + 399 + private function newActionIconView() { 400 + $action_icon = $this->actionIcon; 401 + $action_href = $this->actionIconHref; 402 + 403 + if ($action_icon === null) { 404 + return null; 405 + } 406 + 407 + $icon_view = id(new PHUIIconView()) 408 + ->setIcon($action_icon) 409 + ->addClass('phui-list-item-action-icon'); 410 + 411 + if ($this->dropdownMenu) { 412 + Javelin::initBehavior('phui-dropdown-menu'); 413 + $sigil = 'phui-dropdown-menu'; 414 + $metadata = $this->dropdownMenu->getDropdownMenuMetadata(); 415 + } else { 416 + $sigil = null; 417 + $metadata = null; 418 + } 419 + 420 + return javelin_tag( 421 + 'a', 422 + array( 423 + 'href' => $action_href, 424 + 'class' => 'phui-list-item-action-href', 425 + 'sigil' => $sigil, 426 + 'meta' => $metadata, 427 + ), 428 + $icon_view); 401 429 } 402 430 403 431 }
+20 -7
webroot/rsrc/css/phui/phui-action-list.css
··· 214 214 color: {$sky}; 215 215 } 216 216 217 - .phui-list-navbar .phui-list-item-view.dropdown .phui-list-item-href { 218 - padding-right: 28px; 217 + .phui-list-navbar .phui-list-item-href { 218 + display: inline-block; 219 + } 220 + 221 + .phui-list-navbar .phui-list-item-disabled .phui-list-item-href { 222 + color: {$lightgreytext}; 223 + } 224 + 225 + .phui-list-navbar .phui-list-item-action-href { 226 + display: inline-block; 227 + padding: 8px 16px; 228 + line-height: 16px; 229 + } 230 + 231 + .phui-list-navbar .phui-list-item-action-href .phui-icon-view { 232 + color: {$darkgreytext}; 219 233 } 220 234 221 - .phui-list-navbar .phui-list-item-view .caret { 222 - position: absolute; 223 - top: 6px; 224 - right: 12px; 225 - border-top: 7px solid {$greytext}; 235 + .device-desktop 236 + .phui-list-navbar .phui-list-item-action-href:hover { 237 + background-color: rgba({$alphablue}, 0.07); 238 + color: {$sky}; 226 239 }
+10 -11
webroot/rsrc/css/phui/phui-list.css
··· 110 110 border-right: 1px solid {$thinblueborder}; 111 111 } 112 112 113 - .phui-list-view.phui-list-navbar > li > * { 114 - display: block; 115 - } 116 - 117 113 .phui-list-navbar .phui-list-item-href { 118 114 color: {$bluetext}; 119 115 padding: 8px 16px; ··· 265 261 266 262 /* - Action Icon ----------------------------------------------------------- */ 267 263 268 - .phui-list-item-has-action-icon .phui-list-item-action-href { 264 + .phui-list-sidenav .phui-list-item-has-action-icon .phui-list-item-action-href { 269 265 position: absolute; 270 266 width: 28px; 271 267 top: 0; ··· 277 273 display: none; 278 274 } 279 275 280 - .phui-list-item-has-action-icon.phui-list-item-selected .phui-list-item-href { 276 + .phui-list-sidenav .phui-list-item-has-action-icon.phui-list-item-selected 277 + .phui-list-item-href { 281 278 padding-right: 32px; 282 279 } 283 280 284 - .phui-list-item-has-action-icon.phui-list-item-selected 281 + .phui-list-sidenav .phui-list-item-has-action-icon.phui-list-item-selected 285 282 .phui-list-item-action-href { 286 283 display: block; 287 284 } 288 285 289 - .phui-list-item-has-action-icon .phui-list-item-action-href:hover { 286 + .phui-list-sidenav .phui-list-item-has-action-icon 287 + .phui-list-item-action-href:hover { 290 288 background-color: rgba({$alphablack},.05); 291 289 } 292 290 293 - .phui-list-item-has-action-icon .phui-list-item-action-icon { 291 + .phui-list-sidenav .phui-list-item-has-action-icon .phui-list-item-action-icon { 294 292 opacity: 0.5; 295 293 } 296 294 297 - .phui-list-item-has-action-icon .phui-list-item-action-href:hover 295 + .phui-list-sidenav .phui-list-item-has-action-icon 296 + .phui-list-item-action-href:hover 298 297 .phui-list-item-action-icon { 299 - opacity: 1; 298 + opacity: 1; 300 299 } 301 300 302 301 /* - Item Counts ----------------------------------------------------------- */
+8
webroot/rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js
··· 8 8 JX.behavior('dashboard-tab-panel', function() { 9 9 10 10 JX.Stratcom.listen('click', 'dashboard-tab-panel-tab', function(e) { 11 + // On dashboard panels in edit mode, the user may click the dropdown caret 12 + // within a tab to open the context menu. If they do, this click should 13 + // just open the menu, not select the tab. For now, pass the event here 14 + // to let the menu handler act on it. 15 + if (JX.Stratcom.pass(e)) { 16 + return; 17 + } 18 + 11 19 e.kill(); 12 20 13 21 var ii;