1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 21:02:41 +01:00

Allow inline comments to be individually hidden

Summary:
Ref T7447. Implements per-viewer comment hiding. Once a comment is obsolete or uninteresting, you can hide it completely.

This is sticky per-user.

My hope is that this will strike a better balance between concerns than some of the other approaches (conservative porting, summarization, hide-all).

Specifically, this adds a new action here:

{F435621}

Clicking it completely collapses the comment into a small icon on the previous line, and saves the comment state as hidden for you:

{F435626}

You can click the icon to reveal all hidden comments below the line.

Test Plan:
  - Hid comments.
  - Showed comments.
  - Created, edited, deleted and submitted comments.
  - Used Diffusion comments (hiding is not implemented there yet, but I'd plan to bring it there eventually if it works out in Differential).

Reviewers: btrahan, chad

Reviewed By: btrahan

Subscribers: jparise, yelirekim, epriestley

Maniphest Tasks: T7447

Differential Revision: https://secure.phabricator.com/D13009
This commit is contained in:
epriestley 2015-05-27 10:28:38 -07:00
parent fcac85d807
commit e9f4a84a89
26 changed files with 454 additions and 65 deletions

View file

@ -10,8 +10,8 @@ return array(
'core.pkg.css' => '439658b5', 'core.pkg.css' => '439658b5',
'core.pkg.js' => '328799d0', 'core.pkg.js' => '328799d0',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'bb338e4b', 'differential.pkg.css' => '30602b8c',
'differential.pkg.js' => '63a77807', 'differential.pkg.js' => '8c98ce21',
'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => '0115b37c', 'diffusion.pkg.js' => '0115b37c',
'maniphest.pkg.css' => '68d4dd3d', 'maniphest.pkg.css' => '68d4dd3d',
@ -60,7 +60,7 @@ return array(
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
'rsrc/css/application/differential/changeset-view.css' => 'e19cfd6e', 'rsrc/css/application/differential/changeset-view.css' => 'e19cfd6e',
'rsrc/css/application/differential/core.css' => '7ac3cabc', 'rsrc/css/application/differential/core.css' => '7ac3cabc',
'rsrc/css/application/differential/phui-inline-comment.css' => '2174771a', 'rsrc/css/application/differential/phui-inline-comment.css' => 'aa16f165',
'rsrc/css/application/differential/results-table.css' => '181aa9d9', 'rsrc/css/application/differential/results-table.css' => '181aa9d9',
'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855',
@ -353,7 +353,7 @@ return array(
'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76',
'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1',
'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb',
'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e723c323', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '037b59eb',
'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492',
'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df',
'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf', 'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf',
@ -443,7 +443,7 @@ return array(
'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-device.js' => 'a205cf28',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e',
'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-error-log.js' => '6882e80a',
'rsrc/js/core/behavior-fancy-datepicker.js' => '2d4029a8', 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f',
'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-file-tree.js' => '88236f00',
'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-form.js' => '5c54cbf3',
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
@ -560,7 +560,7 @@ return array(
'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-comment-jump' => '4fdb476d',
'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
'javelin-behavior-differential-dropdown-menus' => '2035b9cb', 'javelin-behavior-differential-dropdown-menus' => '2035b9cb',
'javelin-behavior-differential-edit-inline-comments' => 'e723c323', 'javelin-behavior-differential-edit-inline-comments' => '037b59eb',
'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-feedback-preview' => 'b064af76',
'javelin-behavior-differential-keyboard-navigation' => '2c426492', 'javelin-behavior-differential-keyboard-navigation' => '2c426492',
'javelin-behavior-differential-populate' => '8694b1df', 'javelin-behavior-differential-populate' => '8694b1df',
@ -576,7 +576,7 @@ return array(
'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-durable-column' => '16c695bf',
'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-event-all-day' => '38dcf3c8', 'javelin-behavior-event-all-day' => '38dcf3c8',
'javelin-behavior-fancy-datepicker' => '2d4029a8', 'javelin-behavior-fancy-datepicker' => '5c0f680f',
'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-global-drag-and-drop' => 'c8e57404',
'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => 'a464fe03', 'javelin-behavior-high-security-warning' => 'a464fe03',
@ -782,7 +782,7 @@ return array(
'phui-image-mask-css' => '5a8b09c8', 'phui-image-mask-css' => '5a8b09c8',
'phui-info-panel-css' => '27ea50a1', 'phui-info-panel-css' => '27ea50a1',
'phui-info-view-css' => 'c6f0aef8', 'phui-info-view-css' => 'c6f0aef8',
'phui-inline-comment-view-css' => '2174771a', 'phui-inline-comment-view-css' => 'aa16f165',
'phui-list-view-css' => '2e25ebfb', 'phui-list-view-css' => '2e25ebfb',
'phui-object-box-css' => '7d160002', 'phui-object-box-css' => '7d160002',
'phui-object-item-list-view-css' => 'f3a22696', 'phui-object-item-list-view-css' => 'f3a22696',
@ -830,6 +830,14 @@ return array(
'029a133d' => array( '029a133d' => array(
'aphront-dialog-view-css', 'aphront-dialog-view-css',
), ),
'037b59eb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
'javelin-vector',
'differential-inline-comment-editor',
),
'048330fa' => array( '048330fa' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-typeahead-ondemand-source', 'javelin-typeahead-ondemand-source',
@ -1042,13 +1050,6 @@ return array(
'javelin-install', 'javelin-install',
'javelin-event', 'javelin-event',
), ),
'2d4029a8' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'331b1611' => array( '331b1611' => array(
'javelin-install', 'javelin-install',
), ),
@ -1241,6 +1242,13 @@ return array(
'javelin-uri', 'javelin-uri',
'javelin-routable', 'javelin-routable',
), ),
'5c0f680f' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'5c54cbf3' => array( '5c54cbf3' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1925,14 +1933,6 @@ return array(
'e6e25838' => array( 'e6e25838' => array(
'javelin-install', 'javelin-install',
), ),
'e723c323' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
'javelin-vector',
'differential-inline-comment-editor',
),
'e9581f08' => array( 'e9581f08' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_differential.differential_hiddencomment (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
userPHID VARBINARY(64) NOT NULL,
commentID INT UNSIGNED NOT NULL,
UNIQUE KEY `key_user` (userPHID, commentID),
KEY `key_comment` (commentID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -374,6 +374,7 @@ phutil_register_library_map(array(
'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php',
'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php',
'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php',
'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php',
'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php',
'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php',
'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php',
@ -1178,6 +1179,7 @@ phutil_register_library_map(array(
'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php',
'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php',
'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php',
'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php',
'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php',
'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php',
@ -3616,6 +3618,7 @@ phutil_register_library_map(array(
'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy',
'DifferentialGitSVNIDField' => 'DifferentialCustomField', 'DifferentialGitSVNIDField' => 'DifferentialCustomField',
'DifferentialHiddenComment' => 'DifferentialDAO',
'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHostField' => 'DifferentialCustomField',
'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy',
'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy',
@ -4506,6 +4509,7 @@ phutil_register_library_map(array(
'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffInlineCommentView' => 'AphrontView',
'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDiffRevealIconView' => 'AphrontView',
'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold',
'PHUIDocumentExample' => 'PhabricatorUIExample', 'PHUIDocumentExample' => 'PhabricatorUIExample',
'PHUIDocumentView' => 'AphrontTagView', 'PHUIDocumentView' => 'AphrontTagView',

View file

@ -23,6 +23,14 @@ final class PhabricatorAuditInlineComment
return $this->proxy; return $this->proxy;
} }
public function supportsHiding() {
return false;
}
public function isHidden() {
return false;
}
public function getTransactionCommentForSave() { public function getTransactionCommentForSave() {
$content_source = PhabricatorContentSource::newForSource( $content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_LEGACY, PhabricatorContentSource::SOURCE_LEGACY,

View file

@ -191,6 +191,7 @@ final class DifferentialChangesetViewController extends DifferentialController {
if ($revision) { if ($revision) {
$query = id(new DifferentialInlineCommentQuery()) $query = id(new DifferentialInlineCommentQuery())
->setViewer($viewer) ->setViewer($viewer)
->needHidden(true)
->withRevisionPHIDs(array($revision->getPHID())); ->withRevisionPHIDs(array($revision->getPHID()));
$inlines = $query->execute(); $inlines = $query->execute();
$inlines = $query->adjustInlinesForChangesets( $inlines = $query->adjustInlinesForChangesets(

View file

@ -42,6 +42,7 @@ final class DifferentialInlineCommentEditController
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withIDs(array($id)) ->withIDs(array($id))
->withDeletedDrafts(true) ->withDeletedDrafts(true)
->needHidden(true)
->executeOne(); ->executeOne();
} }
@ -50,6 +51,7 @@ final class DifferentialInlineCommentEditController
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withPHIDs(array($phid)) ->withPHIDs(array($phid))
->withDeletedDrafts(true) ->withDeletedDrafts(true)
->needHidden(true)
->executeOne(); ->executeOne();
} }
@ -152,4 +154,38 @@ final class DifferentialInlineCommentEditController
return $this->loadRevision()->getAuthorPHID(); return $this->loadRevision()->getAuthorPHID();
} }
protected function hideComments(array $ids) {
$viewer = $this->getViewer();
$table = new DifferentialHiddenComment();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($ids as $id) {
$sql[] = qsprintf(
$conn_w,
'(%s, %d)',
$viewer->getPHID(),
$id);
}
queryfx(
$conn_w,
'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q',
$table->getTableName(),
implode(', ', $sql));
}
protected function showComments(array $ids) {
$viewer = $this->getViewer();
$table = new DifferentialHiddenComment();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE userPHID = %s AND commentID IN (%Ld)',
$table->getTableName(),
$viewer->getPHID(),
$ids);
}
} }

View file

@ -19,6 +19,7 @@ extends PhabricatorInlineCommentPreviewController {
->withDrafts(true) ->withDrafts(true)
->withAuthorPHIDs(array($viewer->getPHID())) ->withAuthorPHIDs(array($viewer->getPHID()))
->withRevisionPHIDs(array($revision->getPHID())) ->withRevisionPHIDs(array($revision->getPHID()))
->needHidden(true)
->execute(); ->execute();
} }

View file

@ -175,6 +175,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
$query = id(new DifferentialInlineCommentQuery()) $query = id(new DifferentialInlineCommentQuery())
->setViewer($user) ->setViewer($user)
->needHidden(true)
->withRevisionPHIDs(array($revision->getPHID())); ->withRevisionPHIDs(array($revision->getPHID()));
$inlines = $query->execute(); $inlines = $query->execute();
$inlines = $query->adjustInlinesForChangesets( $inlines = $query->adjustInlinesForChangesets(

View file

@ -15,6 +15,7 @@ final class DifferentialInlineCommentQuery
private $authorPHIDs; private $authorPHIDs;
private $revisionPHIDs; private $revisionPHIDs;
private $deletedDrafts; private $deletedDrafts;
private $needHidden;
public function setViewer(PhabricatorUser $viewer) { public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer; $this->viewer = $viewer;
@ -55,6 +56,11 @@ final class DifferentialInlineCommentQuery
return $this; return $this;
} }
public function needHidden($need) {
$this->needHidden = $need;
return $this;
}
public function execute() { public function execute() {
$table = new DifferentialTransactionComment(); $table = new DifferentialTransactionComment();
$conn_r = $table->establishConnection('r'); $conn_r = $table->establishConnection('r');
@ -68,6 +74,26 @@ final class DifferentialInlineCommentQuery
$comments = $table->loadAllFromArray($data); $comments = $table->loadAllFromArray($data);
if ($this->needHidden) {
$viewer_phid = $this->getViewer()->getPHID();
if ($viewer_phid && $comments) {
$hidden = queryfx_all(
$conn_r,
'SELECT commentID FROM %T WHERE userPHID = %s
AND commentID IN (%Ls)',
id(new DifferentialHiddenComment())->getTableName(),
$viewer_phid,
mpull($comments, 'getID'));
$hidden = array_fuse(ipull($hidden, 'commentID'));
} else {
$hidden = array();
}
foreach ($comments as $inline) {
$inline->attachIsHidden(isset($hidden[$inline->getID()]));
}
}
foreach ($comments as $key => $value) { foreach ($comments as $key => $value) {
$comments[$key] = DifferentialInlineComment::newFromModernComment( $comments[$key] = DifferentialInlineComment::newFromModernComment(
$value); $value);

View file

@ -41,8 +41,10 @@ final class DifferentialChangesetOneUpRenderer
$column_width = 4; $column_width = 4;
$hidden = new PHUIDiffRevealIconView();
$out = array(); $out = array();
foreach ($primitives as $p) { foreach ($primitives as $k => $p) {
$type = $p['type']; $type = $p['type'];
switch ($type) { switch ($type) {
case 'old': case 'old':
@ -51,6 +53,27 @@ final class DifferentialChangesetOneUpRenderer
case 'new-file': case 'new-file':
$is_old = ($type == 'old' || $type == 'old-file'); $is_old = ($type == 'old' || $type == 'old-file');
$o_hidden = array();
$n_hidden = array();
for ($look = $k + 1; isset($primitives[$look]); $look++) {
$next = $primitives[$look];
switch ($next['type']) {
case 'inline':
$comment = $next['comment'];
if ($comment->isHidden()) {
if ($next['right']) {
$n_hidden[] = $comment;
} else {
$o_hidden[] = $comment;
}
}
break;
default:
break 2;
}
}
$cells = array(); $cells = array();
if ($is_old) { if ($is_old) {
if ($p['htype']) { if ($p['htype']) {
@ -68,7 +91,13 @@ final class DifferentialChangesetOneUpRenderer
} else { } else {
$left_id = null; $left_id = null;
} }
$cells[] = phutil_tag('th', array('id' => $left_id), $p['line']);
$line = $p['line'];
if ($o_hidden) {
$line = array($hidden, $line);
}
$cells[] = phutil_tag('th', array('id' => $left_id), $line);
$cells[] = phutil_tag('th', array()); $cells[] = phutil_tag('th', array());
$cells[] = $no_copy; $cells[] = $no_copy;
@ -85,7 +114,13 @@ final class DifferentialChangesetOneUpRenderer
} else { } else {
$left_id = null; $left_id = null;
} }
$cells[] = phutil_tag('th', array('id' => $left_id), $p['oline']);
$oline = $p['oline'];
if ($o_hidden) {
$oline = array($hidden, $oline);
}
$cells[] = phutil_tag('th', array('id' => $left_id), $oline);
} }
if ($type == 'new-file') { if ($type == 'new-file') {
@ -97,8 +132,13 @@ final class DifferentialChangesetOneUpRenderer
} else { } else {
$right_id = null; $right_id = null;
} }
$cells[] = phutil_tag('th', array('id' => $right_id), $p['line']);
$line = $p['line'];
if ($n_hidden) {
$line = array($hidden, $line);
}
$cells[] = phutil_tag('th', array('id' => $right_id), $line);
$cells[] = $no_copy; $cells[] = $no_copy;
$cells[] = phutil_tag('td', array('class' => $class), $p['render']); $cells[] = phutil_tag('td', array('class' => $class), $p['render']);

View file

@ -69,6 +69,8 @@ final class DifferentialChangesetTwoUpRenderer
$depths = $this->getDepths(); $depths = $this->getDepths();
$mask = $this->getMask(); $mask = $this->getMask();
$hidden = new PHUIDiffRevealIconView();
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) { if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap. // If we aren't going to show this line, we've just entered a gap.
@ -235,6 +237,69 @@ final class DifferentialChangesetTwoUpRenderer
$n_id = null; $n_id = null;
} }
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
$scaffolds = array();
$o_hidden = array();
$n_hidden = array();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$inline = $this->buildInlineComment(
$comment,
$on_right = false);
$scaffold = $this->getRowScaffoldForInline($inline);
if ($comment->isHidden()) {
$o_hidden[] = $comment;
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$companion = $this->buildInlineComment(
$new_comment,
$on_right = true);
if ($new_comment->isHidden()) {
$n_hidden = $new_comment;
}
$scaffold->addInlineView($companion);
unset($new_comments[$n_num][$key]);
break;
}
}
}
$scaffolds[] = $scaffold;
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$inline = $this->buildInlineComment(
$comment,
$on_right = true);
if ($comment->isHidden()) {
$n_hidden[] = $comment;
}
$scaffolds[] = $this->getRowScaffoldForInline($inline);
}
}
if ($o_hidden) {
$o_num = array($hidden, $o_num);
}
if ($n_hidden) {
$n_num = array($hidden, $n_num);
}
// NOTE: This is a unicode zero-width space, which we use as a hint when // NOTE: This is a unicode zero-width space, which we use as a hint when
// intercepting 'copy' events to make sure sensible text ends up on the // intercepting 'copy' events to make sure sensible text ends up on the
// clipboard. See the 'phabricator-oncopy' behavior. // clipboard. See the 'phabricator-oncopy' behavior.
@ -259,42 +324,10 @@ final class DifferentialChangesetTwoUpRenderer
$html[] = $context_not_available; $html[] = $context_not_available;
} }
$old_comments = $this->getOldComments(); foreach ($scaffolds as $scaffold) {
$new_comments = $this->getNewComments();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$inline = $this->buildInlineComment(
$comment,
$on_right = false);
$scaffold = $this->getRowScaffoldForInline($inline);
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$companion = $this->buildInlineComment(
$new_comment,
$on_right = true);
$scaffold->addInlineView($companion);
unset($new_comments[$n_num][$key]);
break;
}
}
}
$html[] = $scaffold; $html[] = $scaffold;
} }
} }
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$inline = $this->buildInlineComment(
$comment,
$on_right = true);
$html[] = $this->getRowScaffoldForInline($inline);
}
}
}
return $this->wrapChangeInTable(phutil_implode_html('', $html)); return $this->wrapChangeInTable(phutil_implode_html('', $html));
} }

View file

@ -0,0 +1,24 @@
<?php
final class DifferentialHiddenComment
extends DifferentialDAO {
protected $userPHID;
protected $commentID;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_KEY_SCHEMA => array(
'key_user' => array(
'columns' => array('userPHID', 'commentID'),
'unique' => true,
),
'key_comment' => array(
'columns' => array('commentID'),
),
),
) + parent::getConfiguration();
}
}

View file

@ -24,6 +24,7 @@ final class DifferentialInlineComment
->setViewPolicy('public') ->setViewPolicy('public')
->setEditPolicy($this->getAuthorPHID()) ->setEditPolicy($this->getAuthorPHID())
->setContentSource($content_source) ->setContentSource($content_source)
->attachIsHidden(false)
->setCommentVersion(1); ->setCommentVersion(1);
return $this->proxy; return $this->proxy;
@ -49,6 +50,14 @@ final class DifferentialInlineComment
return $this; return $this;
} }
public function supportsHiding() {
return true;
}
public function isHidden() {
return $this->proxy->getIsHidden();
}
public function getID() { public function getID() {
return $this->proxy->getID(); return $this->proxy->getID();
} }

