diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3b03e33b30..6f40f55c33 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -607,6 +607,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php', 'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', + 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', 'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php', 'PhabricatorApplicationTransactions' => 'applications/transactions/application/PhabricatorApplicationTransactions.php', 'PhabricatorApplicationUIExamples' => 'applications/uiexample/application/PhabricatorApplicationUIExamples.php', @@ -1884,6 +1885,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor', 'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionView' => 'AphrontView', 'PhabricatorApplicationTransactions' => 'PhabricatorApplication', 'PhabricatorApplicationUIExamples' => 'PhabricatorApplication', diff --git a/src/applications/macro/controller/PhabricatorMacroCommentController.php b/src/applications/macro/controller/PhabricatorMacroCommentController.php index 241348fdec..a52e720fb7 100644 --- a/src/applications/macro/controller/PhabricatorMacroCommentController.php +++ b/src/applications/macro/controller/PhabricatorMacroCommentController.php @@ -42,8 +42,15 @@ final class PhabricatorMacroCommentController ))) ->applyTransactions($macro, $xactions); - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); + if ($request->isAjax()) { + return id(new PhabricatorApplicationTransactionResponse()) + ->setViewer($user) + ->setTransactions($xactions) + ->setAnchorOffset($request->getStr('anchor')); + } else { + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } } } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 82e54df157..30b417101a 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -82,6 +82,7 @@ final class PhabricatorMacroViewController $add_comment_form = id(new AphrontFormView()) ->setWorkflow(true) ->setFlexible(true) + ->addSigil('transaction-append') ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) ->setUser($user) ->appendChild( diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index 62a81906b1..950332af70 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -43,7 +43,8 @@ final class PholioMockCommentController extends PholioController { 'ip' => $request->getRemoteAddr(), )); - $xaction = id(new PholioTransaction()) + $xactions = array(); + $xactions[] = id(new PholioTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PholioTransactionComment()) @@ -52,9 +53,16 @@ final class PholioMockCommentController extends PholioController { id(new PholioMockEditor()) ->setActor($user) ->setContentSource($content_source) - ->applyTransactions($mock, array($xaction)); + ->applyTransactions($mock, $xactions); - return id(new AphrontRedirectResponse())->setURI($mock_uri); + if ($request->isAjax()) { + return id(new PhabricatorApplicationTransactionResponse()) + ->setViewer($user) + ->setTransactions($xactions) + ->setAnchorOffset($request->getStr('anchor')); + } else { + return id(new AphrontRedirectResponse())->setURI($mock_uri); + } } } diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index 5a1bd4bfd6..07b870b55c 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -47,7 +47,7 @@ final class PholioMockViewController extends PholioController { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), - PholioTransaction::MARKUP_FIELD_COMMENT); + PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); @@ -64,7 +64,10 @@ final class PholioMockViewController extends PholioController { '

'. 'Carousel Goes Here

