Add previews to ApplicationTransaction
Summary:
Implements previews for Macros and Pholio.
(Design is nonfinal -- kind of split the difference between `diff_full_view.png`, laziness, and space concerns. Next couple diffs will add more stuff here.)
Test Plan: {F28055}
Reviewers: btrahan, chad
Reviewed By: btrahan
CC: aran, vrana
Maniphest Tasks: T2104
Differential Revision: https://secure.phabricator.com/D4246
			
			
This commit is contained in:
		@@ -1676,6 +1676,19 @@ celerity_register_resource_map(array(
 | 
			
		||||
    ),
 | 
			
		||||
    'disk' => '/rsrc/js/application/core/behavior-tooltip.js',
 | 
			
		||||
  ),
 | 
			
		||||
  'javelin-behavior-phabricator-transaction-comment-form' =>
 | 
			
		||||
  array(
 | 
			
		||||
    'uri' => '/res/bdc362ee/rsrc/js/application/transactions/behavior-transaction-comment-form.js',
 | 
			
		||||
    'type' => 'js',
 | 
			
		||||
    'requires' =>
 | 
			
		||||
    array(
 | 
			
		||||
      0 => 'javelin-behavior',
 | 
			
		||||
      1 => 'javelin-dom',
 | 
			
		||||
      2 => 'javelin-util',
 | 
			
		||||
      3 => 'phabricator-shaped-request',
 | 
			
		||||
    ),
 | 
			
		||||
    'disk' => '/rsrc/js/application/transactions/behavior-transaction-comment-form.js',
 | 
			
		||||
  ),
 | 
			
		||||
  'javelin-behavior-phabricator-transaction-list' =>
 | 
			
		||||
  array(
 | 
			
		||||
    'uri' => '/res/212f97ba/rsrc/js/application/transactions/behavior-transaction-list.js',
 | 
			
		||||
@@ -2847,7 +2860,7 @@ celerity_register_resource_map(array(
 | 
			
		||||
  ),
 | 
			
		||||
  'phabricator-timeline-view-css' =>
 | 
			
		||||
  array(
 | 
			
		||||
    'uri' => '/res/1846f7d5/rsrc/css/layout/phabricator-timeline-view.css',
 | 
			
		||||
    'uri' => '/res/9cbeb7f0/rsrc/css/layout/phabricator-timeline-view.css',
 | 
			
		||||
    'type' => 'css',
 | 
			
		||||
    'requires' =>
 | 
			
		||||
    array(
 | 
			
		||||
 
 | 
			
		||||
@@ -613,6 +613,7 @@ phutil_register_library_map(array(
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentHistoryController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentView' => 'applications/transactions/view/PhabricatorApplicationTransactionCommentView.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionController' => 'applications/transactions/controller/PhabricatorApplicationTransactionController.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php',
 | 
			
		||||
    'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php',
 | 
			
		||||
@@ -1904,6 +1905,7 @@ phutil_register_library_map(array(
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentHistoryController' => 'PhabricatorApplicationTransactionController',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
 | 
			
		||||
    'PhabricatorApplicationTransactionCommentView' => 'AphrontView',
 | 
			
		||||
    'PhabricatorApplicationTransactionController' => 'PhabricatorController',
 | 
			
		||||
    'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor',
 | 
			
		||||
    'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory',
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ final class AphrontRequest {
 | 
			
		||||
  const TYPE_CONDUIT = '__conduit__';
 | 
			
		||||
  const TYPE_WORKFLOW = '__wflow__';
 | 
			
		||||
  const TYPE_CONTINUE = '__continue__';
 | 
			
		||||
  const TYPE_PREVIEW = '__preview__';
 | 
			
		||||
 | 
			
		||||
  private $host;
 | 
			
		||||
  private $path;
 | 
			
		||||
@@ -339,6 +340,10 @@ final class AphrontRequest {
 | 
			
		||||
    return $this->isFormPost() && $this->getStr('__continue__');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function isPreviewRequest() {
 | 
			
		||||
    return $this->isFormPost() && $this->getStr('__preview__');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get application request parameters in a flattened form suitable for
 | 
			
		||||
   * inclusion in an HTTP request, excluding parameters with special meanings.
 | 
			
		||||
 
 | 
			
		||||
@@ -22,10 +22,11 @@ final class PhabricatorMacroCommentController
 | 
			
		||||
      return new Aphront404Response();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $is_preview = $request->isPreviewRequest();
 | 
			
		||||
 | 
			
		||||
    $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/');
 | 
			
		||||
 | 
			
		||||
    $xactions = array();
 | 
			
		||||
 | 
			
		||||
    $xactions[] = id(new PhabricatorMacroTransaction())
 | 
			
		||||
      ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
 | 
			
		||||
      ->attachComment(
 | 
			
		||||
@@ -40,7 +41,8 @@ final class PhabricatorMacroCommentController
 | 
			
		||||
          PhabricatorContentSource::SOURCE_WEB,
 | 
			
		||||
          array(
 | 
			
		||||
            'ip' => $request->getRemoteAddr(),
 | 
			
		||||
          )));
 | 
			
		||||
          )))
 | 
			
		||||
      ->setIsPreview($is_preview);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $xactions = $editor->applyTransactions($macro, $xactions);
 | 
			
		||||
@@ -54,6 +56,7 @@ final class PhabricatorMacroCommentController
 | 
			
		||||
      return id(new PhabricatorApplicationTransactionResponse())
 | 
			
		||||
        ->setViewer($user)
 | 
			
		||||
        ->setTransactions($xactions)
 | 
			
		||||
        ->setIsPreview($is_preview)
 | 
			
		||||
        ->setAnchorOffset($request->getStr('anchor'));
 | 
			
		||||
    } else {
 | 
			
		||||
      return id(new AphrontRedirectResponse())
 | 
			
		||||
 
 | 
			
		||||
@@ -79,23 +79,14 @@ final class PhabricatorMacroViewController
 | 
			
		||||
          ? pht('Add Comment')
 | 
			
		||||
          : pht('Grovel in Awe'));
 | 
			
		||||
 | 
			
		||||
    $add_comment_form = id(new AphrontFormView())
 | 
			
		||||
      ->setWorkflow(true)
 | 
			
		||||
      ->setFlexible(true)
 | 
			
		||||
      ->addSigil('transaction-append')
 | 
			
		||||
      ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/'))
 | 
			
		||||
    $submit_button_name = $is_serious
 | 
			
		||||
      ? pht('Add Comment')
 | 
			
		||||
      : pht('Lavish Praise');
 | 
			
		||||
 | 
			
		||||
    $add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
 | 
			
		||||
      ->setUser($user)
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new PhabricatorRemarkupControl())
 | 
			
		||||
          ->setUser($user)
 | 
			
		||||
          ->setLabel('Comment')
 | 
			
		||||
          ->setName('comment'))
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new AphrontFormSubmitControl())
 | 
			
		||||
          ->setValue(
 | 
			
		||||
            $is_serious
 | 
			
		||||
              ? pht('Add Comment')
 | 
			
		||||
              : pht('Lavish Praise')));
 | 
			
		||||
      ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/'))
 | 
			
		||||
      ->setSubmitButtonName($submit_button_name);
 | 
			
		||||
 | 
			
		||||
    return $this->buildApplicationPage(
 | 
			
		||||
      array(
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,10 @@ final class PholioMockCommentController extends PholioController {
 | 
			
		||||
    $request = $this->getRequest();
 | 
			
		||||
    $user = $request->getUser();
 | 
			
		||||
 | 
			
		||||
    if (!$request->isFormPost()) {
 | 
			
		||||
      return new Aphront400Response();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $mock = id(new PholioMockQuery())
 | 
			
		||||
      ->setViewer($user)
 | 
			
		||||
      ->withIDs(array($this->id))
 | 
			
		||||
@@ -24,6 +28,8 @@ final class PholioMockCommentController extends PholioController {
 | 
			
		||||
      return new Aphront404Response();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $is_preview = $request->isPreviewRequest();
 | 
			
		||||
 | 
			
		||||
    $mock_uri = '/M'.$mock->getID();
 | 
			
		||||
 | 
			
		||||
    $comment = $request->getStr('comment');
 | 
			
		||||
@@ -44,7 +50,8 @@ final class PholioMockCommentController extends PholioController {
 | 
			
		||||
    $editor = id(new PholioMockEditor())
 | 
			
		||||
      ->setActor($user)
 | 
			
		||||
      ->setContentSource($content_source)
 | 
			
		||||
      ->setContinueOnException($request->isContinueRequest());
 | 
			
		||||
      ->setContinueOnNoEffect($request->isContinueRequest())
 | 
			
		||||
      ->setIsPreview($is_preview);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      $xactions = $editor->applyTransactions($mock, $xactions);
 | 
			
		||||
@@ -58,6 +65,7 @@ final class PholioMockCommentController extends PholioController {
 | 
			
		||||
      return id(new PhabricatorApplicationTransactionResponse())
 | 
			
		||||
        ->setViewer($user)
 | 
			
		||||
        ->setTransactions($xactions)
 | 
			
		||||
        ->setIsPreview($is_preview)
 | 
			
		||||
        ->setAnchorOffset($request->getStr('anchor'));
 | 
			
		||||
    } else {
 | 
			
		||||
      return id(new AphrontRedirectResponse())->setURI($mock_uri);
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,7 @@ final class PholioMockEditController extends PholioController {
 | 
			
		||||
        $mock->openTransaction();
 | 
			
		||||
          $editor = id(new PholioMockEditor())
 | 
			
		||||
            ->setContentSource($content_source)
 | 
			
		||||
            ->setContinueOnException(true)
 | 
			
		||||
            ->setContinueOnNoEffect(true)
 | 
			
		||||
            ->setActor($user);
 | 
			
		||||
 | 
			
		||||
          $xactions = $editor->applyTransactions($mock, $xactions);
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ final class PholioMockViewController extends PholioController {
 | 
			
		||||
        'Carousel Goes Here</h1>';
 | 
			
		||||
 | 
			
		||||
    $xaction_view = id(new PhabricatorApplicationTransactionView())
 | 
			
		||||
      ->setViewer($this->getRequest()->getUser())
 | 
			
		||||
      ->setUser($this->getRequest()->getUser())
 | 
			
		||||
      ->setTransactions($xactions)
 | 
			
		||||
      ->setMarkupEngine($engine);
 | 
			
		||||
 | 
			
		||||
@@ -168,24 +168,14 @@ final class PholioMockViewController extends PholioController {
 | 
			
		||||
    $header = id(new PhabricatorHeaderView())
 | 
			
		||||
      ->setHeader($title);
 | 
			
		||||
 | 
			
		||||
    $action = $is_serious
 | 
			
		||||
    $button_name = $is_serious
 | 
			
		||||
      ? pht('Add Comment')
 | 
			
		||||
      : pht('Answer The Call');
 | 
			
		||||
 | 
			
		||||
    $form = id(new AphrontFormView())
 | 
			
		||||
    $form = id(new PhabricatorApplicationTransactionCommentView())
 | 
			
		||||
      ->setUser($user)
 | 
			
		||||
      ->addSigil('transaction-append')
 | 
			
		||||
      ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/'))
 | 
			
		||||
      ->setWorkflow(true)
 | 
			
		||||
      ->setFlexible(true)
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new PhabricatorRemarkupControl())
 | 
			
		||||
          ->setName('comment')
 | 
			
		||||
          ->setLabel(pht('Comment'))
 | 
			
		||||
          ->setUser($user))
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new AphrontFormSubmitControl())
 | 
			
		||||
          ->setValue($action));
 | 
			
		||||
      ->setSubmitButtonName($button_name)
 | 
			
		||||
      ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/'));
 | 
			
		||||
 | 
			
		||||
    return array(
 | 
			
		||||
      $header,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
  private $mentionedPHIDs;
 | 
			
		||||
  private $continueOnNoEffect;
 | 
			
		||||
 | 
			
		||||
  private $isPreview;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * When the editor tries to apply transactions that have no effect, should
 | 
			
		||||
@@ -46,6 +47,15 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
    return $this->mentionedPHIDs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setIsPreview($is_preview) {
 | 
			
		||||
    $this->isPreview = $is_preview;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getIsPreview() {
 | 
			
		||||
    return $this->isPreview;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getTransactionTypes() {
 | 
			
		||||
    $types = array(
 | 
			
		||||
      PhabricatorTransactions::TYPE_COMMENT,
 | 
			
		||||
@@ -225,11 +235,16 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
    $xactions = $this->filterTransactions($object, $xactions);
 | 
			
		||||
 | 
			
		||||
    if (!$xactions) {
 | 
			
		||||
      return $this;
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $xactions = $this->sortTransactions($xactions);
 | 
			
		||||
 | 
			
		||||
    if ($this->getIsPreview()) {
 | 
			
		||||
      $this->loadHandles($xactions);
 | 
			
		||||
      return $xactions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor())
 | 
			
		||||
      ->setActor($actor)
 | 
			
		||||
      ->setContentSource($this->getContentSource());
 | 
			
		||||
@@ -256,10 +271,8 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
      }
 | 
			
		||||
    $object->saveTransaction();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    $this->loadHandles($xactions);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    $mail = null;
 | 
			
		||||
    if ($this->supportsMail()) {
 | 
			
		||||
      $mail = $this->sendMail($object, $xactions);
 | 
			
		||||
@@ -285,8 +298,8 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
 | 
			
		||||
  private function loadHandles(array $xactions) {
 | 
			
		||||
    $phids = array();
 | 
			
		||||
    foreach ($xactions as $xaction) {
 | 
			
		||||
      $phids[$xaction->getPHID()] = $xaction->getRequiredHandlePHIDs();
 | 
			
		||||
    foreach ($xactions as $key => $xaction) {
 | 
			
		||||
      $phids[$key] = $xaction->getRequiredHandlePHIDs();
 | 
			
		||||
    }
 | 
			
		||||
    $handles = array();
 | 
			
		||||
    $merged = array_mergev($phids);
 | 
			
		||||
@@ -295,11 +308,8 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
        ->setViewer($this->requireActor())
 | 
			
		||||
        ->loadHandles();
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($xactions as $xaction) {
 | 
			
		||||
      $xaction->setHandles(
 | 
			
		||||
        array_select_keys(
 | 
			
		||||
          $handles,
 | 
			
		||||
          $phids[$xaction->getPHID()]));
 | 
			
		||||
    foreach ($xactions as $key => $xaction) {
 | 
			
		||||
      $xaction->setHandles(array_select_keys($handles, $phids[$key]));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -587,13 +597,18 @@ abstract class PhabricatorApplicationTransactionEditor
 | 
			
		||||
      return $xactions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!$this->getContinueOnNoEffect()) {
 | 
			
		||||
    if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) {
 | 
			
		||||
      throw new PhabricatorApplicationTransactionNoEffectException(
 | 
			
		||||
        $no_effect,
 | 
			
		||||
        $any_effect,
 | 
			
		||||
        $has_comment);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!$any_effect && !$has_comment) {
 | 
			
		||||
      // If we only have empty comment transactions, just drop them all.
 | 
			
		||||
      return array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    foreach ($no_effect as $key => $xaction) {
 | 
			
		||||
      if ($xaction->getComment()) {
 | 
			
		||||
        $xaction->setTransactionType($type_comment);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ final class PhabricatorApplicationTransactionResponse
 | 
			
		||||
  private $viewer;
 | 
			
		||||
  private $transactions;
 | 
			
		||||
  private $anchorOffset;
 | 
			
		||||
  private $isPreview;
 | 
			
		||||
 | 
			
		||||
  protected function buildProxy() {
 | 
			
		||||
    return new AphrontAjaxResponse();
 | 
			
		||||
@@ -40,16 +41,26 @@ final class PhabricatorApplicationTransactionResponse
 | 
			
		||||
    return $this->viewer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setIsPreview($is_preview) {
 | 
			
		||||
    $this->isPreview = $is_preview;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function reduceProxyResponse() {
 | 
			
		||||
    $view = id(new PhabricatorApplicationTransactionView())
 | 
			
		||||
      ->setViewer($this->getViewer())
 | 
			
		||||
      ->setTransactions($this->getTransactions());
 | 
			
		||||
      ->setUser($this->getViewer())
 | 
			
		||||
      ->setTransactions($this->getTransactions())
 | 
			
		||||
      ->setIsPreview($this->isPreview);
 | 
			
		||||
 | 
			
		||||
    if ($this->getAnchorOffset()) {
 | 
			
		||||
      $view->setAnchorOffset($this->getAnchorOffset());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $xactions = mpull($view->buildEvents(), 'render', 'getTransactionPHID');
 | 
			
		||||
    if ($this->isPreview) {
 | 
			
		||||
      $xactions = mpull($view->buildEvents(), 'render');
 | 
			
		||||
    } else {
 | 
			
		||||
      $xactions = mpull($view->buildEvents(), 'render', 'getTransactionPHID');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $content = array(
 | 
			
		||||
      'xactions' => $xactions,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,147 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @concrete-extensible
 | 
			
		||||
 */
 | 
			
		||||
class PhabricatorApplicationTransactionCommentView extends AphrontView {
 | 
			
		||||
 | 
			
		||||
  private $submitButtonName;
 | 
			
		||||
  private $action;
 | 
			
		||||
 | 
			
		||||
  private $previewPanelID;
 | 
			
		||||
  private $previewTimelineID;
 | 
			
		||||
  private $previewToggleID;
 | 
			
		||||
  private $formID;
 | 
			
		||||
  private $statusID;
 | 
			
		||||
 | 
			
		||||
  public function setSubmitButtonName($submit_button_name) {
 | 
			
		||||
    $this->submitButtonName = $submit_button_name;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getSubmitButtonName() {
 | 
			
		||||
    return $this->submitButtonName;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setAction($action) {
 | 
			
		||||
    $this->action = $action;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getAction() {
 | 
			
		||||
    return $this->action;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function render() {
 | 
			
		||||
 | 
			
		||||
    $data = array();
 | 
			
		||||
 | 
			
		||||
    $comment = $this->renderCommentPanel();
 | 
			
		||||
 | 
			
		||||
    $preview = $this->renderPreviewPanel();
 | 
			
		||||
 | 
			
		||||
    Javelin::initBehavior(
 | 
			
		||||
      'phabricator-transaction-comment-form',
 | 
			
		||||
      array(
 | 
			
		||||
        'formID'        => $this->getFormID(),
 | 
			
		||||
        'timelineID'    => $this->getPreviewTimelineID(),
 | 
			
		||||
        'panelID'       => $this->getPreviewPanelID(),
 | 
			
		||||
        'statusID'      => $this->getStatusID(),
 | 
			
		||||
 | 
			
		||||
        'loadingString' => pht('Loading Preview...'),
 | 
			
		||||
        'savingString'  => pht('Saving Draft...'),
 | 
			
		||||
        'draftString'   => pht('Saved Draft'),
 | 
			
		||||
 | 
			
		||||
        'actionURI'     => $this->getAction(),
 | 
			
		||||
      ));
 | 
			
		||||
 | 
			
		||||
    return self::renderSingleView(
 | 
			
		||||
      array(
 | 
			
		||||
        $comment,
 | 
			
		||||
        $preview,
 | 
			
		||||
      ));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function renderCommentPanel() {
 | 
			
		||||
    $status = phutil_render_tag(
 | 
			
		||||
      'div',
 | 
			
		||||
      array(
 | 
			
		||||
        'id' => $this->getStatusID(),
 | 
			
		||||
      ),
 | 
			
		||||
      '');
 | 
			
		||||
 | 
			
		||||
    return id(new AphrontFormView())
 | 
			
		||||
      ->setUser($this->getUser())
 | 
			
		||||
      ->setFlexible(true)
 | 
			
		||||
      ->addSigil('transaction-append')
 | 
			
		||||
      ->setWorkflow(true)
 | 
			
		||||
      ->setAction($this->getAction())
 | 
			
		||||
      ->setID($this->getFormID())
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new PhabricatorRemarkupControl())
 | 
			
		||||
          ->setName('comment')
 | 
			
		||||
          ->setLabel(pht('Comment'))
 | 
			
		||||
          ->setUser($this->getUser()))
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new AphrontFormSubmitControl())
 | 
			
		||||
          ->setValue($this->getSubmitButtonName()))
 | 
			
		||||
      ->appendChild(
 | 
			
		||||
        id(new AphrontFormMarkupControl())
 | 
			
		||||
          ->setValue($status));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function renderPreviewPanel() {
 | 
			
		||||
 | 
			
		||||
    $preview = id(new PhabricatorTimelineView())
 | 
			
		||||
      ->setID($this->getPreviewTimelineID());
 | 
			
		||||
 | 
			
		||||
    $header = phutil_render_tag(
 | 
			
		||||
      'div',
 | 
			
		||||
      array(
 | 
			
		||||
        'class' => 'phabricator-timeline-preview-header',
 | 
			
		||||
      ),
 | 
			
		||||
      phutil_escape_html(pht('Preview')));
 | 
			
		||||
 | 
			
		||||
    return phutil_render_tag(
 | 
			
		||||
      'div',
 | 
			
		||||
      array(
 | 
			
		||||
        'id'    => $this->getPreviewPanelID(),
 | 
			
		||||
        'style' => 'display: none',
 | 
			
		||||
      ),
 | 
			
		||||
      self::renderSingleView(
 | 
			
		||||
        array(
 | 
			
		||||
          $header,
 | 
			
		||||
          $preview,
 | 
			
		||||
        )));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function getPreviewPanelID() {
 | 
			
		||||
    if (!$this->previewPanelID) {
 | 
			
		||||
      $this->previewPanelID = celerity_generate_unique_node_id();
 | 
			
		||||
    }
 | 
			
		||||
    return $this->previewPanelID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function getPreviewTimelineID() {
 | 
			
		||||
    if (!$this->previewTimelineID) {
 | 
			
		||||
      $this->previewTimelineID = celerity_generate_unique_node_id();
 | 
			
		||||
    }
 | 
			
		||||
    return $this->previewTimelineID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function getFormID() {
 | 
			
		||||
    if (!$this->formID) {
 | 
			
		||||
      $this->formID = celerity_generate_unique_node_id();
 | 
			
		||||
    }
 | 
			
		||||
    return $this->formID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function getStatusID() {
 | 
			
		||||
    if (!$this->statusID) {
 | 
			
		||||
      $this->statusID = celerity_generate_unique_node_id();
 | 
			
		||||
    }
 | 
			
		||||
    return $this->statusID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -5,11 +5,16 @@
 | 
			
		||||
 */
 | 
			
		||||
class PhabricatorApplicationTransactionView extends AphrontView {
 | 
			
		||||
 | 
			
		||||
  private $viewer;
 | 
			
		||||
  private $transactions;
 | 
			
		||||
  private $engine;
 | 
			
		||||
  private $anchorOffset = 1;
 | 
			
		||||
  private $showEditActions = true;
 | 
			
		||||
  private $isPreview;
 | 
			
		||||
 | 
			
		||||
  public function setIsPreview($is_preview) {
 | 
			
		||||
    $this->isPreview = $is_preview;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setShowEditActions($show_edit_actions) {
 | 
			
		||||
    $this->showEditActions = $show_edit_actions;
 | 
			
		||||
@@ -36,16 +41,11 @@ class PhabricatorApplicationTransactionView extends AphrontView {
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setViewer(PhabricatorUser $viewer) {
 | 
			
		||||
    $this->viewer = $viewer;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function buildEvents() {
 | 
			
		||||
    $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
 | 
			
		||||
    $engine = $this->getOrBuildEngine();
 | 
			
		||||
 | 
			
		||||
    $viewer = $this->viewer;
 | 
			
		||||
    $user = $this->getUser();
 | 
			
		||||
 | 
			
		||||
    $anchor = $this->anchorOffset;
 | 
			
		||||
    $events = array();
 | 
			
		||||
@@ -55,22 +55,29 @@ class PhabricatorApplicationTransactionView extends AphrontView {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $event = id(new PhabricatorTimelineEventView())
 | 
			
		||||
        ->setViewer($viewer)
 | 
			
		||||
        ->setUser($user)
 | 
			
		||||
        ->setTransactionPHID($xaction->getPHID())
 | 
			
		||||
        ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID()))
 | 
			
		||||
        ->setIcon($xaction->getIcon())
 | 
			
		||||
        ->setColor($xaction->getColor())
 | 
			
		||||
        ->setTitle($xaction->getTitle())
 | 
			
		||||
        ->setDateCreated($xaction->getDateCreated())
 | 
			
		||||
        ->setContentSource($xaction->getContentSource())
 | 
			
		||||
        ->setAnchor($anchor);
 | 
			
		||||
        ->setTitle($xaction->getTitle());
 | 
			
		||||
 | 
			
		||||
      if ($this->isPreview) {
 | 
			
		||||
        $event->setIsPreview(true);
 | 
			
		||||
      } else {
 | 
			
		||||
        $event
 | 
			
		||||
          ->setDateCreated($xaction->getDateCreated())
 | 
			
		||||
          ->setContentSource($xaction->getContentSource())
 | 
			
		||||
          ->setAnchor($anchor);
 | 
			
		||||
 | 
			
		||||
        $anchor++;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $anchor++;
 | 
			
		||||
 | 
			
		||||
      $has_deleted_comment = $xaction->getComment() &&
 | 
			
		||||
        $xaction->getComment()->getIsDeleted();
 | 
			
		||||
 | 
			
		||||
      if ($this->getShowEditActions()) {
 | 
			
		||||
      if ($this->getShowEditActions() && !$this->isPreview) {
 | 
			
		||||
        if ($xaction->getCommentVersion() > 1) {
 | 
			
		||||
          $event->setIsEdited(true);
 | 
			
		||||
        }
 | 
			
		||||
@@ -79,7 +86,7 @@ class PhabricatorApplicationTransactionView extends AphrontView {
 | 
			
		||||
 | 
			
		||||
        if ($xaction->hasComment() || $has_deleted_comment) {
 | 
			
		||||
          $has_edit_capability = PhabricatorPolicyFilter::hasCapability(
 | 
			
		||||
            $viewer,
 | 
			
		||||
            $user,
 | 
			
		||||
            $xaction,
 | 
			
		||||
            $can_edit);
 | 
			
		||||
          if ($has_edit_capability) {
 | 
			
		||||
@@ -134,7 +141,7 @@ class PhabricatorApplicationTransactionView extends AphrontView {
 | 
			
		||||
    $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
 | 
			
		||||
 | 
			
		||||
    $engine = id(new PhabricatorMarkupEngine())
 | 
			
		||||
      ->setViewer($this->viewer);
 | 
			
		||||
      ->setViewer($this->getUser());
 | 
			
		||||
    foreach ($this->transactions as $xaction) {
 | 
			
		||||
      if (!$xaction->hasComment()) {
 | 
			
		||||
        continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ final class PhabricatorTimelineEventView extends AphrontView {
 | 
			
		||||
  private $classes = array();
 | 
			
		||||
  private $contentSource;
 | 
			
		||||
  private $dateCreated;
 | 
			
		||||
  private $viewer;
 | 
			
		||||
  private $anchor;
 | 
			
		||||
  private $isEditable;
 | 
			
		||||
  private $isEdited;
 | 
			
		||||
  private $transactionPHID;
 | 
			
		||||
  private $isPreview;
 | 
			
		||||
 | 
			
		||||
  public function setTransactionPHID($transaction_phid) {
 | 
			
		||||
    $this->transactionPHID = $transaction_phid;
 | 
			
		||||
@@ -33,6 +33,14 @@ final class PhabricatorTimelineEventView extends AphrontView {
 | 
			
		||||
    return $this->isEdited;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setIsPreview($is_preview) {
 | 
			
		||||
    $this->isPreview = $is_preview;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getIsPreview() {
 | 
			
		||||
    return $this->isPreview;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setIsEditable($is_editable) {
 | 
			
		||||
    $this->isEditable = $is_editable;
 | 
			
		||||
@@ -43,15 +51,6 @@ final class PhabricatorTimelineEventView extends AphrontView {
 | 
			
		||||
    return $this->isEditable;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setViewer(PhabricatorUser $viewer) {
 | 
			
		||||
    $this->viewer = $viewer;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function getViewer() {
 | 
			
		||||
    return $this->viewer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function setDateCreated($date_created) {
 | 
			
		||||
    $this->dateCreated = $date_created;
 | 
			
		||||
    return $this;
 | 
			
		||||
@@ -108,67 +107,7 @@ final class PhabricatorTimelineEventView extends AphrontView {
 | 
			
		||||
      $title = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $extra = array();
 | 
			
		||||
    $xaction_phid = $this->getTransactionPHID();
 | 
			
		||||
 | 
			
		||||
    if ($this->getIsEdited()) {
 | 
			
		||||
      $extra[] = javelin_render_tag(
 | 
			
		||||
        'a',
 | 
			
		||||
        array(
 | 
			
		||||
          'href'  => '/transactions/history/'.$xaction_phid.'/',
 | 
			
		||||
          'sigil' => 'workflow',
 | 
			
		||||
        ),
 | 
			
		||||
        pht('Edited'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($this->getIsEditable()) {
 | 
			
		||||
      $extra[] = javelin_render_tag(
 | 
			
		||||
        'a',
 | 
			
		||||
        array(
 | 
			
		||||
          'href'  => '/transactions/edit/'.$xaction_phid.'/',
 | 
			
		||||
          'sigil' => 'workflow transaction-edit',
 | 
			
		||||
        ),
 | 
			
		||||
        pht('Edit'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $source = $this->getContentSource();
 | 
			
		||||
    if ($source) {
 | 
			
		||||
      $extra[] = id(new PhabricatorContentSourceView())
 | 
			
		||||
        ->setContentSource($source)
 | 
			
		||||
        ->setUser($this->getViewer())
 | 
			
		||||
        ->render();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($this->getDateCreated()) {
 | 
			
		||||
      $date = phabricator_datetime(
 | 
			
		||||
        $this->getDateCreated(),
 | 
			
		||||
        $this->getViewer());
 | 
			
		||||
      if ($this->anchor) {
 | 
			
		||||
        Javelin::initBehavior('phabricator-watch-anchor');
 | 
			
		||||
 | 
			
		||||
        $anchor = id(new PhabricatorAnchorView())
 | 
			
		||||
          ->setAnchorName($this->anchor)
 | 
			
		||||
          ->render();
 | 
			
		||||
 | 
			
		||||
        $date = $anchor.phutil_render_tag(
 | 
			
		||||
            'a',
 | 
			
		||||
            array(
 | 
			
		||||
              'href' => '#'.$this->anchor,
 | 
			
		||||
            ),
 | 
			
		||||
            $date);
 | 
			
		||||
      }
 | 
			
		||||
      $extra[] = $date;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $extra = implode(' · ', $extra);
 | 
			
		||||
    if ($extra) {
 | 
			
		||||
      $extra = phutil_render_tag(
 | 
			
		||||
        'span',
 | 
			
		||||
        array(
 | 
			
		||||
          'class' => 'phabricator-timeline-extra',
 | 
			
		||||
        ),
 | 
			
		||||
        $extra);
 | 
			
		||||
    }
 | 
			
		||||
    $extra = $this->renderExtra();
 | 
			
		||||
 | 
			
		||||
    if ($title !== null || $extra !== null) {
 | 
			
		||||
      $title_classes = array();
 | 
			
		||||
@@ -286,4 +225,76 @@ final class PhabricatorTimelineEventView extends AphrontView {
 | 
			
		||||
        $content));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function renderExtra() {
 | 
			
		||||
    $extra = array();
 | 
			
		||||
 | 
			
		||||
    if ($this->getIsPreview()) {
 | 
			
		||||
      $extra[] = pht('PREVIEW');
 | 
			
		||||
    } else {
 | 
			
		||||
      $xaction_phid = $this->getTransactionPHID();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      if ($this->getIsEdited()) {
 | 
			
		||||
        $extra[] = javelin_render_tag(
 | 
			
		||||
          'a',
 | 
			
		||||
          array(
 | 
			
		||||
            'href'  => '/transactions/history/'.$xaction_phid.'/',
 | 
			
		||||
            'sigil' => 'workflow',
 | 
			
		||||
          ),
 | 
			
		||||
          pht('Edited'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($this->getIsEditable()) {
 | 
			
		||||
        $extra[] = javelin_render_tag(
 | 
			
		||||
          'a',
 | 
			
		||||
          array(
 | 
			
		||||
            'href'  => '/transactions/edit/'.$xaction_phid.'/',
 | 
			
		||||
            'sigil' => 'workflow transaction-edit',
 | 
			
		||||
          ),
 | 
			
		||||
          pht('Edit'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      $source = $this->getContentSource();
 | 
			
		||||
      if ($source) {
 | 
			
		||||
        $extra[] = id(new PhabricatorContentSourceView())
 | 
			
		||||
          ->setContentSource($source)
 | 
			
		||||
          ->setUser($this->getUser())
 | 
			
		||||
          ->render();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if ($this->getDateCreated()) {
 | 
			
		||||
        $date = phabricator_datetime(
 | 
			
		||||
          $this->getDateCreated(),
 | 
			
		||||
          $this->getUser());
 | 
			
		||||
        if ($this->anchor) {
 | 
			
		||||
          Javelin::initBehavior('phabricator-watch-anchor');
 | 
			
		||||
 | 
			
		||||
          $anchor = id(new PhabricatorAnchorView())
 | 
			
		||||
            ->setAnchorName($this->anchor)
 | 
			
		||||
            ->render();
 | 
			
		||||
 | 
			
		||||
          $date = $anchor.phutil_render_tag(
 | 
			
		||||
              'a',
 | 
			
		||||
              array(
 | 
			
		||||
                'href' => '#'.$this->anchor,
 | 
			
		||||
              ),
 | 
			
		||||
              $date);
 | 
			
		||||
        }
 | 
			
		||||
        $extra[] = $date;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $extra = implode(' · ', $extra);
 | 
			
		||||
    if ($extra) {
 | 
			
		||||
      $extra = phutil_render_tag(
 | 
			
		||||
        'span',
 | 
			
		||||
        array(
 | 
			
		||||
          'class' => 'phabricator-timeline-extra',
 | 
			
		||||
        ),
 | 
			
		||||
        $extra);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $extra;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -245,3 +245,10 @@
 | 
			
		||||
  background: rgba(255, 255, 0, 0.50);
 | 
			
		||||
  box-shadow: 0 0 3px 6px rgba(255, 255, 0, 0.50);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phabricator-timeline-preview-header {
 | 
			
		||||
  background: #e0e3ec;
 | 
			
		||||
  color: #444444;
 | 
			
		||||
  padding: 4px 1.25%;
 | 
			
		||||
  border: solid #c0c5d1 1px 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @provides javelin-behavior-phabricator-transaction-comment-form
 | 
			
		||||
 * @requires javelin-behavior
 | 
			
		||||
 *           javelin-dom
 | 
			
		||||
 *           javelin-util
 | 
			
		||||
 *           phabricator-shaped-request
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
JX.behavior('phabricator-transaction-comment-form', function(config) {
 | 
			
		||||
 | 
			
		||||
  var form = JX.$(config.formID);
 | 
			
		||||
 | 
			
		||||
  var getdata = function() {
 | 
			
		||||
    var obj = JX.DOM.convertFormToDictionary(form);
 | 
			
		||||
    obj.__preview__ = 1;
 | 
			
		||||
    return obj;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  var onresponse = function(response) {
 | 
			
		||||
    var panel = JX.$(config.panelID);
 | 
			
		||||
    if (!response.xactions.length) {
 | 
			
		||||
      JX.DOM.hide(panel);
 | 
			
		||||
    } else {
 | 
			
		||||
      JX.DOM.setContent(
 | 
			
		||||
        JX.$(config.timelineID),
 | 
			
		||||
        [
 | 
			
		||||
          JX.$H(response.spacer),
 | 
			
		||||
          JX.$H(response.xactions.join(response.spacer)),
 | 
			
		||||
          JX.$H(response.spacer),
 | 
			
		||||
        ]);
 | 
			
		||||
      JX.DOM.show(panel);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  var request = new JX.PhabricatorShapedRequest(
 | 
			
		||||
    config.actionURI,
 | 
			
		||||
    onresponse,
 | 
			
		||||
    getdata);
 | 
			
		||||
  var trigger = JX.bind(request, request.trigger);
 | 
			
		||||
 | 
			
		||||
  JX.DOM.listen(form, 'keydown', null, trigger);
 | 
			
		||||
 | 
			
		||||
  request.start();
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user