Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php
index a5417376fa..c3f2263de6 100644
--- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php
+++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php
@@ -1,284 +1,312 @@
<?php
final class PhabricatorAuditInlineComment
implements PhabricatorInlineCommentInterface {
private $proxy;
private $syntheticAuthor;
public function __construct() {
$this->proxy = new PhabricatorAuditTransactionComment();
}
public function __clone() {
$this->proxy = clone $this->proxy;
}
public function getTransactionPHID() {
return $this->proxy->getTransactionPHID();
}
public function getTransactionComment() {
return $this->proxy;
}
public function getTransactionCommentForSave() {
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_LEGACY,
array());
$this->proxy
->setViewPolicy('public')
->setEditPolicy($this->getAuthorPHID())
->setContentSource($content_source)
->setCommentVersion(1);
return $this->proxy;
}
public static function loadID($id) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'id = %d',
$id);
if (!$inlines) {
return null;
}
return head(self::buildProxies($inlines));
}
+ public static function loadPHID($phid) {
+ $inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
+ 'phid = %s',
+ $phid);
+ if (!$inlines) {
+ return null;
+ }
+ return head(self::buildProxies($inlines));
+ }
+
public static function loadDraftComments(
PhabricatorUser $viewer,
$commit_phid) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'authorPHID = %s AND commitPHID = %s AND transactionPHID IS NULL
AND pathID IS NOT NULL',
$viewer->getPHID(),
$commit_phid);
return self::buildProxies($inlines);
}
public static function loadPublishedComments(
PhabricatorUser $viewer,
$commit_phid) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND transactionPHID IS NOT NULL
AND pathID IS NOT NULL',
$commit_phid);
return self::buildProxies($inlines);
}
public static function loadDraftAndPublishedComments(
PhabricatorUser $viewer,
$commit_phid,
$path_id = null) {
if ($path_id === null) {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND (transactionPHID IS NOT NULL OR authorPHID = %s)
AND pathID IS NOT NULL',
$commit_phid,
$viewer->getPHID());
} else {
$inlines = id(new PhabricatorAuditTransactionComment())->loadAllWhere(
'commitPHID = %s AND pathID = %d AND
(authorPHID = %s OR transactionPHID IS NOT NULL)',
$commit_phid,
$path_id,
$viewer->getPHID());
}
return self::buildProxies($inlines);
}
private static function buildProxies(array $inlines) {
$results = array();
foreach ($inlines as $key => $inline) {
$results[$key] = PhabricatorAuditInlineComment::newFromModernComment(
$inline);
}
return $results;
}
public function setSyntheticAuthor($synthetic_author) {
$this->syntheticAuthor = $synthetic_author;
return $this;
}
public function getSyntheticAuthor() {
return $this->syntheticAuthor;
}
public function openTransaction() {
$this->proxy->openTransaction();
}
public function saveTransaction() {
$this->proxy->saveTransaction();
}
public function save() {
$this->getTransactionCommentForSave()->save();
return $this;
}
public function delete() {
$this->proxy->delete();
return $this;
}
public function getID() {
return $this->proxy->getID();
}
public function getPHID() {
return $this->proxy->getPHID();
}
public static function newFromModernComment(
PhabricatorAuditTransactionComment $comment) {
$obj = new PhabricatorAuditInlineComment();
$obj->proxy = $comment;
return $obj;
}
public function isCompatible(PhabricatorInlineCommentInterface $comment) {
return
($this->getAuthorPHID() === $comment->getAuthorPHID()) &&
($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) &&
($this->getContent() === $comment->getContent());
}
public function setContent($content) {
$this->proxy->setContent($content);
return $this;
}
public function getContent() {
return $this->proxy->getContent();
}
public function isDraft() {
return !$this->proxy->getTransactionPHID();
}
public function setPathID($id) {
$this->proxy->setPathID($id);
return $this;
}
public function getPathID() {
return $this->proxy->getPathID();
}
public function setIsNewFile($is_new) {
$this->proxy->setIsNewFile($is_new);
return $this;
}
public function getIsNewFile() {
return $this->proxy->getIsNewFile();
}
public function setLineNumber($number) {
$this->proxy->setLineNumber($number);
return $this;
}
public function getLineNumber() {
return $this->proxy->getLineNumber();
}
public function setLineLength($length) {
$this->proxy->setLineLength($length);
return $this;
}
public function getLineLength() {
return $this->proxy->getLineLength();
}
public function setCache($cache) {
return $this;
}
public function getCache() {
return null;
}
public function setAuthorPHID($phid) {
$this->proxy->setAuthorPHID($phid);
return $this;
}
public function getAuthorPHID() {
return $this->proxy->getAuthorPHID();
}
public function setCommitPHID($commit_phid) {
$this->proxy->setCommitPHID($commit_phid);
return $this;
}
public function getCommitPHID() {
return $this->proxy->getCommitPHID();
}
// When setting a comment ID, we also generate a phantom transaction PHID for
// the future transaction.
public function setAuditCommentID($id) {
$this->proxy->setLegacyCommentID($id);
$this->proxy->setTransactionPHID(
PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST,
PhabricatorRepositoryCommitPHIDType::TYPECONST));
return $this;
}
public function getAuditCommentID() {
return $this->proxy->getLegacyCommentID();
}
public function setChangesetID($id) {
return $this->setPathID($id);
}
public function getChangesetID() {
return $this->getPathID();
}
+ public function setReplyToCommentPHID($phid) {
+ $this->proxy->setReplyToCommentPHID($phid);
+ return $this;
+ }
+
+ public function getReplyToCommentPHID() {
+ return $this->proxy->getReplyToCommentPHID();
+ }
+
+ public function setHasReplies($has_replies) {
+ $this->proxy->setHasReplies($has_replies);
+ return $this;
+ }
+
+ public function getHasReplies() {
+ return $this->proxy->getHasReplies();
+ }
+
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
return 'AI:'.$this->getID();
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newDifferentialMarkupEngine();
}
public function getMarkupText($field) {
return $this->getContent();
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getID() && $this->getAuditCommentID());
}
}
diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php
index 08366bbdd6..89d5b5e428 100644
--- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php
+++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php
@@ -1,96 +1,102 @@
<?php
final class DifferentialInlineCommentEditController
extends PhabricatorInlineCommentController {
private $revisionID;
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
protected function createComment() {
// Verify revision and changeset correspond to actual objects.
$revision_id = $this->revisionID;
$changeset_id = $this->getChangesetID();
$viewer = $this->getRequest()->getUser();
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($revision_id))
->executeOne();
if (!$revision) {
throw new Exception('Invalid revision ID!');
}
if (!id(new DifferentialChangeset())->load($changeset_id)) {
throw new Exception('Invalid changeset ID!');
}
return id(new DifferentialInlineComment())
->setRevision($revision)
->setChangesetID($changeset_id);
}
protected function loadComment($id) {
return id(new DifferentialInlineCommentQuery())
->withIDs(array($id))
->executeOne();
}
+ protected function loadCommentByPHID($phid) {
+ return id(new DifferentialInlineCommentQuery())
+ ->withPHIDs(array($phid))
+ ->executeOne();
+ }
+
protected function loadCommentForEdit($id) {
$request = $this->getRequest();
$user = $request->getUser();
$inline = $this->loadComment($id);
if (!$this->canEditInlineComment($user, $inline)) {
throw new Exception('That comment is not editable!');
}
return $inline;
}
private function canEditInlineComment(
PhabricatorUser $user,
DifferentialInlineComment $inline) {
// Only the author may edit a comment.
if ($inline->getAuthorPHID() != $user->getPHID()) {
return false;
}
// Saved comments may not be edited, for now, although the schema now
// supports it.
if (!$inline->isDraft()) {
return false;
}
// Inline must be attached to the active revision.
if ($inline->getRevisionID() != $this->revisionID) {
return false;
}
return true;
}
protected function deleteComment(PhabricatorInlineCommentInterface $inline) {
$inline->openTransaction();
DifferentialDraft::deleteHasDraft(
$inline->getAuthorPHID(),
$inline->getRevisionPHID(),
$inline->getPHID());
$inline->delete();
$inline->saveTransaction();
}
protected function saveComment(PhabricatorInlineCommentInterface $inline) {
$inline->openTransaction();
$inline->save();
DifferentialDraft::markHasDraft(
$inline->getAuthorPHID(),
$inline->getRevisionPHID(),
$inline->getPHID());
$inline->saveTransaction();
}
}
diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php
index cd338b74c1..c60316336e 100644
--- a/src/applications/differential/query/DifferentialInlineCommentQuery.php
+++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php
@@ -1,156 +1,169 @@
<?php
/**
* Temporary wrapper for transitioning Differential to ApplicationTransactions.
*/
final class DifferentialInlineCommentQuery
extends PhabricatorOffsetPagedQuery {
private $revisionIDs;
private $notDraft;
private $ids;
+ private $phids;
private $commentIDs;
private $viewerAndChangesetIDs;
private $draftComments;
private $draftsByAuthors;
public function withRevisionIDs(array $ids) {
$this->revisionIDs = $ids;
return $this;
}
public function withNotDraft($not_draft) {
$this->notDraft = $not_draft;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
public function withViewerAndChangesetIDs($author_phid, array $ids) {
$this->viewerAndChangesetIDs = array($author_phid, $ids);
return $this;
}
public function withDraftComments($author_phid, $revision_id) {
$this->draftComments = array($author_phid, $revision_id);
return $this;
}
public function withDraftsByAuthors(array $author_phids) {
$this->draftsByAuthors = $author_phids;
return $this;
}
public function execute() {
$table = new DifferentialTransactionComment();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildLimitClause($conn_r));
$comments = $table->loadAllFromArray($data);
foreach ($comments as $key => $value) {
$comments[$key] = DifferentialInlineComment::newFromModernComment(
$value);
}
return $comments;
}
public function executeOne() {
return head($this->execute());
}
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
// Only find inline comments.
$where[] = qsprintf(
$conn_r,
'changesetID IS NOT NULL');
if ($this->revisionIDs) {
// Look up revision PHIDs.
$revision_phids = queryfx_all(
$conn_r,
'SELECT phid FROM %T WHERE id IN (%Ld)',
id(new DifferentialRevision())->getTableName(),
$this->revisionIDs);
if (!$revision_phids) {
throw new PhabricatorEmptyQueryException();
}
$revision_phids = ipull($revision_phids, 'phid');
$where[] = qsprintf(
$conn_r,
'revisionPHID IN (%Ls)',
$revision_phids);
}
if ($this->notDraft) {
$where[] = qsprintf(
$conn_r,
'transactionPHID IS NOT NULL');
}
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
if ($this->viewerAndChangesetIDs) {
list($phid, $ids) = $this->viewerAndChangesetIDs;
$where[] = qsprintf(
$conn_r,
'changesetID IN (%Ld) AND
(authorPHID = %s OR transactionPHID IS NOT NULL)',
$ids,
$phid);
}
if ($this->draftComments) {
list($phid, $rev_id) = $this->draftComments;
$rev_phid = queryfx_one(
$conn_r,
'SELECT phid FROM %T WHERE id = %d',
id(new DifferentialRevision())->getTableName(),
$rev_id);
if (!$rev_phid) {
throw new PhabricatorEmptyQueryException();
}
$rev_phid = $rev_phid['phid'];
$where[] = qsprintf(
$conn_r,
'authorPHID = %s AND revisionPHID = %s AND transactionPHID IS NULL',
$phid,
$rev_phid);
}
if ($this->draftsByAuthors) {
$where[] = qsprintf(
$conn_r,
'authorPHID IN (%Ls) AND transactionPHID IS NULL',
$this->draftsByAuthors);
}
return $this->formatWhereClause($where);
}
}
diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php
index 59e555c0fb..8701363708 100644
--- a/src/applications/differential/storage/DifferentialInlineComment.php
+++ b/src/applications/differential/storage/DifferentialInlineComment.php
@@ -1,217 +1,236 @@
<?php
final class DifferentialInlineComment
implements PhabricatorInlineCommentInterface {
private $proxy;
private $syntheticAuthor;
public function __construct() {
$this->proxy = new DifferentialTransactionComment();
}
public function __clone() {
$this->proxy = clone $this->proxy;
}
public function getTransactionCommentForSave() {
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_LEGACY,
array());
$this->proxy
->setViewPolicy('public')
->setEditPolicy($this->getAuthorPHID())
->setContentSource($content_source)
->setCommentVersion(1);
return $this->proxy;
}
public function openTransaction() {
$this->proxy->openTransaction();
}
public function saveTransaction() {
$this->proxy->saveTransaction();
}
public function save() {
$this->getTransactionCommentForSave()->save();
return $this;
}
public function delete() {
$this->proxy->delete();
return $this;
}
public function getID() {
return $this->proxy->getID();
}
public function getPHID() {
return $this->proxy->getPHID();
}
public static function newFromModernComment(
DifferentialTransactionComment $comment) {
$obj = new DifferentialInlineComment();
$obj->proxy = $comment;
return $obj;
}
public function setSyntheticAuthor($synthetic_author) {
$this->syntheticAuthor = $synthetic_author;
return $this;
}
public function getSyntheticAuthor() {
return $this->syntheticAuthor;
}
public function isCompatible(PhabricatorInlineCommentInterface $comment) {
return
($this->getAuthorPHID() === $comment->getAuthorPHID()) &&
($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) &&
($this->getContent() === $comment->getContent());
}
public function setContent($content) {
$this->proxy->setContent($content);
return $this;
}
public function getContent() {
return $this->proxy->getContent();
}
public function isDraft() {
return !$this->proxy->getTransactionPHID();
}
public function setChangesetID($id) {
$this->proxy->setChangesetID($id);
return $this;
}
public function getChangesetID() {
return $this->proxy->getChangesetID();
}
public function setIsNewFile($is_new) {
$this->proxy->setIsNewFile($is_new);
return $this;
}
public function getIsNewFile() {
return $this->proxy->getIsNewFile();
}
public function setLineNumber($number) {
$this->proxy->setLineNumber($number);
return $this;
}
public function getLineNumber() {
return $this->proxy->getLineNumber();
}
public function setLineLength($length) {
$this->proxy->setLineLength($length);
return $this;
}
public function getLineLength() {
return $this->proxy->getLineLength();
}
public function setCache($cache) {
return $this;
}
public function getCache() {
return null;
}
public function setAuthorPHID($phid) {
$this->proxy->setAuthorPHID($phid);
return $this;
}
public function getAuthorPHID() {
return $this->proxy->getAuthorPHID();
}
public function setRevision(DifferentialRevision $revision) {
$this->proxy->setRevisionPHID($revision->getPHID());
return $this;
}
public function getRevisionPHID() {
return $this->proxy->getRevisionPHID();
}
// Although these are purely transitional, they're also *extra* dumb.
public function setRevisionID($revision_id) {
$revision = id(new DifferentialRevision())->load($revision_id);
return $this->setRevision($revision);
}
public function getRevisionID() {
$phid = $this->proxy->getRevisionPHID();
if (!$phid) {
return null;
}
$revision = id(new DifferentialRevision())->loadOneWhere(
'phid = %s',
$phid);
if (!$revision) {
return null;
}
return $revision->getID();
}
// When setting a comment ID, we also generate a phantom transaction PHID for
// the future transaction.
public function setCommentID($id) {
$this->proxy->setTransactionPHID(
PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST,
DifferentialRevisionPHIDType::TYPECONST));
return $this;
}
+ public function setReplyToCommentPHID($phid) {
+ $this->proxy->setReplyToCommentPHID($phid);
+ return $this;
+ }
+
+ public function getReplyToCommentPHID() {
+ return $this->proxy->getReplyToCommentPHID();
+ }
+
+ public function setHasReplies($has_replies) {
+ $this->proxy->setHasReplies($has_replies);
+ return $this;
+ }
+
+ public function getHasReplies() {
+ return $this->proxy->getHasReplies();
+ }
+
+
/* -( PhabricatorMarkupInterface Implementation )-------------------------- */
public function getMarkupFieldKey($field) {
// We can't use ID because synthetic comments don't have it.
return 'DI:'.PhabricatorHash::digest($this->getContent());
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newDifferentialMarkupEngine();
}
public function getMarkupText($field) {
return $this->getContent();
}
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
// Only cache submitted comments.
return ($this->getID() && !$this->isDraft());
}
}
diff --git a/src/applications/diffusion/controller/DiffusionInlineCommentController.php b/src/applications/diffusion/controller/DiffusionInlineCommentController.php
index c7d45c903d..62e70dde6c 100644
--- a/src/applications/diffusion/controller/DiffusionInlineCommentController.php
+++ b/src/applications/diffusion/controller/DiffusionInlineCommentController.php
@@ -1,87 +1,91 @@
<?php
final class DiffusionInlineCommentController
extends PhabricatorInlineCommentController {
private $commitPHID;
public function willProcessRequest(array $data) {
$this->commitPHID = $data['phid'];
}
protected function createComment() {
// Verify commit and path correspond to actual objects.
$commit_phid = $this->commitPHID;
$path_id = $this->getChangesetID();
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'phid = %s',
$commit_phid);
if (!$commit) {
throw new Exception('Invalid commit ID!');
}
// TODO: Write a real PathQuery object?
$path = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT path FROM %T WHERE id = %d',
PhabricatorRepository::TABLE_PATH,
$path_id);
if (!$path) {
throw new Exception('Invalid path ID!');
}
return id(new PhabricatorAuditInlineComment())
->setCommitPHID($commit_phid)
->setPathID($path_id);
}
protected function loadComment($id) {
return PhabricatorAuditInlineComment::loadID($id);
}
+ protected function loadCommentByPHID($phid) {
+ return PhabricatorAuditInlineComment::loadPHID($phid);
+ }
+
protected function loadCommentForEdit($id) {
$request = $this->getRequest();
$user = $request->getUser();
$inline = $this->loadComment($id);
if (!$this->canEditInlineComment($user, $inline)) {
throw new Exception('That comment is not editable!');
}
return $inline;
}
private function canEditInlineComment(
PhabricatorUser $user,
PhabricatorAuditInlineComment $inline) {
// Only the author may edit a comment.
if ($inline->getAuthorPHID() != $user->getPHID()) {
return false;
}
// Saved comments may not be edited.
if ($inline->getAuditCommentID()) {
return false;
}
// Inline must be attached to the active revision.
if ($inline->getCommitPHID() != $this->commitPHID) {
return false;
}
return true;
}
protected function deleteComment(PhabricatorInlineCommentInterface $inline) {
return $inline->delete();
}
protected function saveComment(PhabricatorInlineCommentInterface $inline) {
return $inline->save();
}
}
diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php
index f11fb4a572..4aa0a7d6ee 100644
--- a/src/infrastructure/diff/PhabricatorInlineCommentController.php
+++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php
@@ -1,269 +1,303 @@
<?php
abstract class PhabricatorInlineCommentController
extends PhabricatorController {
abstract protected function createComment();
abstract protected function loadComment($id);
abstract protected function loadCommentForEdit($id);
+ abstract protected function loadCommentByPHID($phid);
abstract protected function deleteComment(
PhabricatorInlineCommentInterface $inline);
abstract protected function saveComment(
PhabricatorInlineCommentInterface $inline);
private $changesetID;
private $isNewFile;
private $isOnRight;
private $lineNumber;
private $lineLength;
private $commentText;
private $operation;
private $commentID;
private $renderer;
+ private $replyToCommentPHID;
public function getCommentID() {
return $this->commentID;
}
public function getOperation() {
return $this->operation;
}
public function getCommentText() {
return $this->commentText;
}
public function getLineLength() {
return $this->lineLength;
}
public function getLineNumber() {
return $this->lineNumber;
}
public function getIsOnRight() {
return $this->isOnRight;
}
public function getChangesetID() {
return $this->changesetID;
}
public function getIsNewFile() {
return $this->isNewFile;
}
public function setRenderer($renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
+ public function setReplyToCommentPHID($phid) {
+ $this->replyToCommentPHID = $phid;
+ return $this;
+ }
+
+ public function getReplyToCommentPHID() {
+ return $this->replyToCommentPHID;
+ }
+
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$this->readRequestParameters();
switch ($this->getOperation()) {
case 'delete':
$inline = $this->loadCommentForEdit($this->getCommentID());
if ($request->isFormPost()) {
$this->deleteComment($inline);
return $this->buildEmptyResponse();
}
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setSubmitURI($request->getRequestURI());
$dialog->setTitle(pht('Really delete this comment?'));
$dialog->addHiddenInput('id', $this->getCommentID());
$dialog->addHiddenInput('op', 'delete');
$dialog->appendChild(
phutil_tag('p', array(), pht('Delete this inline comment?')));
$dialog->addCancelButton('#');
$dialog->addSubmitButton(pht('Delete'));
return id(new AphrontDialogResponse())->setDialog($dialog);
case 'edit':
$inline = $this->loadCommentForEdit($this->getCommentID());
$text = $this->getCommentText();
if ($request->isFormPost()) {
if (strlen($text)) {
$inline->setContent($text);
$this->saveComment($inline);
return $this->buildRenderedCommentResponse(
$inline,
$this->getIsOnRight());
} else {
$this->deleteComment($inline);
return $this->buildEmptyResponse();
}
}
$edit_dialog = $this->buildEditDialog();
$edit_dialog->setTitle(pht('Edit Inline Comment'));
$edit_dialog->addHiddenInput('id', $this->getCommentID());
$edit_dialog->addHiddenInput('op', 'edit');
- $edit_dialog->addHiddenInput('renderer', $this->getRenderer());
$edit_dialog->appendChild(
$this->renderTextArea(
nonempty($text, $inline->getContent())));
return id(new AphrontAjaxResponse())
->setContent($edit_dialog->render());
case 'create':
$text = $this->getCommentText();
if (!$request->isFormPost() || !strlen($text)) {
return $this->buildEmptyResponse();
}
$inline = $this->createComment()
->setChangesetID($this->getChangesetID())
->setAuthorPHID($user->getPHID())
->setLineNumber($this->getLineNumber())
->setLineLength($this->getLineLength())
->setIsNewFile($this->getIsNewFile())
->setContent($text);
+
+ if ($this->getReplyToCommentPHID()) {
+ $inline->setReplyToCommentPHID($this->getReplyToCommentPHID());
+ }
+
$this->saveComment($inline);
return $this->buildRenderedCommentResponse(
$inline,
$this->getIsOnRight());
case 'reply':
default:
$edit_dialog = $this->buildEditDialog();
if ($this->getOperation() == 'reply') {
$inline = $this->loadComment($this->getCommentID());
$edit_dialog->setTitle(pht('Reply to Inline Comment'));
$changeset = $inline->getChangesetID();
$is_new = $inline->getIsNewFile();
$number = $inline->getLineNumber();
$length = $inline->getLineLength();
} else {
$edit_dialog->setTitle(pht('New Inline Comment'));
$changeset = $this->getChangesetID();
$is_new = $this->getIsNewFile();
$number = $this->getLineNumber();
$length = $this->getLineLength();
}
$edit_dialog->addHiddenInput('op', 'create');
- $edit_dialog->addHiddenInput('changeset', $changeset);
$edit_dialog->addHiddenInput('is_new', $is_new);
$edit_dialog->addHiddenInput('number', $number);
$edit_dialog->addHiddenInput('length', $length);
- $edit_dialog->addHiddenInput('renderer', $this->getRenderer());
$text_area = $this->renderTextArea($this->getCommentText());
$edit_dialog->appendChild($text_area);
return id(new AphrontAjaxResponse())
->setContent($edit_dialog->render());
}
}
private function readRequestParameters() {
$request = $this->getRequest();
// NOTE: This isn't necessarily a DifferentialChangeset ID, just an
// application identifier for the changeset. In Diffusion, it's a Path ID.
- $this->changesetID = $request->getInt('changeset');
+ $this->changesetID = $request->getInt('changesetID');
$this->isNewFile = (int)$request->getBool('is_new');
$this->isOnRight = $request->getBool('on_right');
$this->lineNumber = $request->getInt('number');
$this->lineLength = $request->getInt('length');
$this->commentText = $request->getStr('text');
$this->commentID = $request->getInt('id');
$this->operation = $request->getStr('op');
$this->renderer = $request->getStr('renderer');
+ $this->replyToCommentPHID = $request->getStr('replyToCommentPHID');
+
+ if ($this->getReplyToCommentPHID()) {
+ $reply_phid = $this->getReplyToCommentPHID();
+ $reply_comment = $this->loadCommentByPHID($reply_phid);
+ if (!$reply_comment) {
+ throw new Exception(
+ pht('Failed to load comment "%s".', $reply_phid));
+ }
+
+ if ($reply_comment->getChangesetID() != $this->getChangesetID()) {
+ throw new Exception(
+ pht(
+ 'Comment "%s" belongs to wrong changeset (%s vs %s).',
+ $reply_phid,
+ $reply_comment->getChangesetID(),
+ $this->getChangesetID()));
+ }
+ }
}
private function buildEditDialog() {
$request = $this->getRequest();
$user = $request->getUser();
$edit_dialog = id(new PHUIDiffInlineCommentEditView())
->setUser($user)
->setSubmitURI($request->getRequestURI())
->setOnRight($this->getIsOnRight())
->setIsNewFile($this->getIsNewFile())
->setNumber($this->getLineNumber())
->setLength($this->getLineLength())
- ->setRenderer($this->getRenderer());
+ ->setRenderer($this->getRenderer())
+ ->setReplyToCommentPHID($this->getReplyToCommentPHID())
+ ->setChangesetID($this->getChangesetID());
return $edit_dialog;
}
private function buildEmptyResponse() {
return id(new AphrontAjaxResponse())
->setContent(
array(
'markup' => '',
));
}
private function buildRenderedCommentResponse(
PhabricatorInlineCommentInterface $inline,
$on_right) {
$request = $this->getRequest();
$user = $request->getUser();
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
$engine->process();
$phids = array($user->getPHID());
$handles = $this->loadViewerHandles($phids);
$view = id(new PHUIDiffInlineCommentDetailView())
->setInlineComment($inline)
->setOnRight($on_right)
->setMarkupEngine($engine)
->setHandles($handles)
->setEditable(true);
$renderer = DifferentialChangesetHTMLRenderer::getHTMLRendererByKey(
$this->getRenderer());
$view = $renderer->getRowScaffoldForInline($view);
$view = id(new PHUIDiffInlineCommentTableScaffold())
->addRowScaffold($view);
return id(new AphrontAjaxResponse())
->setContent(
array(
'inlineCommentID' => $inline->getID(),
'markup' => $view->render(),
));
}
private function renderTextArea($text) {
return id(new PhabricatorRemarkupControl())
->setUser($this->getRequest()->getUser())
->setSigil('differential-inline-comment-edit-textarea')
->setName('text')
->setValue($text)
->setDisableFullScreen(true);
}
}
diff --git a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php
index 55295fd2e3..01914e03db 100644
--- a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php
+++ b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php
@@ -1,40 +1,46 @@
<?php
/**
* Shared interface used by Differential and Diffusion inline comments.
*/
interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface {
const MARKUP_FIELD_BODY = 'markup:body';
public function setChangesetID($id);
public function getChangesetID();
public function setIsNewFile($is_new);
public function getIsNewFile();
public function setLineNumber($number);
public function getLineNumber();
public function setLineLength($length);
public function getLineLength();
+ public function setReplyToCommentPHID($phid);
+ public function getReplyToCommentPHID();
+
+ public function setHasReplies($has_replies);
+ public function getHasReplies();
+
public function setContent($content);
public function getContent();
public function setCache($cache);
public function getCache();
public function setAuthorPHID($phid);
public function getAuthorPHID();
public function setSyntheticAuthor($synthetic_author);
public function getSyntheticAuthor();
public function isCompatible(PhabricatorInlineCommentInterface $inline);
public function isDraft();
public function save();
public function delete();
}
diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
index e21f431994..de85a44a93 100644
--- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
+++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
@@ -1,259 +1,271 @@
<?php
final class PHUIDiffInlineCommentDetailView
extends PHUIDiffInlineCommentView {
private $inlineComment;
private $onRight;
private $handles;
private $markupEngine;
private $editable;
private $preview;
private $allowReply;
private $renderer;
public function getIsOnRight() {
return $this->onRight;
}
public function setInlineComment(PhabricatorInlineCommentInterface $comment) {
$this->inlineComment = $comment;
return $this;
}
public function setOnRight($on_right) {
$this->onRight = $on_right;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setAllowReply($allow_reply) {
$this->allowReply = $allow_reply;
return $this;
}
public function setRenderer($renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
public function render() {
$inline = $this->inlineComment;
$start = $inline->getLineNumber();
$length = $inline->getLineLength();
if ($length) {
$end = $start + $length;
$line = 'Lines '.number_format($start).'-'.number_format($end);
} else {
$line = 'Line '.number_format($start);
}
$metadata = array(
'id' => $inline->getID(),
+ 'phid' => $inline->getPHID(),
+ 'changesetID' => $inline->getChangesetID(),
'number' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'isNewFile' => (bool)$inline->getIsNewFile(),
'on_right' => $this->onRight,
'original' => $inline->getContent(),
+ 'replyToCommentPHID' => $inline->getReplyToCommentPHID(),
);
$sigil = 'differential-inline-comment';
if ($this->preview) {
$sigil = $sigil.' differential-inline-comment-preview';
}
$content = $inline->getContent();
$handles = $this->handles;
$links = array();
$is_synthetic = false;
if ($inline->getSyntheticAuthor()) {
$is_synthetic = true;
}
$is_draft = false;
if ($inline->isDraft() && !$is_synthetic) {
$links[] = pht('Not Submitted Yet');
$is_draft = true;
}
+
+ // TODO: This stuff is nonfinal, just making it do something.
+ if ($inline->getHasReplies()) {
+ $links[] = pht('Has Reply');
+ }
+ if ($inline->getReplyToCommentPHID()) {
+ $links[] = pht('Is Reply');
+ }
+
if (!$this->preview) {
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-prev',
),
pht('Previous'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-next',
),
pht('Next'));
if ($this->allowReply) {
if (!$is_synthetic) {
// NOTE: No product reason why you can't reply to these, but the reply
// mechanism currently sends the inline comment ID to the server, not
// file/line information, and synthetic comments don't have an inline
// comment ID.
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-reply',
),
pht('Reply'));
}
}
}
$anchor_name = 'inline-'.$inline->getID();
if ($this->editable && !$this->preview) {
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-edit',
),
pht('Edit'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
} else if ($this->preview) {
$links[] = javelin_tag(
'a',
array(
'meta' => array(
'anchor' => $anchor_name,
),
'sigil' => 'differential-inline-preview-jump',
),
pht('Not Visible'));
$links[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'differential-inline-delete',
),
pht('Delete'));
}
if ($links) {
$links = phutil_tag(
'span',
array('class' => 'differential-inline-comment-links'),
phutil_implode_html(" \xC2\xB7 ", $links));
} else {
$links = null;
}
$content = $this->markupEngine->getOutput(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
if ($this->preview) {
$anchor = null;
} else {
$anchor = phutil_tag(
'a',
array(
'name' => $anchor_name,
'id' => $anchor_name,
'class' => 'differential-inline-comment-anchor',
),
'');
}
$classes = array(
'differential-inline-comment',
);
if ($is_draft) {
$classes[] = 'differential-inline-comment-unsaved-draft';
}
if ($is_synthetic) {
$classes[] = 'differential-inline-comment-synthetic';
}
$classes = implode(' ', $classes);
if ($is_synthetic) {
$author = $inline->getSyntheticAuthor();
} else {
$author = $handles[$inline->getAuthorPHID()]->getName();
}
$line = phutil_tag(
'span',
array('class' => 'differential-inline-comment-line'),
$line);
$markup = javelin_tag(
'div',
array(
'class' => $classes,
'sigil' => $sigil,
'meta' => $metadata,
),
array(
phutil_tag_div('differential-inline-comment-head', array(
$anchor,
$links,
' ',
$line,
' ',
$author,
)),
phutil_tag_div(
'differential-inline-comment-content',
phutil_tag_div('phabricator-remarkup', $content)),
));
return $markup;
}
}
diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
index 47569dab3a..47710a85e0 100644
--- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
+++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
@@ -1,186 +1,214 @@
<?php
final class PHUIDiffInlineCommentEditView
extends PHUIDiffInlineCommentView {
private $inputs = array();
private $uri;
private $title;
private $onRight;
private $number;
private $length;
private $renderer;
private $isNewFile;
+ private $replyToCommentPHID;
+ private $changesetID;
public function setIsNewFile($is_new_file) {
$this->isNewFile = $is_new_file;
return $this;
}
public function getIsNewFile() {
return $this->isNewFile;
}
public function getIsOnRight() {
return $this->onRight;
}
public function setRenderer($renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
public function addHiddenInput($key, $value) {
$this->inputs[] = array($key, $value);
return $this;
}
public function setSubmitURI($uri) {
$this->uri = $uri;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
+ public function setReplyToCommentPHID($reply_to_phid) {
+ $this->replyToCommentPHID = $reply_to_phid;
+ return $this;
+ }
+
+ public function getReplyToCommentPHID() {
+ return $this->replyToCommentPHID;
+ }
+
+ public function setChangesetID($changeset_id) {
+ $this->changesetID = $changeset_id;
+ return $this;
+ }
+
+ public function getChangesetID() {
+ return $this->changesetID;
+ }
+
public function setOnRight($on_right) {
$this->onRight = $on_right;
- $this->addHiddenInput('on_right', $on_right);
return $this;
}
public function setNumber($number) {
$this->number = $number;
return $this;
}
public function setLength($length) {
$this->length = $length;
return $this;
}
public function render() {
if (!$this->uri) {
throw new Exception('Call setSubmitURI() before render()!');
}
if (!$this->user) {
throw new Exception('Call setUser() before render()!');
}
$content = phabricator_form(
$this->user,
array(
'action' => $this->uri,
'method' => 'POST',
'sigil' => 'inline-edit-form',
),
array(
$this->renderInputs(),
$this->renderBody(),
));
if ($this->renderer == '1up') {
$cells = array(
phutil_tag('th', array()),
phutil_tag('th', array()),
phutil_tag(
'td',
array('colspan' => 3, 'class' => 'right3'),
$content),
);
} else {
$cells = array(
phutil_tag('th', array()),
phutil_tag(
'td',
array('class' => 'left'),
$this->onRight ? null : $content),
phutil_tag('th', array()),
phutil_tag(
'td',
array('colspan' => 3, 'class' => 'right3'),
$this->onRight ? $content : null),
);
}
$row = phutil_tag('tr', array('class' => 'inline-comment-splint'), $cells);
return phutil_tag('table', array(), $row);
}
private function renderInputs() {
+ $inputs = $this->inputs;
$out = array();
- foreach ($this->inputs as $input) {
+
+ $inputs[] = array('on_right', (bool)$this->getIsOnRight());
+ $inputs[] = array('replyToCommentPHID', $this->getReplyToCommentPHID());
+ $inputs[] = array('renderer', $this->getRenderer());
+ $inputs[] = array('changesetID', $this->getChangesetID());
+
+ foreach ($inputs as $input) {
list($name, $value) = $input;
$out[] = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => $name,
'value' => $value,
));
}
return $out;
}
private function renderBody() {
$buttons = array();
$buttons[] = phutil_tag('button', array(), pht('Ready'));
$buttons[] = javelin_tag(
'button',
array(
'sigil' => 'inline-edit-cancel',
'class' => 'grey',
),
pht('Cancel'));
$title = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-title',
),
$this->title);
$body = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-body',
),
$this->renderChildren());
$edit = phutil_tag(
'div',
array(
'class' => 'differential-inline-comment-edit-buttons',
),
array(
$buttons,
phutil_tag('div', array('style' => 'clear: both'), ''),
));
return javelin_tag(
'div',
array(
'class' => 'differential-inline-comment-edit',
'sigil' => 'differential-inline-comment',
'meta' => array(
+ 'changesetID' => $this->getChangesetID(),
'on_right' => $this->getIsOnRight(),
'isNewFile' => (bool)$this->getIsNewFile(),
'number' => $this->number,
'length' => $this->length,
+ 'replyToCommentPHID' => $this->getReplyToCommentPHID(),
),
),
array(
$title,
$body,
$edit,
));
}
}
diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
index c3e0dd9bd4..98b2745633 100644
--- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
+++ b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
@@ -1,296 +1,298 @@
/**
* @provides differential-inline-comment-editor
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* javelin-request
* javelin-workflow
*/
JX.install('DifferentialInlineCommentEditor', {
construct : function(uri) {
this._uri = uri;
},
events : ['done'],
members : {
_uri : null,
_undoText : null,
_skipOverInlineCommentRows : function(node) {
// TODO: Move this semantic information out of class names.
while (node && node.className.indexOf('inline') !== -1) {
node = node.nextSibling;
}
return node;
},
_buildRequestData : function() {
return {
op : this.getOperation(),
on_right : this.getOnRight(),
id : this.getID(),
number : this.getLineNumber(),
is_new : this.getIsNew(),
length : this.getLength(),
- changeset : this.getChangeset(),
+ changesetID : this.getChangesetID(),
text : this.getText() || '',
- renderer: this.getRenderer()
+ renderer: this.getRenderer(),
+ replyToCommentPHID: this.getReplyToCommentPHID() || '',
};
},
_draw : function(content, exact_row) {
var row = this.getRow();
var table = this.getTable();
var target = exact_row ? row : this._skipOverInlineCommentRows(row);
function copyRows(dst, src, before) {
var rows = JX.DOM.scry(src, 'tr');
for (var ii = 0; ii < rows.length; ii++) {
// Find the table this <tr /> belongs to. If it's a sub-table, like a
// table in an inline comment, don't copy it.
if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
continue;
}
if (before) {
dst.insertBefore(rows[ii], before);
} else {
dst.appendChild(rows[ii]);
}
}
return rows;
}
return copyRows(table, content, target);
},
_removeUndoLink : function() {
var rows = JX.DifferentialInlineCommentEditor._undoRows;
if (rows) {
for (var ii = 0; ii < rows.length; ii++) {
JX.DOM.remove(rows[ii]);
}
}
},
_undo : function() {
this._removeUndoLink();
this.setText(this._undoText);
this.start();
},
_registerUndoListener : function() {
if (!JX.DifferentialInlineCommentEditor._activeEditor) {
JX.Stratcom.listen(
'click',
'differential-inline-comment-undo',
function(e) {
JX.DifferentialInlineCommentEditor._activeEditor._undo();
e.kill();
});
}
JX.DifferentialInlineCommentEditor._activeEditor = this;
},
_setRowState : function(state) {
var is_hidden = (state == 'hidden');
var is_loading = (state == 'loading');
var row = this.getRow();
JX.DOM.alterClass(row, 'differential-inline-hidden', is_hidden);
JX.DOM.alterClass(row, 'differential-inline-loading', is_loading);
},
_didContinueWorkflow : function(response) {
var drawn = this._draw(JX.$H(response).getNode());
var op = this.getOperation();
if (op == 'edit') {
this._setRowState('hidden');
}
JX.DOM.find(
drawn[0],
'textarea',
'differential-inline-comment-edit-textarea').focus();
var oncancel = JX.bind(this, function(e) {
e.kill();
this._didCancelWorkflow();
if (op == 'edit') {
this._setRowState('visible');
}
JX.DOM.remove(drawn[0]);
});
JX.DOM.listen(drawn[0], 'click', 'inline-edit-cancel', oncancel);
var onsubmit = JX.bind(this, function(e) {
e.kill();
JX.Workflow.newFromForm(e.getTarget())
.setHandler(JX.bind(this, function(response) {
JX.DOM.remove(drawn[0]);
if (op == 'edit') {
this._setRowState('visible');
}
this._didCompleteWorkflow(response);
}))
.start();
JX.DOM.alterClass(drawn[0], 'differential-inline-loading', true);
});
JX.DOM.listen(
drawn[0],
['submit', 'didSyntheticSubmit'],
'inline-edit-form',
onsubmit);
},
_didCompleteWorkflow : function(response) {
var op = this.getOperation();
// We don't get any markup back if the user deletes a comment, or saves
// an empty comment (which effects a delete).
if (response.markup) {
this._draw(JX.$H(response.markup).getNode());
}
// These operations remove the old row (edit adds a new row first).
var remove_old = (op == 'edit' || op == 'delete');
if (remove_old) {
JX.DOM.remove(this.getRow());
var other_rows = this.getOtherRows();
for(var i = 0; i < other_rows.length; ++i) {
JX.DOM.remove(other_rows[i]);
}
}
// Once the user saves something, get rid of the 'undo' option. A
// particular case where we need this is saving a delete, when we might
// otherwise leave around an 'undo' for an earlier edit to the same
// comment.
this._removeUndoLink();
JX.Stratcom.invoke('differential-inline-comment-update');
this.invoke('done');
},
_didCancelWorkflow : function() {
this.invoke('done');
var op = this.getOperation();
if (op == 'delete') {
// No undo for delete, we prompt the user explicitly.
return;
}
var textarea;
try {
textarea = JX.DOM.find(
document.body, // TODO: use getDialogRootNode() when available
'textarea',
'differential-inline-comment-edit-textarea');
} catch (ex) {
// The close handler is called whenever the dialog closes, even if the
// user closed it by completing the workflow with "Save". The
// JX.Workflow API should probably be refined to allow programmatic
// distinction of close caused by 'cancel' vs 'submit'. Testing for
// presence of the textarea serves as a proxy for detecting a 'cancel'.
return;
}
var text = textarea.value;
// If the user hasn't edited the text (i.e., no change from original for
// 'edit' or no text at all), don't offer them an undo.
if (text == this.getOriginalText() || text === '') {
return;
}
// Save the text so we can 'undo' back to it.
this._undoText = text;
var templates = this.getTemplates();
var template = this.getOnRight() ? templates.r : templates.l;
template = JX.$H(template).getNode();
// NOTE: Operation order matters here; we can't remove anything until
// after we draw the new rows because _draw uses the old rows to figure
// out where to place the comment.
// We use 'exact_row' to put the "undo" text directly above the affected
// comment.
var exact_row = true;
var rows = this._draw(template, exact_row);
this._removeUndoLink();
JX.DifferentialInlineCommentEditor._undoRows = rows;
},
start : function() {
this._registerUndoListener();
var data = this._buildRequestData();
var op = this.getOperation();
if (op == 'delete') {
this._setRowState('loading');
var oncomplete = JX.bind(this, this._didCompleteWorkflow);
var onclose = JX.bind(this, function() {
this._setRowState('visible');
this._didCancelWorkflow();
});
new JX.Workflow(this._uri, data)
.setHandler(oncomplete)
.setCloseHandler(onclose)
.start();
} else {
var handler = JX.bind(this, this._didContinueWorkflow);
if (op == 'edit') {
this._setRowState('loading');
}
new JX.Request(this._uri, handler)
.setData(data)
.send();
}
return this;
}
},
statics : {
/**
* Global refernece to the 'undo' rows currently rendered in the document.
*/
_undoRows : null,
/**
* Global listener for the 'undo' click associated with the currently
* displayed 'undo' link. When an editor is start()ed, it becomes the active
* editor.
*/
_activeEditor : null
},
properties : {
operation : null,
row : null,
otherRows: [],
table : null,
onRight : null,
ID : null,
lineNumber : null,
- changeset : null,
+ changesetID : null,
length : null,
isNew : null,
text : null,
templates : null,
originalText : null,
- renderer: null
+ renderer: null,
+ replyToCommentPHID: null
}
});
diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
index 98b2a50892..853bff03fc 100644
--- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
+++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js
@@ -1,360 +1,371 @@
/**
* @provides javelin-behavior-differential-edit-inline-comments
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* javelin-util
* javelin-vector
* differential-inline-comment-editor
*/
JX.behavior('differential-edit-inline-comments', function(config) {
var selecting = false;
var reticle = JX.$N('div', {className: 'differential-reticle'});
var old_cells = [];
JX.DOM.hide(reticle);
var origin = null;
var target = null;
var root = null;
var changeset = null;
var editor = null;
function updateReticle() {
JX.DOM.getContentFrame().appendChild(reticle);
var top = origin;
var bot = target;
if (JX.$V(top).y > JX.$V(bot).y) {
var tmp = top;
top = bot;
bot = tmp;
}
// Find the leftmost cell that we're going to highlight: this is the next
// <td /> in the row. In 2up views, it should be directly adjacent. In
// 1up views, we may have to skip over the other line number column.
var l = top;
while (JX.DOM.isType(l, 'th')) {
l = l.nextSibling;
}
// Find the rightmost cell that we're going to highlight: this is the
// farthest consecutive, adjacent <td /> in the row. Sometimes the left
// and right nodes are the same (left side of 2up view); sometimes we're
// going to highlight several nodes (copy + code + coverage).
var r = l;
while (r.nextSibling && JX.DOM.isType(r.nextSibling, 'td')) {
r = r.nextSibling;
}
var pos = JX.$V(l)
.add(JX.Vector.getAggregateScrollForNode(l));
var dim = JX.$V(r)
.add(JX.Vector.getAggregateScrollForNode(r))
.add(-pos.x, -pos.y)
.add(JX.Vector.getDim(r));
var bpos = JX.$V(bot)
.add(JX.Vector.getAggregateScrollForNode(bot));
dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y;
pos.setPos(reticle);
dim.setDim(reticle);
JX.DOM.show(reticle);
// Find all the cells in the same row position between the top and bottom
// cell, so we can highlight them.
var seq = 0;
var row = top.parentNode;
for (seq = 0; seq < row.childNodes.length; seq++) {
if (row.childNodes[seq] == top) {
break;
}
}
var cells = [];
while (true) {
cells.push(row.childNodes[seq]);
if (row.childNodes[seq] == bot) {
break;
}
row = row.nextSibling;
}
setSelectedCells(cells);
}
function setSelectedCells(new_cells) {
updateSelectedCellsClass(old_cells, false);
updateSelectedCellsClass(new_cells, true);
old_cells = new_cells;
}
function updateSelectedCellsClass(cells, selected) {
for (var ii = 0; ii < cells.length; ii++) {
JX.DOM.alterClass(cells[ii], 'selected', selected);
}
}
function hideReticle() {
JX.DOM.hide(reticle);
setSelectedCells([]);
}
JX.DifferentialInlineCommentEditor.listen('done', function() {
selecting = false;
editor = false;
hideReticle();
set_link_state(false);
});
function isOnRight(node) {
return node.parentNode.firstChild != node;
}
function isNewFile(node) {
var data = JX.Stratcom.getData(root);
return isOnRight(node) || (data.left != data.right);
}
function getRowNumber(th_node) {
try {
return parseInt(th_node.id.match(/^C\d+[ON]L(\d+)$/)[1], 10);
} catch (x) {
return undefined;
}
}
var set_link_state = function(active) {
JX.DOM.alterClass(JX.$(config.stage), 'inline-editor-active', active);
};
JX.Stratcom.listen(
'mousedown',
['differential-changeset', 'tag:th'],
function(e) {
if (editor ||
selecting ||
e.isRightButton() ||
getRowNumber(e.getTarget()) === undefined) {
return;
}
selecting = true;
root = e.getNode('differential-changeset');
origin = target = e.getTarget();
var data = e.getNodeData('differential-changeset');
if (isOnRight(target)) {
changeset = data.right;
} else {
changeset = data.left;
}
updateReticle();
e.kill();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
['differential-changeset', 'tag:th'],
function(e) {
if (editor) {
// Don't update the reticle if we're editing a comment, since this
// would be distracting and we want to keep the lines corresponding
// to the comment highlighted during the edit.
return;
}
if (getRowNumber(e.getTarget()) === undefined) {
// Don't update the reticle if this "<th />" doesn't correspond to a
// line number. For instance, this may be a dead line number, like the
// empty line numbers on the left hand side of a newly added file.
return;
}
if (selecting) {
if (isOnRight(e.getTarget()) != isOnRight(origin)) {
// Don't update the reticle if we're selecting a line range and the
// "<th />" under the cursor is on the wrong side of the file. You
// can only leave inline comments on the left or right side of a
// file, not across lines on both sides.
return;
}
if (e.getNode('differential-changeset') !== root) {
// Don't update the reticle if we're selecting a line range and
// the "<th />" under the cursor corresponds to a different file.
// You can only leave inline comments on lines in a single file,
// not across multiple files.
return;
}
}
if (e.getType() == 'mouseout') {
if (selecting) {
// Don't hide the reticle if we're selecting, since we want to
// keep showing the line range that will be used if the mouse is
// released.
return;
}
hideReticle();
} else {
target = e.getTarget();
if (!selecting) {
// If we're just hovering the mouse and not selecting a line range,
// set the origin to the current row so we highlight it.
origin = target;
}
updateReticle();
}
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (editor || !selecting) {
return;
}
var o = getRowNumber(origin);
var t = getRowNumber(target);
var insert;
var len;
if (t < o) {
len = (o - t);
o = t;
insert = origin.parentNode;
} else {
len = (t - o);
insert = target.parentNode;
}
var view = JX.ChangesetViewManager.getForNode(root);
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation('new')
- .setChangeset(changeset)
+ .setChangesetID(changeset)
.setLineNumber(o)
.setLength(len)
.setIsNew(isNewFile(target) ? 1 : 0)
.setOnRight(isOnRight(target) ? 1 : 0)
.setRow(insert.nextSibling)
.setTable(insert.parentNode)
.setRenderer(view.getRenderer())
.start();
set_link_state(true);
e.kill();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'differential-inline-comment',
function(e) {
if (e.getType() == 'mouseout') {
hideReticle();
} else {
root = e.getNode('differential-changeset');
if (root) {
var data = e.getNodeData('differential-inline-comment');
var change = e.getNodeData('differential-changeset');
var id_part = data.on_right ? change.right : change.left;
var new_part = data.isNewFile ? 'N' : 'O';
var prefix = 'C' + id_part + new_part + 'L';
origin = JX.$(prefix + data.number);
target = JX.$(prefix + (parseInt(data.number, 10) +
parseInt(data.length, 10)));
updateReticle();
}
}
});
var action_handler = function(op, e) {
e.kill();
if (editor) {
return;
}
var node = e.getNode('differential-inline-comment');
handle_inline_action(node, op);
};
var handle_inline_action = function(node, op) {
var data = JX.Stratcom.getData(node);
var row = node.parentNode.parentNode;
var other_rows = [];
if (JX.Stratcom.hasSigil(node, 'differential-inline-comment-preview')) {
// The DOM structure around the comment is different if it's part of the
// preview, so make sure not to pass the wrong container.
row = node;
if (op === 'delete') {
// Furthermore, deleting a comment in the preview does not automatically
// delete other occurrences of the same comment, so do that manually.
var nodes = JX.DOM.scry(
JX.DOM.getContentFrame(),
'div',
'differential-inline-comment');
for (var i = 0; i < nodes.length; ++i) {
if (JX.Stratcom.getData(nodes[i]).id === data.id) {
other_rows.push(nodes[i]);
}
}
}
}
var original = data.original;
+ var reply_phid = null;
if (op == 'reply') {
// If the user hit "reply", the original text is empty (a new reply), not
// the text of the comment they're replying to.
original = '';
+ reply_phid = data.phid;
}
+ var changeset_root = JX.DOM.findAbove(
+ node,
+ 'div',
+ 'differential-changeset');
+ var view = JX.ChangesetViewManager.getForNode(changeset_root);
+
editor = new JX.DifferentialInlineCommentEditor(config.uri)
.setTemplates(config.undo_templates)
.setOperation(op)
.setID(data.id)
+ .setChangesetID(data.changesetID)
.setLineNumber(data.number)
.setLength(data.length)
.setOnRight(data.on_right)
.setOriginalText(original)
.setRow(row)
.setOtherRows(other_rows)
.setTable(row.parentNode)
+ .setReplyToCommentPHID(reply_phid)
+ .setRenderer(view.getRenderer())
.start();
set_link_state(true);
};
for (var op in {'edit' : 1, 'delete' : 1, 'reply' : 1}) {
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-' + op],
JX.bind(null, action_handler, op));
}
JX.Stratcom.listen(
'differential-inline-action',
null,
function(e) {
var data = e.getData();
handle_inline_action(data.node, data.op);
});
});

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 23:12 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129742
Default Alt Text
(67 KB)

Event Timeline