1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

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:
epriestley 2012-12-21 05:51:33 -08:00
parent 4af2e3c4e2
commit 0fd77783a4
15 changed files with 391 additions and 137 deletions

View file

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

View file

@ -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',

View file

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

View file

@ -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())

View file

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

View file

@ -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);

View file

@ -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);

View file

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

View file

@ -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);

View file

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

View file

@ -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;
}
}

View file

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

View file

@ -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(' &middot; ', $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(' &middot; ', $extra);
if ($extra) {
$extra = phutil_render_tag(
'span',
array(
'class' => 'phabricator-timeline-extra',
),
$extra);
}
return $extra;
}
}

View file

@ -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;
}

View file

@ -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();
});