'; - $xaction_view = $this->buildTransactionView($xactions, $engine); + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setViewer($this->getRequest()->getUser()) + ->setTransactions($xactions) + ->setMarkupEngine($engine); $add_comment = $this->buildAddCommentView($mock); @@ -171,6 +174,7 @@ final class PholioMockViewController extends PholioController { $form = id(new AphrontFormView()) ->setUser($user) + ->addSigil('transaction-append') ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/')) ->setWorkflow(true) ->setFlexible(true) @@ -189,42 +193,4 @@ final class PholioMockViewController extends PholioController { ); } - private function buildTransactionView( - array $xactions, - PhabricatorMarkupEngine $engine) { - assert_instances_of($xactions, 'PholioTransaction'); - - $view = new PhabricatorTimelineView(); - - $anchor_name = 0; - foreach ($xactions as $xaction) { - if ($xaction->shouldHide()) { - continue; - } - - $anchor_name++; - - $event = id(new PhabricatorTimelineEventView()) - ->setViewer($this->getRequest()->getUser()) - ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) - ->setIcon($xaction->getIcon()) - ->setColor($xaction->getColor()) - ->setTitle($xaction->getTitle()) - ->setDateCreated($xaction->getDateCreated()) - ->setContentSource($xaction->getContentSource()) - ->setAnchor($anchor_name); - - if ($xaction->getComment()) { - $event->appendChild( - $engine->getOutput( - $xaction->getComment(), - PholioTransaction::MARKUP_FIELD_COMMENT)); - } - - $view->addEvent($event); - } - - return $view; - } - } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php index b67c833b4c..f89e274b5c 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php @@ -58,22 +58,10 @@ final class PhabricatorApplicationTransactionCommentEditController ->applyEdit($xaction, $comment); if ($request->isAjax()) { - $view = id(new PhabricatorApplicationTransactionView()) + return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) - ->setTransactions(array($xaction)); - - $anchor = $request->getStr('anchor'); - if ($anchor) { - $view->setAnchorOffset($anchor); - } - - return id(new AphrontAjaxResponse())->setContent( - array( - 'xactions' => mpull( - $view->buildEvents(), - 'render', - 'getTransactionPHID'), - )); + ->setTransactions(array($xaction)) + ->setAnchorOffset($request->getStr('anchor')); } else { return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); } diff --git a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php new file mode 100644 index 0000000000..a0ff24345f --- /dev/null +++ b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php @@ -0,0 +1,65 @@ +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 buildResponseString() { + $view = id(new PhabricatorApplicationTransactionView()) + ->setViewer($this->getViewer()) + ->setTransactions($this->getTransactions()); + + if ($this->getAnchorOffset()) { + $view->setAnchorOffset($this->getAnchorOffset()); + } + + $xactions = mpull($view->buildEvents(), 'render', 'getTransactionPHID'); + + $content = array( + 'xactions' => $xactions, + 'spacer' => PhabricatorTimelineView::renderSpacer(), + ); + + return $this + ->getProxy() + ->setContent($content) + ->buildResponseString(); + } + +} diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index b58aad2b64..df4bfda588 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -67,7 +67,6 @@ class PhabricatorApplicationTransactionView extends AphrontView { $anchor++; - $has_deleted_comment = $xaction->getComment() && $xaction->getComment()->getIsDeleted(); @@ -105,7 +104,8 @@ class PhabricatorApplicationTransactionView extends AphrontView { public function render() { $view = new PhabricatorTimelineView(); - foreach ($this->buildEvents() as $event) { + $events = $this->buildEvents(); + foreach ($events as $event) { $view->addEvent($event); } @@ -117,7 +117,8 @@ class PhabricatorApplicationTransactionView extends AphrontView { Javelin::initBehavior( 'phabricator-transaction-list', array( - 'listID' => $list_id, + 'listID' => $list_id, + 'nextAnchor' => $this->anchorOffset + count($events), )); } diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index 0196b9c6ed..01a5e2d4e0 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -11,6 +11,7 @@ final class AphrontFormView extends AphrontView { private $workflow; private $id; private $flexible; + private $sigils = array(); public function setFlexible($flexible) { $this->flexible = $flexible; @@ -52,6 +53,11 @@ final class AphrontFormView extends AphrontView { return $this; } + public function addSigil($sigil) { + $this->sigils[] = $sigil; + return $this; + } + public function render() { if ($this->flexible) { require_celerity_resource('phabricator-form-view-css'); @@ -76,6 +82,11 @@ final class AphrontFormView extends AphrontView { throw new Exception('You must pass the user to AphrontFormView.'); } + $sigils = $this->sigils; + if ($this->workflow) { + $sigils[] = 'workflow'; + } + return phabricator_render_form( $this->user, array( @@ -83,7 +94,7 @@ final class AphrontFormView extends AphrontView { 'action' => $this->action, 'method' => $this->method, 'enctype' => $this->encType, - 'sigil' => $this->workflow ? 'workflow' : null, + 'sigil' => $sigils ? implode(' ', $sigils) : null, 'id' => $this->id, ), $layout->render()); diff --git a/src/view/layout/PhabricatorTimelineView.php b/src/view/layout/PhabricatorTimelineView.php index eae7f527d6..4def7593ea 100644 --- a/src/view/layout/PhabricatorTimelineView.php +++ b/src/view/layout/PhabricatorTimelineView.php @@ -18,13 +18,7 @@ final class PhabricatorTimelineView extends AphrontView { public function render() { require_celerity_resource('phabricator-timeline-view-css'); - $spacer = phutil_render_tag( - 'div', - array( - 'class' => 'phabricator-timeline-event-view '. - 'phabricator-timeline-spacer', - ), - ''); + $spacer = self::renderSpacer(); $events = array(); foreach ($this->events as $event) { @@ -42,4 +36,13 @@ final class PhabricatorTimelineView extends AphrontView { implode('', $events)); } + public static function renderSpacer() { + return phutil_render_tag( + 'div', + array( + 'class' => 'phabricator-timeline-event-view '. + 'phabricator-timeline-spacer', + ), + ''); + } } diff --git a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js index b683a5213e..481b39b8b5 100644 --- a/webroot/rsrc/js/application/transactions/behavior-transaction-list.js +++ b/webroot/rsrc/js/application/transactions/behavior-transaction-list.js @@ -4,12 +4,14 @@ * javelin-stratcom * javelin-workflow * javelin-dom + * javelin-fx */ JX.behavior('phabricator-transaction-list', function(config) { var list = JX.$(config.listID); var xaction_nodes = null; + var next_anchor = config.nextAnchor; function get_xaction_nodes() { if (xaction_nodes === null) { @@ -23,16 +25,41 @@ JX.behavior('phabricator-transaction-list', function(config) { } function ontransactions(response) { + var fade_in = []; + var first_new = null; + var nodes = get_xaction_nodes(); for (var phid in response.xactions) { var new_node = JX.$H(response.xactions[phid]).getFragment().firstChild; + fade_in.push(new_node); + if (nodes[phid]) { JX.DOM.replace(nodes[phid], new_node); } else { + if (first_new === null) { + first_new = new_node; + } list.appendChild(new_node); + + // Add a spacer after new transactions. + var spacer = JX.$H(response.spacer).getFragment().firstChild; + list.appendChild(spacer); + fade_in.push(spacer); + + next_anchor++; } nodes[phid] = new_node; } + + // Scroll to the first new transaction, if transactions were added. + if (first_new) { + JX.DOM.scrollTo(first_new); + } + + // Make any new or updated transactions fade in. + for (var ii = 0; ii < fade_in.length; ii++) { + new JX.FX(fade_in[ii]).setDuration(500).start({opacity: [0, 1]}); + } } JX.DOM.listen(list, 'click', 'transaction-edit', function(e) { @@ -48,4 +75,17 @@ JX.behavior('phabricator-transaction-list', function(config) { e.kill(); }); + JX.Stratcom.listen('submit', 'transaction-append', function(e) { + var form = e.getTarget(); + + JX.Workflow.newFromForm(form, {anchor: next_anchor}) + .setHandler(function(response) { + ontransactions(response); + form.reset(); + }) + .start(); + + e.kill(); + }); + });