Page MenuHomePhabricator

D16157.id.diff
No OneTemporary

D16157.id.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,8 +7,8 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => 'b9e2e1e5',
- 'core.pkg.js' => '80f86a0a',
+ 'core.pkg.css' => 'f577cd20',
+ 'core.pkg.js' => 'f2139810',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'b3eea3f5',
'differential.pkg.js' => '4b7d8f19',
@@ -124,7 +124,7 @@
'rsrc/css/phui/phui-badge.css' => '3baef8db',
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
'rsrc/css/phui/phui-box.css' => '5c8387cf',
- 'rsrc/css/phui/phui-button.css' => 'a64a8de6',
+ 'rsrc/css/phui/phui-button.css' => 'e266e0bc',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
@@ -516,14 +516,15 @@
'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d',
'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
'rsrc/js/core/phtize.js' => 'd254d646',
- 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475',
+ 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '1aa4c968',
'rsrc/js/phui/behavior-phui-file-upload.js' => 'b003d4fb',
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
'rsrc/js/phui/behavior-phui-profile-menu.js' => '12884df9',
+ 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06',
- 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
+ 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da',
'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
@@ -669,11 +670,12 @@
'javelin-behavior-phabricator-watch-anchor' => '9f36c42d',
'javelin-behavior-pholio-mock-edit' => 'bee502c8',
'javelin-behavior-pholio-mock-view' => 'fbe497e7',
- 'javelin-behavior-phui-dropdown-menu' => '54733475',
+ 'javelin-behavior-phui-dropdown-menu' => '1aa4c968',
'javelin-behavior-phui-file-upload' => 'b003d4fb',
'javelin-behavior-phui-hovercards' => 'bcaccd64',
'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
'javelin-behavior-phui-profile-menu' => '12884df9',
+ 'javelin-behavior-phui-submenu' => 'a6f7a73b',
'javelin-behavior-policy-control' => 'd0c516d5',
'javelin-behavior-policy-rule-editor' => '5e9f347c',
'javelin-behavior-project-boards' => '14a1faae',
@@ -822,7 +824,7 @@
'phui-badge-view-css' => '3baef8db',
'phui-big-info-view-css' => 'bd903741',
'phui-box-css' => '5c8387cf',
- 'phui-button-css' => 'a64a8de6',
+ 'phui-button-css' => 'e266e0bc',
'phui-calendar-css' => 'ccabe893',
'phui-calendar-day-css' => 'd1cf6f93',
'phui-calendar-list-css' => '56e6381a',
@@ -870,7 +872,7 @@
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '9196fb06',
- 'phuix-dropdown-menu' => 'bd4c8dca',
+ 'phuix-dropdown-menu' => '82e270da',
'phuix-form-control-view' => 'e15869a8',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
@@ -1024,6 +1026,12 @@
'javelin-workflow',
'javelin-workboard-controller',
),
+ '1aa4c968' => array(
+ 'javelin-behavior',
+ 'javelin-stratcom',
+ 'javelin-dom',
+ 'phuix-dropdown-menu',
+ ),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
@@ -1275,12 +1283,6 @@
'javelin-leader',
'javelin-json',
),
- 54733475 => array(
- 'javelin-behavior',
- 'javelin-stratcom',
- 'javelin-dom',
- 'phuix-dropdown-menu',
- ),
'54b612ba' => array(
'javelin-color',
'javelin-install',
@@ -1529,6 +1531,13 @@
'javelin-vector',
'javelin-stratcom',
),
+ '82e270da' => array(
+ 'javelin-install',
+ 'javelin-util',
+ 'javelin-dom',
+ 'javelin-vector',
+ 'javelin-stratcom',
+ ),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
@@ -1693,6 +1702,11 @@
'javelin-uri',
'phabricator-notification',
),
+ 'a6f7a73b' => array(
+ 'javelin-behavior',
+ 'javelin-stratcom',
+ 'javelin-dom',
+ ),
'a80d0378' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -1846,13 +1860,6 @@
'javelin-vector',
'phui-hovercard',
),
- 'bd4c8dca' => array(
- 'javelin-install',
- 'javelin-util',
- 'javelin-dom',
- 'javelin-vector',
- 'javelin-stratcom',
- ),
'bdaf4d04' => array(
'javelin-behavior',
'javelin-dom',
diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php
--- a/src/applications/base/controller/PhabricatorController.php
+++ b/src/applications/base/controller/PhabricatorController.php
@@ -474,8 +474,11 @@
public function newCurtainView($object) {
$viewer = $this->getViewer();
+ $action_id = celerity_generate_unique_node_id();
+
$action_list = id(new PhabricatorActionListView())
- ->setViewer($viewer);
+ ->setViewer($viewer)
+ ->setID($action_id);
// NOTE: Applications (objects of class PhabricatorApplication) can't
// currently be set here, although they don't need any of the extensions
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -166,15 +166,6 @@
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Merge Duplicates In'))
- ->setHref("/search/attach/{$phid}/TASK/merge/")
- ->setWorkflow(true)
- ->setIcon('fa-compress')
- ->setDisabled(!$can_edit)
- ->setWorkflow(true));
-
$edit_config = $edit_engine->loadDefaultEditConfiguration();
$can_create = (bool)$edit_config;
@@ -195,23 +186,36 @@
$edit_uri = $this->getApplicationURI($edit_uri);
}
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Create Subtask'))
- ->setHref($edit_uri)
- ->setIcon('fa-level-down')
- ->setDisabled(!$can_create)
- ->setWorkflow(!$can_create));
+ $task_submenu = array();
+
+ $task_submenu[] = id(new PhabricatorActionView())
+ ->setName(pht('Create Subtask'))
+ ->setHref($edit_uri)
+ ->setIcon('fa-level-down')
+ ->setDisabled(!$can_create)
+ ->setWorkflow(!$can_create);
+
+ $task_submenu[] = id(new PhabricatorActionView())
+ ->setName(pht('Edit Blocking Tasks'))
+ ->setHref("/search/attach/{$phid}/TASK/blocks/")
+ ->setWorkflow(true)
+ ->setIcon('fa-link')
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(true);
+
+ $task_submenu[] = id(new PhabricatorActionView())
+ ->setName(pht('Merge Duplicates In'))
+ ->setHref("/search/attach/{$phid}/TASK/merge/")
+ ->setWorkflow(true)
+ ->setIcon('fa-compress')
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(true);
$curtain->addAction(
id(new PhabricatorActionView())
- ->setName(pht('Edit Blocking Tasks'))
- ->setHref("/search/attach/{$phid}/TASK/blocks/")
- ->setWorkflow(true)
- ->setIcon('fa-link')
- ->setDisabled(!$can_edit)
- ->setWorkflow(true));
-
+ ->setName(pht('Edit Related Tasks...'))
+ ->setIcon('fa-anchor')
+ ->setSubmenu($task_submenu));
$owner_phid = $task->getOwnerPHID();
$author_phid = $task->getAuthorPHID();
diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php
--- a/src/view/layout/PhabricatorActionListView.php
+++ b/src/view/layout/PhabricatorActionListView.php
@@ -21,6 +21,10 @@
return $this;
}
+ public function getID() {
+ return $this->id;
+ }
+
public function render() {
$viewer = $this->getViewer();
@@ -44,13 +48,20 @@
require_celerity_resource('phabricator-action-list-view-css');
+ $items = array();
+ foreach ($actions as $action) {
+ foreach ($action->getItems() as $item) {
+ $items[] = $item;
+ }
+ }
+
return phutil_tag(
'ul',
array(
'class' => 'phabricator-action-list-view',
'id' => $this->id,
),
- $actions);
+ $items);
}
public function getDropdownMenuMetadata() {
diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php
--- a/src/view/layout/PhabricatorActionView.php
+++ b/src/view/layout/PhabricatorActionView.php
@@ -14,6 +14,10 @@
private $metadata;
private $selected;
private $openInNewWindow;
+ private $submenu = array();
+ private $hidden;
+ private $depth;
+ private $id;
public function setSelected($selected) {
$this->selected = $selected;
@@ -95,7 +99,60 @@
return $this->openInNewWindow;
}
+ public function getID() {
+ if (!$this->id) {
+ $this->id = celerity_generate_unique_node_id();
+ }
+ return $this->id;
+ }
+
+ public function setSubmenu(array $submenu) {
+ $this->submenu = $submenu;
+
+ if (!$this->getHref()) {
+ $this->setHref('#');
+ }
+
+ return $this;
+ }
+
+ public function getItems($depth = 0) {
+ $items = array();
+
+ $items[] = $this;
+ foreach ($this->submenu as $action) {
+ foreach ($action->getItems($depth + 1) as $item) {
+ $item
+ ->setHidden(true)
+ ->setDepth($depth + 1);
+
+ $items[] = $item;
+ }
+ }
+
+ return $items;
+ }
+
+ public function setHidden($hidden) {
+ $this->hidden = $hidden;
+ return $this;
+ }
+
+ public function getHidden() {
+ return $this->hidden;
+ }
+
+ public function setDepth($depth) {
+ $this->depth = $depth;
+ return $this;
+ }
+
+ public function getDepth() {
+ return $this->depth;
+ }
+
public function render() {
+ $caret_id = celerity_generate_unique_node_id();
$icon = null;
if ($this->icon) {
@@ -155,6 +212,18 @@
$target = null;
}
+ if ($this->submenu) {
+ $caret = javelin_tag(
+ 'span',
+ array(
+ 'class' => 'caret-right',
+ 'id' => $caret_id,
+ ),
+ '');
+ } else {
+ $caret = null;
+ }
+
$item = javelin_tag(
'a',
array(
@@ -164,7 +233,7 @@
'sigil' => $sigils,
'meta' => $this->metadata,
),
- array($icon, $this->name));
+ array($icon, $this->name, $caret));
}
} else {
$item = phutil_tag(
@@ -190,10 +259,47 @@
$classes[] = 'phabricator-action-view-selected';
}
- return phutil_tag(
+ if ($this->submenu) {
+ $classes[] = 'phabricator-action-view-submenu';
+ }
+
+ $style = array();
+
+ if ($this->hidden) {
+ $style[] = 'display: none;';
+ }
+
+ if ($this->depth) {
+ $indent = ($this->depth * 16);
+ $style[] = "margin-left: {$indent}px;";
+ }
+
+ $sigil = null;
+ $meta = null;
+
+ if ($this->submenu) {
+ Javelin::initBehavior('phui-submenu');
+ $sigil = 'phui-submenu';
+
+ $item_ids = array();
+ foreach ($this->submenu as $subitem) {
+ $item_ids[] = $subitem->getID();
+ }
+
+ $meta = array(
+ 'itemIDs' => $item_ids,
+ 'caretID' => $caret_id,
+ );
+ }
+
+ return javelin_tag(
'li',
array(
+ 'id' => $this->getID(),
'class' => implode(' ', $classes),
+ 'style' => implode(' ', $style),
+ 'sigil' => $sigil,
+ 'meta' => $meta,
),
$item);
}
diff --git a/src/view/phui/PHUIButtonView.php b/src/view/phui/PHUIButtonView.php
--- a/src/view/phui/PHUIButtonView.php
+++ b/src/view/phui/PHUIButtonView.php
@@ -110,6 +110,18 @@
return $this;
}
+ public function setDropdownMenuID($id) {
+ Javelin::initBehavior('phui-dropdown-menu');
+
+ $this->addSigil('phui-dropdown-menu');
+ $this->setMetadata(
+ array(
+ 'menuID' => $id,
+ ));
+
+ return $this;
+ }
+
protected function getTagAttributes() {
require_celerity_resource('phui-button-css');
diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php
--- a/src/view/phui/PHUIHeaderView.php
+++ b/src/view/phui/PHUIHeaderView.php
@@ -24,6 +24,7 @@
private $badges = array();
private $href;
private $actionList;
+ private $actionListID;
public function setHeader($header) {
$this->header = $header;
@@ -90,6 +91,11 @@
return $this;
}
+ public function setActionListID($action_list_id) {
+ $this->actionListID = $action_list_id;
+ return $this;
+ }
+
public function setPolicyObject(PhabricatorPolicyInterface $object) {
$this->policyObject = $object;
return $this;
@@ -189,14 +195,20 @@
protected function getTagContent() {
- if ($this->actionList) {
+ if ($this->actionList || $this->actionListID) {
$action_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('Actions'))
->setHref('#')
->setIcon('fa-bars')
- ->addClass('phui-mobile-menu')
- ->setDropdownMenu($this->actionList);
+ ->addClass('phui-mobile-menu');
+
+ if ($this->actionList) {
+ $action_button->setDropdownMenu($this->actionList);
+ } else if ($this->actionListID) {
+ $action_button->setDropdownMenuID($this->actionListID);
+ }
+
$this->addActionLink($action_button);
}
diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php
--- a/src/view/phui/PHUITimelineEventView.php
+++ b/src/view/phui/PHUITimelineEventView.php
@@ -327,9 +327,7 @@
'sigil' => $sigil,
'aria-haspopup' => 'true',
'aria-expanded' => 'false',
- 'meta' => array(
- 'items' => hsprintf('%s', $action_list),
- ),
+ 'meta' => $action_list->getDropdownMenuMetadata(),
),
array(
$aural,
diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php
--- a/src/view/phui/PHUITwoColumnView.php
+++ b/src/view/phui/PHUITwoColumnView.php
@@ -114,7 +114,7 @@
$curtain = $this->getCurtain();
if ($curtain) {
$action_list = $curtain->getActionList();
- $this->header->setActionList($action_list);
+ $this->header->setActionListID($action_list->getID());
}
$header = phutil_tag_div(
diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css
--- a/webroot/rsrc/css/phui/phui-button.css
+++ b/webroot/rsrc/css/phui/phui-button.css
@@ -264,6 +264,42 @@
border-top-color: #000;
}
+.phabricator-action-view-submenu .caret-right {
+ float: right;
+ margin-top: 4px;
+ margin-right: 6px;
+ border-left-color: {$lightgreytext};
+}
+
+.phabricator-action-view-submenu .caret {
+ float: right;
+ margin-top: 5px;
+ margin-right: 4px;
+ border-top: 7px solid {$lightgreytext};
+}
+
+.phabricator-action-view-submenu.phui-submenu-open {
+ background: {$greybackground};
+}
+
+.phui-submenu-animate {
+ animation: phui-submenu-summon 0.25s;
+}
+
+@keyframes phui-submenu-summon {
+ 0% {
+ color: {$lightgreytext};
+ margin-left: 0;
+ transform: rotate(12deg);
+ }
+ 60% {
+ margin-left: 24px;
+ transform: rotate(-5deg);
+ margin-top: 18px;
+ }
+}
+
+
/* Icons */
.button.has-icon {
position: relative;
diff --git a/webroot/rsrc/js/phui/behavior-phui-dropdown-menu.js b/webroot/rsrc/js/phui/behavior-phui-dropdown-menu.js
--- a/webroot/rsrc/js/phui/behavior-phui-dropdown-menu.js
+++ b/webroot/rsrc/js/phui/behavior-phui-dropdown-menu.js
@@ -16,17 +16,42 @@
e.kill();
- var list = JX.$H(data.items).getFragment().firstChild;
+ var list;
+ var placeholder;
+ if (data.items) {
+ list = JX.$H(data.items).getFragment().firstChild;
+ } else {
+ list = JX.$(data.menuID);
+ placeholder = JX.$N('span');
+ }
var icon = e.getNode('phui-dropdown-menu');
data.menu = new JX.PHUIXDropdownMenu(icon);
- data.menu.setContent(list);
+
+ data.menu.listen('open', function() {
+ if (placeholder) {
+ JX.DOM.replace(list, placeholder);
+ }
+ data.menu.setContent(list);
+ });
+
+ data.menu.listen('close', function() {
+ if (placeholder) {
+ JX.DOM.replace(placeholder, list);
+ }
+ });
+
data.menu.open();
JX.DOM.listen(list, 'click', 'tag:a', function(e) {
if (!e.isNormalClick()) {
return;
}
+
+ if (JX.Stratcom.pass()) {
+ return;
+ }
+
data.menu.close();
});
});
diff --git a/webroot/rsrc/js/phui/behavior-phui-submenu.js b/webroot/rsrc/js/phui/behavior-phui-submenu.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/phui/behavior-phui-submenu.js
@@ -0,0 +1,44 @@
+/**
+ * @provides javelin-behavior-phui-submenu
+ * @requires javelin-behavior
+ * javelin-stratcom
+ * javelin-dom
+ */
+
+JX.behavior('phui-submenu', function() {
+
+ JX.Stratcom.listen('click', 'phui-submenu', function(e) {
+ if (!e.isNormalClick()) {
+ return;
+ }
+
+ var node = e.getNode('phui-submenu');
+ var data = e.getNodeData('phui-submenu');
+
+ e.kill();
+
+ data.open = !data.open;
+
+ for (var ii = 0; ii < data.itemIDs.length; ii++) {
+ var id = data.itemIDs[ii];
+ var item = JX.$(id);
+ if (data.open) {
+ JX.DOM.show(item);
+ } else {
+ JX.DOM.hide(item);
+ }
+
+ // Add a class so we can animate zany effects.
+ JX.DOM.alterClass(item, 'phui-submenu-animate', data.open);
+ }
+
+ JX.DOM.alterClass(node, 'phui-submenu-open', data.open);
+
+ // Toggle the caret from ">" to "V" when opening the menu, and back again
+ // when closing it.
+ var caret = JX.$(data.caretID);
+ JX.DOM.alterClass(caret, 'caret', data.open);
+ JX.DOM.alterClass(caret, 'caret-right', !data.open);
+ });
+
+});
diff --git a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js
--- a/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js
+++ b/webroot/rsrc/js/phuix/PHUIXDropdownMenu.js
@@ -42,7 +42,7 @@
JX.Stratcom.listen('keydown', null, JX.bind(this, this._onkey));
},
- events: ['open'],
+ events: ['open', 'close'],
properties: {
width: null,
@@ -83,6 +83,8 @@
this._open = false;
this._hide();
+ this.invoke('close');
+
return this;
},

File Metadata

Mime Type
text/plain
Expires
Sun, Oct 20, 4:47 AM (4 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6735161
Default Alt Text
D16157.id.diff (18 KB)

Event Timeline