View file

@ -522,6 +522,7 @@ final class DifferentialRevision extends DifferentialDAO
} }
$query = id(new DifferentialInlineCommentQuery()) $query = id(new DifferentialInlineCommentQuery())
->needHidden(true)
->setViewer($viewer); ->setViewer($viewer);
// NOTE: This is a bit sketchy: this method adjusts the inlines as a // NOTE: This is a bit sketchy: this method adjusts the inlines as a

View file

@ -13,6 +13,7 @@ final class DifferentialTransactionComment
protected $replyToCommentPHID; protected $replyToCommentPHID;
private $replyToComment = self::ATTACHABLE; private $replyToComment = self::ATTACHABLE;
private $isHidden = self::ATTACHABLE;
public function getApplicationTransactionObject() { public function getApplicationTransactionObject() {
return new DifferentialTransaction(); return new DifferentialTransaction();
@ -99,4 +100,13 @@ final class DifferentialTransactionComment
return $inline_groups; return $inline_groups;
} }
public function getIsHidden() {
return $this->assertAttached($this->isHidden);
}
public function attachIsHidden($hidden) {
$this->isHidden = $hidden;
return $this;
}
} }

View file

@ -234,6 +234,7 @@ final class DifferentialChangesetListView extends AphrontView {
Javelin::initBehavior('differential-edit-inline-comments', array( Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => $this->inlineURI, 'uri' => $this->inlineURI,
'stage' => 'differential-review-stage', 'stage' => 'differential-review-stage',
'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()),
)); ));
} }

View file

@ -15,6 +15,14 @@ abstract class PhabricatorInlineCommentController
abstract protected function saveComment( abstract protected function saveComment(
PhabricatorInlineCommentInterface $inline); PhabricatorInlineCommentInterface $inline);
protected function hideComments(array $ids) {
throw new PhutilMethodNotImplementedException();
}
protected function showComments(array $ids) {
throw new PhutilMethodNotImplementedException();
}
private $changesetID; private $changesetID;
private $isNewFile; private $isNewFile;
private $isOnRight; private $isOnRight;
@ -84,6 +92,22 @@ abstract class PhabricatorInlineCommentController
$op = $this->getOperation(); $op = $this->getOperation();
switch ($op) { switch ($op) {
case 'hide':
case 'show':
if (!$request->validateCSRF()) {
return new Aphront404Response();
}
$ids = $request->getStrList('ids');
if ($ids) {
if ($op == 'hide') {
$this->hideComments($ids);
} else {
$this->showComments($ids);
}
}
return id(new AphrontAjaxResponse())->setContent(array());
case 'done': case 'done':
if (!$request->validateCSRF()) { if (!$request->validateCSRF()) {
return new Aphront404Response(); return new Aphront404Response();

View file

@ -57,4 +57,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface {
public function setIsGhost($is_ghost); public function setIsGhost($is_ghost);
public function getIsGhost(); public function getIsGhost();
public function supportsHiding();
public function isHidden();
} }

View file

@ -18,6 +18,10 @@ final class PHUIDiffInlineCommentDetailView
return $this; return $this;
} }
public function isHidden() {
return $this->inlineComment->isHidden();
}
public function setHandles(array $handles) { public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle'); assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles; $this->handles = $handles;
@ -192,6 +196,8 @@ final class PHUIDiffInlineCommentDetailView
if (!$this->preview) { if (!$this->preview) {
$nextprev = new PHUIButtonBarView(); $nextprev = new PHUIButtonBarView();
$nextprev->addClass('mml'); $nextprev->addClass('mml');
$up = id(new PHUIButtonView()) $up = id(new PHUIButtonView())
->setTag('a') ->setTag('a')
->setColor(PHUIButtonView::SIMPLE) ->setColor(PHUIButtonView::SIMPLE)
@ -208,6 +214,18 @@ final class PHUIDiffInlineCommentDetailView
->addSigil('differential-inline-next') ->addSigil('differential-inline-next')
->setMustCapture(true); ->setMustCapture(true);
$hide = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::SIMPLE)
->setTooltip(pht('Hide Comment'))
->setIconFont('fa-times')
->addSigil('hide-inline')
->setMustCapture(true);
if ($viewer_phid && $inline->getID() && $inline->supportsHiding()) {
$nextprev->addButton($hide);
}
$nextprev->addButton($up); $nextprev->addButton($up);
$nextprev->addButton($down); $nextprev->addButton($down);

View file

@ -23,8 +23,18 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView {
protected function getRowAttributes() { protected function getRowAttributes() {
// TODO: This is semantic information used by the JS when placing comments // TODO: This is semantic information used by the JS when placing comments
// and using keyboard navigation; we should move it out of class names. // and using keyboard navigation; we should move it out of class names.
$style = null;
foreach ($this->getInlineViews() as $view) {
if ($view->isHidden()) {
$style = 'display: none';
}
}
return array( return array(
'class' => 'inline', 'class' => 'inline',
'sigil' => 'inline-row',
'style' => $style,
); );
} }

View file

@ -17,4 +17,8 @@ abstract class PHUIDiffInlineCommentView extends AphrontView {
return null; return null;
} }
public function isHidden() {
return false;
}
} }

View file

@ -28,7 +28,7 @@ final class PHUIDiffOneUpInlineCommentRowScaffold
phutil_tag('td', $attrs, $inline), phutil_tag('td', $attrs, $inline),
); );
return phutil_tag('tr', $this->getRowAttributes(), $cells); return javelin_tag('tr', $this->getRowAttributes(), $cells);
} }
} }

View file

@ -0,0 +1,27 @@
<?php
final class PHUIDiffRevealIconView extends AphrontView {
public function render() {
$icon = id(new PHUIIconView())
->setIconFont('fa-comment')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Show Hidden Comments'),
'align' => 'E',
'size' => 275,
));
return javelin_tag(
'a',
array(
'href' => '#',
'class' => 'reveal-inlines',
'sigil' => 'reveal-inlines',
'mustcapture' => true,
),
$icon);
}
}

View file

@ -68,7 +68,7 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold
phutil_tag('td', $right_attrs, $right_side), phutil_tag('td', $right_attrs, $right_side),
); );
return phutil_tag('tr', $this->getRowAttributes(), $cells); return javelin_tag('tr', $this->getRowAttributes(), $cells);
} }
} }

View file

@ -434,3 +434,21 @@
border-color: {$lightgreyborder}; border-color: {$lightgreyborder};
color: {$lightgreytext}; color: {$lightgreytext};
} }
/* - Hiding Inlines ------------------------------------------------------------
*/
.reveal-inlines {
float: left;
margin-left: 4px;
color: {$lightbluetext};
}
.reveal-inlines span.phui-icon-view {
color: {$lightbluetext};
}
.reveal-inlines:hover span.phui-icon-view {
color: {$darkbluetext};
}

View file

@ -395,4 +395,87 @@ JX.behavior('differential-edit-inline-comments', function(config) {
handle_inline_action(data.node, data.op); handle_inline_action(data.node, data.op);
}); });
// Respond to the user clicking the "Hide Inline" button on an inline
// comment.
JX.Stratcom.listen('click', 'hide-inline', function(e) {
e.kill();
var row = e.getNode('inline-row');
JX.DOM.hide(row);
var prev = row.previousSibling;
while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) {
prev = prev.previousSibling;
}
if (!prev) {
return;
}
var comment = e.getNodeData('differential-inline-comment');
var slots = [];
for (var ii = 0; ii < prev.childNodes.length; ii++) {
if (JX.DOM.isType(prev.childNodes[ii], 'th')) {
slots.push(prev.childNodes[ii]);
}
}
// Select the right-hand side if the comment is on the right.
var slot = (comment.on_right && slots[1]) || slots[0];
var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0];
if (!reveal) {
reveal = JX.$N(
'a',
{
className: 'reveal-inlines',
sigil: 'reveal-inlines'
},
JX.$H(config.revealIcon));
JX.DOM.prependContent(slot, reveal);
}
new JX.Workflow(config.uri, {op: 'hide', ids: comment.id})
.setHandler(JX.bag)
.start();
});
JX.Stratcom.listen('click', 'reveal-inlines', function(e) {
e.kill();
var row = e.getNode('tag:tr');
var next = row.nextSibling;
var ids = [];
var ii;
// Show any hidden inline comment rows directly below this one.
while (next && JX.Stratcom.hasSigil(next, 'inline-row')) {
JX.DOM.show(next);
var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment');
for (ii = 0; ii < comments.length; ii++) {
var id = JX.Stratcom.getData(comments[ii]).id;
if (id) {
ids.push(id);
}
}
next = next.nextSibling;
}
// Remove any "reveal" icons on the row.
var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines');
for (ii = 0; ii < reveals.length; ii++) {
JX.DOM.remove(reveals[ii]);
}
new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')})
.setHandler(JX.bag)
.start();
});
}); });