diff --git a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php index 826579333e..776f802447 100644 --- a/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php +++ b/src/applications/maniphest/controller/ManiphestTransactionPreviewController.php @@ -1,136 +1,135 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $comments = $request->getStr('comments'); $task = id(new ManiphestTaskQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$task) { return new Aphront404Response(); } id(new PhabricatorDraft()) ->setAuthorPHID($user->getPHID()) ->setDraftKey($task->getPHID()) ->setDraft($comments) ->replaceOrDelete(); $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setTransactionType($action); // This should really be split into a separate transaction, but it should // all come out in the wash once we fully move to modern stuff. $transaction->attachComment( id(new ManiphestTransactionComment()) ->setContent($comments)); $value = $request->getStr('value'); // grab phids for handles and set transaction values based on action and // value (empty or control-specific format) coming in from the wire switch ($action) { case ManiphestTransaction::TYPE_PRIORITY: $transaction->setOldValue($task->getPriority()); $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_OWNER: if ($value) { $value = current(json_decode($value)); $phids = array($value); } else { $phids = array(); } $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_CCS: if ($value) { $value = json_decode($value); } if (!$value) { $value = array(); } $phids = $value; foreach ($task->getCCPHIDs() as $cc_phid) { $phids[] = $cc_phid; $value[] = $cc_phid; } $transaction->setOldValue($task->getCCPHIDs()); $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_PROJECTS: if ($value) { $value = json_decode($value); } if (!$value) { $value = array(); } $phids = $value; foreach ($task->getProjectPHIDs() as $project_phid) { $phids[] = $project_phid; $value[] = $project_phid; } $transaction->setOldValue($task->getProjectPHIDs()); $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_STATUS: $phids = array(); - $transaction->setOldvalue($task->getStatus()); + $transaction->setOldValue($task->getStatus()); $transaction->setNewValue($value); break; default: $phids = array(); $transaction->setNewValue($value); break; } $phids[] = $user->getPHID(); $handles = $this->loadViewerHandles($phids); $transactions = array(); $transactions[] = $transaction; $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); if ($transaction->hasComment()) { $engine->addObject( $transaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } $engine->process(); $transaction->setHandles($handles); $view = id(new PhabricatorApplicationTransactionView()) ->setUser($user) ->setTransactions($transactions) - ->setIsPreview(true) - ->setIsDetailView(true); + ->setIsPreview(true); return id(new AphrontAjaxResponse()) ->setContent((string)phutil_implode_html('', $view->buildEvents())); } } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 568b2903b8..72b76714ba 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -1,675 +1,677 @@ getNewValue(); $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: if ($new) { $phids[] = $new; } if ($old) { $phids[] = $old; } break; case self::TYPE_CCS: case self::TYPE_PROJECTS: $phids = array_mergev( array( $phids, nonempty($old, array()), nonempty($new, array()), )); break; case self::TYPE_EDGE: $phids = array_mergev( array( $phids, array_keys(nonempty($old, array())), array_keys(nonempty($new, array())), )); break; case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $phids = array_mergev( array( $phids, array_keys(idx($new, 'FILE', array())), array_keys(idx($old, 'FILE', array())), )); break; } return $phids; } public function shouldHide() { switch ($this->getTransactionType()) { case self::TYPE_TITLE: case self::TYPE_DESCRIPTION: case self::TYPE_PRIORITY: if ($this->getOldValue() === null) { return true; } else { return false; } break; } return false; } public function getActionStrength() { switch ($this->getTransactionType()) { case self::TYPE_STATUS: return 1.3; case self::TYPE_OWNER: return 1.2; case self::TYPE_PRIORITY: return 1.1; } return parent::getActionStrength(); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: if ($this->getAuthorPHID() == $new) { return 'green'; } else if (!$new) { return 'black'; } else if (!$old) { return 'green'; } else { return 'green'; } case self::TYPE_STATUS: if ($new == ManiphestTaskStatus::STATUS_OPEN) { return 'green'; } else { return 'black'; } case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return 'green'; } else if ($old > $new) { return 'grey'; } else { return 'yellow'; } } return parent::getColor(); } public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht('Retitled'); case self::TYPE_STATUS: switch ($new) { case ManiphestTaskStatus::STATUS_OPEN: if ($old === null) { return pht('Created'); } else { return pht('Reopened'); } case ManiphestTaskStatus::STATUS_CLOSED_SPITE: return pht('Spited'); case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE: return pht('Merged'); default: return pht('Closed'); } case self::TYPE_DESCRIPTION: return pht('Edited'); case self::TYPE_OWNER: if ($this->getAuthorPHID() == $new) { return pht('Claimed'); } else if (!$new) { return pht('Up For Grabs'); } else if (!$old) { return pht('Assigned'); } else { return pht('Reassigned'); } case self::TYPE_CCS: return pht('Changed CC'); case self::TYPE_PROJECTS: return pht('Changed Projects'); case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht('Triaged'); } else if ($old > $new) { return pht('Lowered Priority'); } else { return pht('Raised Priority'); } case self::TYPE_EDGE: case self::TYPE_ATTACH: return pht('Attached'); } return parent::getActionName(); } public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: return 'user'; case self::TYPE_CCS: return 'meta-mta'; case self::TYPE_TITLE: return 'edit'; case self::TYPE_STATUS: switch ($new) { case ManiphestTaskStatus::STATUS_OPEN: return 'create'; case ManiphestTaskStatus::STATUS_CLOSED_SPITE: return 'dislike'; case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE: return 'delete'; default: return 'check'; } case self::TYPE_DESCRIPTION: return 'edit'; case self::TYPE_PROJECTS: return 'project'; case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return 'normal-priority'; return pht('Triaged'); } else if ($old > $new) { return 'lower-priority'; } else { return 'raise-priority'; } case self::TYPE_EDGE: case self::TYPE_ATTACH: return 'attach'; } return parent::getIcon(); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht( '%s changed the title from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); case self::TYPE_DESCRIPTION: return pht( '%s edited the task description.', $this->renderHandleLink($author_phid)); case self::TYPE_STATUS: switch ($new) { case ManiphestTaskStatus::STATUS_OPEN: if ($old === null) { return pht( '%s created this task.', $this->renderHandleLink($author_phid)); } else { return pht( '%s reopened this task.', $this->renderHandleLink($author_phid)); } case ManiphestTaskStatus::STATUS_CLOSED_SPITE: return pht( '%s closed this task out of spite.', $this->renderHandleLink($author_phid)); case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE: return pht( '%s closed this task as a duplicate.', $this->renderHandleLink($author_phid)); default: $status_name = idx( ManiphestTaskStatus::getTaskStatusMap(), $new, '???'); return pht( '%s closed this task as "%s".', $this->renderHandleLink($author_phid), $status_name); } case self::TYPE_OWNER: if ($author_phid == $new) { return pht( '%s claimed this task.', $this->renderHandleLink($author_phid)); } else if (!$new) { return pht( '%s placed this task up for grabs.', $this->renderHandleLink($author_phid)); } else if (!$old) { return pht( '%s assigned this task to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); } else { return pht( '%s reassigned this task from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); } case self::TYPE_PROJECTS: $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s added %d project(s): %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s removed %d project(s): %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleList($removed)); } else if ($removed && $added) { return pht( '%s changed project(s), added %d: %s; removed %d: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } else { // This is hit when rendering previews. return pht( '%s changed projects...', $this->renderHandleLink($author_phid)); } case self::TYPE_PRIORITY: $old_name = ManiphestTaskPriority::getTaskPriorityName($old); $new_name = ManiphestTaskPriority::getTaskPriorityName($new); if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht( '%s triaged this task as "%s" priority.', $this->renderHandleLink($author_phid), $new_name); } else if ($old > $new) { return pht( '%s lowered the priority of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $old_name, $new_name); } else { return pht( '%s raised the priority of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $old_name, $new_name); } case self::TYPE_CCS: // TODO: Remove this when we switch to subscribers. Just reuse the // code in the parent. $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); return $clone->getTitle(); case self::TYPE_EDGE: // TODO: Remove this when we switch to real edges. Just reuse the // code in the parent; $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE); return $clone->getTitle(); case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $new = array_keys(idx($new, 'FILE', array())); $old = array_keys(idx($old, 'FILE', array())); $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s attached %d file(s): %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s detached %d file(s): %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleList($removed)); } else { return pht( '%s changed file(s), attached %d: %s; detached %d: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } } return parent::getTitle(); } public function getTitleForFeed(PhabricatorFeedStory $story) { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht( '%s renamed %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); case self::TYPE_DESCRIPTION: return pht( '%s edited the description of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case self::TYPE_STATUS: switch ($new) { case ManiphestTaskStatus::STATUS_OPEN: if ($old === null) { return pht( '%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s reopened %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } case ManiphestTaskStatus::STATUS_CLOSED_SPITE: return pht( '%s closed %s out of spite.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE: return pht( '%s closed %s as a duplicate.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); default: $status_name = idx( ManiphestTaskStatus::getTaskStatusMap(), $new, '???'); return pht( '%s closed %s as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $status_name); } case self::TYPE_OWNER: if ($author_phid == $new) { return pht( '%s claimed %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else if (!$new) { return pht( '%s placed %s up for grabs.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else if (!$old) { return pht( '%s assigned %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($new)); } else { return pht( '%s reassigned %s from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); } case self::TYPE_PROJECTS: $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s added %d project(s) to %s: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleLink($object_phid), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s removed %d project(s) from %s: %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleLink($object_phid), $this->renderHandleList($removed)); } else if ($removed && $added) { return pht( '%s changed project(s) of %s, added %d: %s; removed %d: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } case self::TYPE_PRIORITY: $old_name = ManiphestTaskPriority::getTaskPriorityName($old); $new_name = ManiphestTaskPriority::getTaskPriorityName($new); if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht( '%s triaged %s as "%s" priority.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name); } else if ($old > $new) { return pht( '%s lowered the priority of %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name); } else { return pht( '%s raised the priority of %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name); } case self::TYPE_CCS: // TODO: Remove this when we switch to subscribers. Just reuse the // code in the parent. $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); return $clone->getTitleForFeed($story); case self::TYPE_EDGE: // TODO: Remove this when we switch to real edges. Just reuse the // code in the parent; $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE); return $clone->getTitleForFeed($story); case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $new = array_keys(idx($new, 'FILE', array())); $old = array_keys(idx($old, 'FILE', array())); $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s attached %d file(s) of %s: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s detached %d file(s) of %s: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($removed), $this->renderHandleList($removed)); } else { return pht( '%s changed file(s) for %s, attached %d: %s; detached %d: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } } return parent::getTitleForFeed($story); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return true; } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { $old = $this->getOldValue(); $new = $this->getNewValue(); + require_celerity_resource('differential-changeset-view-css'); + $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) ->setUser($viewer) ->setOldText($old) ->setNewText($new); return $view->render(); } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { case self::TYPE_STATUS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_STATUS; break; case self::TYPE_OWNER: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OWNER; break; case self::TYPE_CCS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_CC; break; case self::TYPE_PROJECTS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS; break; case self::TYPE_PRIORITY: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY; break; case PhabricatorTransactions::TYPE_COMMENT: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_COMMENT; break; default: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OTHER; break; } return $tags; } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php index 6ec85be402..f345866d20 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php @@ -1,33 +1,50 @@ phid = $data['phid']; } public function processRequest() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $xaction = id(new PhabricatorObjectQuery()) ->withPHIDs(array($this->phid)) - ->setViewer($user) + ->setViewer($viewer) ->executeOne(); - if (!$xaction) { - // future proofing for the day visibility of transactions can change return new Aphront404Response(); } - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($user) - ->setTransactions(array($xaction)) - ->setIsDetailView(true) - ->setAnchorOffset($request->getStr('anchor')); + $details = $xaction->renderChangeDetails($viewer); + + // Take an educated guess at the URI where the transactions appear so we + // can send the cancel button somewhere sensible. This won't always get the + // best answer (for example, Diffusion's history is visible on a page other + // than the main object view page) but should always get a reasonable one. + + $cancel_uri = '/'; + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($xaction->getObjectPHID())) + ->executeOne(); + if ($handle) { + $cancel_uri = $handle->getURI(); + } + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Change Details')) + ->setWidth(AphrontDialogView::WIDTH_FULL) + ->appendChild($details) + ->addCancelButton($cancel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php index b8c28d7c0c..44e5baa300 100644 --- a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php +++ b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php @@ -1,88 +1,81 @@ anchorOffset = $anchor_offset; return $this; } public function getAnchorOffset() { return $this->anchorOffset; } public function setTransactions($transactions) { assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); $this->transactions = $transactions; return $this; } public function getTransactions() { return $this->transactions; } public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } - public function setIsDetailView($is_detail_view) { - $this->isDetailView = $is_detail_view; - return $this; - } - public function reduceProxyResponse() { if ($this->getTransactions()) { $view = head($this->getTransactions()) ->getApplicationTransactionViewObject(); } else { $view = new PhabricatorApplicationTransactionView(); } $view ->setUser($this->getViewer()) ->setTransactions($this->getTransactions()) - ->setIsPreview($this->isPreview) - ->setIsDetailView($this->isDetailView); + ->setIsPreview($this->isPreview); if ($this->getAnchorOffset()) { $view->setAnchorOffset($this->getAnchorOffset()); } if ($this->isPreview) { $xactions = mpull($view->buildEvents(), 'render'); } else { $xactions = mpull($view->buildEvents(), 'render', 'getTransactionPHID'); } $content = array( 'xactions' => $xactions, 'spacer' => PHUITimelineView::renderSpacer(), ); return $this->getProxy()->setContent($content); } } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index 01f60ef4aa..d0692c6052 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -1,377 +1,316 @@ isDetailView = $is_detail_view; - return $this; - } public function setObjectPHID($object_phid) { $this->objectPHID = $object_phid; return $this; } public function getObjectPHID() { return $this->objectPHID; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } public function setShowEditActions($show_edit_actions) { $this->showEditActions = $show_edit_actions; return $this; } public function getShowEditActions() { return $this->showEditActions; } public function setAnchorOffset($anchor_offset) { $this->anchorOffset = $anchor_offset; return $this; } public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->engine = $engine; return $this; } public function setTransactions(array $transactions) { assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); $this->transactions = $transactions; return $this; } public function buildEvents() { $user = $this->getUser(); $anchor = $this->anchorOffset; $xactions = $this->transactions; $xactions = $this->filterHiddenTransactions($xactions); $xactions = $this->groupRelatedTransactions($xactions); $groups = $this->groupDisplayTransactions($xactions); $events = array(); foreach ($groups as $group) { $group_event = null; foreach ($group as $xaction) { $event = $this->renderEvent($xaction, $group, $anchor); $anchor++; if (!$group_event) { $group_event = $event; } else { $group_event->addEventToGroup($event); } } $events[] = $group_event; } return $events; } public function render() { if (!$this->getObjectPHID()) { throw new Exception("Call setObjectPHID() before render()!"); } $view = new PHUITimelineView(); $events = $this->buildEvents(); foreach ($events as $event) { $view->addEvent($event); } if ($this->getShowEditActions()) { $list_id = celerity_generate_unique_node_id(); $view->setID($list_id); Javelin::initBehavior( 'phabricator-transaction-list', array( 'listID' => $list_id, 'objectPHID' => $this->getObjectPHID(), 'nextAnchor' => $this->anchorOffset + count($events), )); } return $view->render(); } protected function getOrBuildEngine() { if ($this->engine) { return $this->engine; } $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); foreach ($this->transactions as $xaction) { if (!$xaction->hasComment()) { continue; } $engine->addObject($xaction->getComment(), $field); } $engine->process(); return $engine; } - private function buildChangeDetails( - PhabricatorApplicationTransaction $xaction) { - - Javelin::initBehavior('phabricator-reveal-content'); - - $show_id = celerity_generate_unique_node_id(); - $hide_id = celerity_generate_unique_node_id(); - $content_id = celerity_generate_unique_node_id(); - - $show_more = javelin_tag( - 'a', - array( - 'href' => '#', - 'sigil' => 'reveal-content', - 'mustcapture' => true, - 'id' => $show_id, - 'style' => 'display: none', - 'meta' => array( - 'hideIDs' => array($show_id), - 'showIDs' => array($hide_id, $content_id), - ), - ), - pht('(Show Details)')); - - $hide_more = javelin_tag( - 'a', - array( - 'href' => '#', - 'sigil' => 'reveal-content', - 'mustcapture' => true, - 'id' => $hide_id, - 'meta' => array( - 'hideIDs' => array($hide_id, $content_id), - 'showIDs' => array($show_id), - ), - ), - pht('(Hide Details)')); - - $content = phutil_tag( - 'div', - array( - 'id' => $content_id, - 'class' => 'phui-timeline-change-details', - ), - $xaction->renderChangeDetails($this->getUser())); - - return array( - $show_more, - $hide_more, - $content, - ); - } - private function buildChangeDetailsLink( PhabricatorApplicationTransaction $xaction) { return javelin_tag( 'a', array( 'href' => '/transactions/detail/'.$xaction->getPHID().'/', 'sigil' => 'transaction-detail', 'mustcapture' => true, 'meta' => array( 'anchor' => $this->anchorOffset, ), ), pht('(Show Details)')); } protected function shouldGroupTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { return false; } protected function renderTransactionContent( PhabricatorApplicationTransaction $xaction) { $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = $this->getOrBuildEngine(); $comment = $xaction->getComment(); if ($xaction->hasComment()) { if ($comment->getIsDeleted()) { return phutil_tag( 'em', array(), pht('This comment has been deleted.')); } else { return $engine->getOutput($comment, $field); } } return null; } private function filterHiddenTransactions(array $xactions) { foreach ($xactions as $key => $xaction) { if ($xaction->shouldHide()) { unset($xactions[$key]); } } return $xactions; } private function groupRelatedTransactions(array $xactions) { $last = null; $last_key = null; $groups = array(); foreach ($xactions as $key => $xaction) { if ($last && $this->shouldGroupTransactions($last, $xaction)) { $groups[$last_key][] = $xaction; unset($xactions[$key]); } else { $last = $xaction; $last_key = $key; } } foreach ($xactions as $key => $xaction) { $xaction->attachTransactionGroup(idx($groups, $key, array())); } return $xactions; } private function groupDisplayTransactions(array $xactions) { $groups = array(); $group = array(); foreach ($xactions as $xaction) { if ($xaction->shouldDisplayGroupWith($group)) { $group[] = $xaction; } else { if ($group) { $groups[] = $group; } $group = array($xaction); } } if ($group) { $groups[] = $group; } foreach ($groups as $key => $group) { $group = msort($group, 'getActionStrength'); $group = array_reverse($group); $groups[$key] = $group; } return $groups; } private function renderEvent( PhabricatorApplicationTransaction $xaction, array $group, $anchor) { $viewer = $this->getUser(); $event = id(new PHUITimelineEventView()) ->setUser($viewer) ->setTransactionPHID($xaction->getPHID()) ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) ->setIcon($xaction->getIcon()) ->setColor($xaction->getColor()); if (!$this->shouldSuppressTitle($xaction, $group)) { $title = $xaction->getTitle(); if ($xaction->hasChangeDetails()) { - if ($this->isPreview || $this->isDetailView) { - $details = $this->buildChangeDetails($xaction); - } else { + if (!$this->isPreview) { $details = $this->buildChangeDetailsLink($xaction); + $title = array( + $title, + ' ', + $details, + ); } - $title = array( - $title, - ' ', - $details, - ); } $event->setTitle($title); } if ($this->isPreview) { $event->setIsPreview(true); } else { $event ->setDateCreated($xaction->getDateCreated()) ->setContentSource($xaction->getContentSource()) ->setAnchor($anchor); } $has_deleted_comment = $xaction->getComment() && $xaction->getComment()->getIsDeleted(); if ($this->getShowEditActions() && !$this->isPreview) { if ($xaction->getCommentVersion() > 1) { $event->setIsEdited(true); } $can_edit = PhabricatorPolicyCapability::CAN_EDIT; if ($xaction->hasComment() || $has_deleted_comment) { $has_edit_capability = PhabricatorPolicyFilter::hasCapability( $viewer, $xaction, $can_edit); if ($has_edit_capability) { $event->setIsEditable(true); } } } $content = $this->renderTransactionContent($xaction); if ($content) { $event->appendChild($content); } return $event; } private function shouldSuppressTitle( PhabricatorApplicationTransaction $xaction, array $group) { // This is a little hard-coded, but we don't have any other reasonable // cases for now. Suppress "commented on" if there are other actions in // the display group. if (count($group) > 1) { $type_comment = PhabricatorTransactions::TYPE_COMMENT; if ($xaction->getTransactionType() == $type_comment) { return true; } } return false; } }