Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2896499
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
67 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment