@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make "editing" state persistent for inline comments

Summary:
Ref T13513. This is mostly an infrastructure cleanup change.

In a perfect world, this would be a series of several changes, but they're tightly interconnected and don't have an obvious clean, nontrivial partition (or, at least, I don't see one). Followup changes will exercise this code repeatedly and all of these individual mutations are "obviously good", so I'm not too worried about the breadth of this change.

---

Inline comments are stored as transaction comments in the `PhabricatorAuditTransactionComment` and `DifferentialTransactionComment` classes.

On top of these two storage classes sit `PhabricatorAuditInlineComment` and `DifferentialInlineComment`. Historically, these were an indirection layer over significantly different storage classes, but nowadays both storage classes look pretty similar and most of the logic is actually the same. Prior to this change, these two classes were about 80% copy/pastes of one another.

Part of the reason they're so copy/pastey is that they implement a parent `Interface`. They are the only classes which implement this interface, and the interface does not provide any correctness guarantees (the storage objects are not actually constrained by it).

To simplify this:

- Make `PhabricatorInlineCommentInterface` an abstract base class instead.
- Lift as much code out of the `Audit` and `Differential` subclasses as possible.
- Delete methods which no longer have callers, or have only trivial callers.

---

Inline comments have two `View` rendering classes, `DetailView` and `EditView`. They share very little code.

Partly, this is because `EditView` does not take an `$inline` object. Historically, it needed to be able to operate on inlines that did not have an ID yet, and even further back in history this was probably just an outgrowth of a simple `<form />`.

These classes can be significantly simplified by passing an `$inline` to the `EditView`, instead of individually setting all the properties on the `View` itself. This allows the `DetailView` and `EditView` classes to share a lot of code.

The `EditView` can not fully render its content. Move the content rendering code into the view.

---

Prior to this change, some operations need to work on inlines that don't have an inline ID yet (we assign an ID the first time you "Save" a comment). Since "editing" comments will now be saved, we can instead create a row immediately.

This means that all the inline code can always rely on having a valid ID to work with, even if that ID corresponds to an empty, draft, "isEditing" comment. This simplifies more code in `EditView` and allows the "create" and "reply" code to be merged in `PhabricatorInlineCommentController`.

---

Client-side inline events are currently handled through a mixture of `ChangesetList` listeners (good) and ad-hoc row-level listeners (less good). In particular, the "save", "cancel", and "undo" events are row-level. All other events are list-level.

Move all events to list-level. This is supported by all inlines now having an ID at all stages of their lifecycle.

This allows some of the client behavior to be simplified. It currently depends on binding complex ad-hoc dictionaries into event handlers in `_drawRows()`, but it seems like almost all of this code can be removed. In fact, no more than one row ever seems to be drawn, so this code can probably be simplified further.

---

Finally, save an "isEditing" state. When we rebuild a revision on the client, click the "edit" button if it's in this state. This is a little hacky, but simpler to get into a stable state, since the row layout of an inline depends on a "view row" followed by an "edit row".

Test Plan:
- Created comments on either side of a diff.
- Edited a comment, reloaded, saw edit stick.
- Saved comments, reloaded, saw save stick.
- Edited a comment, typed text, cancelled, "unedited" to get state back.
- Created a comment, typed text, cancelled, "unedited" to get state back.
- Deleted a comment, "undeleted" to get state back.

Weirdness / known issues:

- Drafts don't autosave yet.
- Fixed in D21187:
- When you create an empty comment then reload, you get an empty editor. This is a bit silly.
- "Cancel" does not save state, but should, once drafts autosave.
- Mostly fixed in D21188:
- "Editing" comments aren't handled specially by the overall submission flow.
- "Editing" comments submitted in that state try to edit themselves again on load, which doesn't work.

Subscribers: jmeador

Maniphest Tasks: T13513

Differential Revision: https://secure.phabricator.com/D21186

+687 -944
+13 -13
resources/celerity/map.php
··· 13 13 'core.pkg.js' => '632fb8f5', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => '2d70b7b9', 16 - 'differential.pkg.js' => 'c8f88d74', 16 + 'differential.pkg.js' => 'b289f75d', 17 17 'diffusion.pkg.css' => '42c75c37', 18 18 'diffusion.pkg.js' => 'a98c0bf7', 19 19 'maniphest.pkg.css' => '35995d6d', ··· 380 380 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 381 381 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 382 382 'rsrc/js/application/diff/DiffChangeset.js' => '9a713ba5', 383 - 'rsrc/js/application/diff/DiffChangesetList.js' => 'adf069cd', 384 - 'rsrc/js/application/diff/DiffInline.js' => '16e97ebc', 383 + 'rsrc/js/application/diff/DiffChangesetList.js' => '10726e6a', 384 + 'rsrc/js/application/diff/DiffInline.js' => '7b0bdd6d', 385 385 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', 386 386 'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b', 387 387 'rsrc/js/application/diff/behavior-preview-link.js' => 'f51e9c17', ··· 777 777 'phabricator-darkmessage' => '26cd4b73', 778 778 'phabricator-dashboard-css' => '5a205b9d', 779 779 'phabricator-diff-changeset' => '9a713ba5', 780 - 'phabricator-diff-changeset-list' => 'adf069cd', 781 - 'phabricator-diff-inline' => '16e97ebc', 780 + 'phabricator-diff-changeset-list' => '10726e6a', 781 + 'phabricator-diff-inline' => '7b0bdd6d', 782 782 'phabricator-diff-path-view' => '8207abf9', 783 783 'phabricator-diff-tree-view' => '5d83623b', 784 784 'phabricator-drag-and-drop-file-upload' => '4370900d', ··· 1022 1022 'javelin-workflow', 1023 1023 'phuix-icon-view', 1024 1024 ), 1025 + '10726e6a' => array( 1026 + 'javelin-install', 1027 + 'phuix-button-view', 1028 + 'phabricator-diff-tree-view', 1029 + ), 1025 1030 '111bfd2d' => array( 1026 1031 'javelin-install', 1027 1032 ), ··· 1035 1040 'javelin-dom', 1036 1041 'javelin-stratcom', 1037 1042 'javelin-util', 1038 - ), 1039 - '16e97ebc' => array( 1040 - 'javelin-dom', 1041 1043 ), 1042 1044 '1a844c06' => array( 1043 1045 'javelin-install', ··· 1614 1616 'phabricator-drag-and-drop-file-upload', 1615 1617 'phabricator-textareautils', 1616 1618 ), 1619 + '7b0bdd6d' => array( 1620 + 'javelin-dom', 1621 + ), 1617 1622 '7b139193' => array( 1618 1623 'javelin-behavior', 1619 1624 'javelin-stratcom', ··· 1929 1934 'javelin-request', 1930 1935 'javelin-typeahead-ondemand-source', 1931 1936 'javelin-util', 1932 - ), 1933 - 'adf069cd' => array( 1934 - 'javelin-install', 1935 - 'phuix-button-view', 1936 - 'phabricator-diff-tree-view', 1937 1937 ), 1938 1938 'aec8e38c' => array( 1939 1939 'javelin-dom',
+7 -10
src/__phutil_library_map__.php
··· 3592 3592 'PhabricatorIndexEngineExtensionModule' => 'applications/search/index/PhabricatorIndexEngineExtensionModule.php', 3593 3593 'PhabricatorIndexableInterface' => 'applications/search/interface/PhabricatorIndexableInterface.php', 3594 3594 'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php', 3595 + 'PhabricatorInlineComment' => 'infrastructure/diff/interface/PhabricatorInlineComment.php', 3595 3596 'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php', 3596 - 'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php', 3597 3597 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 3598 3598 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 3599 3599 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', ··· 6623 6623 'DifferentialHunkParserTestCase' => 'PhabricatorTestCase', 6624 6624 'DifferentialHunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6625 6625 'DifferentialHunkTestCase' => 'PhutilTestCase', 6626 - 'DifferentialInlineComment' => array( 6627 - 'Phobject', 6628 - 'PhabricatorInlineCommentInterface', 6629 - ), 6626 + 'DifferentialInlineComment' => 'PhabricatorInlineComment', 6630 6627 'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController', 6631 6628 'DifferentialInlineCommentMailView' => 'DifferentialMailView', 6632 6629 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', ··· 8595 8592 'PhabricatorAuditCommentEditor' => 'PhabricatorEditor', 8596 8593 'PhabricatorAuditController' => 'PhabricatorController', 8597 8594 'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor', 8598 - 'PhabricatorAuditInlineComment' => array( 8599 - 'Phobject', 8600 - 'PhabricatorInlineCommentInterface', 8601 - ), 8595 + 'PhabricatorAuditInlineComment' => 'PhabricatorInlineComment', 8602 8596 'PhabricatorAuditListView' => 'AphrontView', 8603 8597 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 8604 8598 'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow', ··· 10115 10109 'PhabricatorIndexEngineExtension' => 'Phobject', 10116 10110 'PhabricatorIndexEngineExtensionModule' => 'PhabricatorConfigModule', 10117 10111 'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase', 10112 + 'PhabricatorInlineComment' => array( 10113 + 'Phobject', 10114 + 'PhabricatorMarkupInterface', 10115 + ), 10118 10116 'PhabricatorInlineCommentController' => 'PhabricatorController', 10119 - 'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface', 10120 10117 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 10121 10118 'PhabricatorInlineSummaryView' => 'AphrontView', 10122 10119 'PhabricatorInstructionsEditField' => 'PhabricatorEditField',
+14 -220
src/applications/audit/storage/PhabricatorAuditInlineComment.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorAuditInlineComment 4 - extends Phobject 5 - implements PhabricatorInlineCommentInterface { 4 + extends PhabricatorInlineComment { 6 5 7 - private $proxy; 8 - private $syntheticAuthor; 9 - private $isGhost; 10 - 11 - public function __construct() { 12 - $this->proxy = new PhabricatorAuditTransactionComment(); 6 + protected function newStorageObject() { 7 + return new PhabricatorAuditTransactionComment(); 13 8 } 14 9 15 - public function __clone() { 16 - $this->proxy = clone $this->proxy; 17 - } 18 - 19 - public function getTransactionPHID() { 20 - return $this->proxy->getTransactionPHID(); 21 - } 22 - 23 - public function getTransactionComment() { 24 - return $this->proxy; 10 + public function getControllerURI() { 11 + return urisprintf( 12 + '/diffusion/inline/edit/%s/', 13 + $this->getCommitPHID()); 25 14 } 26 15 27 16 public function supportsHiding() { ··· 36 25 $content_source = PhabricatorContentSource::newForSource( 37 26 PhabricatorOldWorldContentSource::SOURCECONST); 38 27 39 - $this->proxy 28 + $this->getStorageObject() 40 29 ->setViewPolicy('public') 41 30 ->setEditPolicy($this->getAuthorPHID()) 42 31 ->setContentSource($content_source) 43 32 ->setCommentVersion(1); 44 33 45 - return $this->proxy; 34 + return $this->getStorageObject(); 46 35 } 47 36 48 37 public static function loadID($id) { ··· 134 123 return $results; 135 124 } 136 125 137 - public function setSyntheticAuthor($synthetic_author) { 138 - $this->syntheticAuthor = $synthetic_author; 139 - return $this; 140 - } 141 - 142 - public function getSyntheticAuthor() { 143 - return $this->syntheticAuthor; 144 - } 145 - 146 - public function openTransaction() { 147 - $this->proxy->openTransaction(); 148 - } 149 - 150 - public function saveTransaction() { 151 - $this->proxy->saveTransaction(); 152 - } 153 - 154 - public function save() { 155 - $this->getTransactionCommentForSave()->save(); 156 - 157 - return $this; 158 - } 159 - 160 - public function delete() { 161 - $this->proxy->delete(); 162 - 163 - return $this; 164 - } 165 - 166 - public function getID() { 167 - return $this->proxy->getID(); 168 - } 169 - 170 - public function getPHID() { 171 - return $this->proxy->getPHID(); 172 - } 173 - 174 126 public static function newFromModernComment( 175 127 PhabricatorAuditTransactionComment $comment) { 176 128 177 129 $obj = new PhabricatorAuditInlineComment(); 178 - $obj->proxy = $comment; 130 + $obj->setStorageObject($comment); 179 131 180 132 return $obj; 181 - } 182 - 183 - public function isCompatible(PhabricatorInlineCommentInterface $comment) { 184 - return 185 - ($this->getAuthorPHID() === $comment->getAuthorPHID()) && 186 - ($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) && 187 - ($this->getContent() === $comment->getContent()); 188 - } 189 - 190 - public function setContent($content) { 191 - $this->proxy->setContent($content); 192 - return $this; 193 - } 194 - 195 - public function getContent() { 196 - return $this->proxy->getContent(); 197 - } 198 - 199 - public function isDraft() { 200 - return !$this->proxy->getTransactionPHID(); 201 133 } 202 134 203 135 public function setPathID($id) { 204 - $this->proxy->setPathID($id); 136 + $this->getStorageObject()->setPathID($id); 205 137 return $this; 206 138 } 207 139 208 140 public function getPathID() { 209 - return $this->proxy->getPathID(); 210 - } 211 - 212 - public function setIsNewFile($is_new) { 213 - $this->proxy->setIsNewFile($is_new); 214 - return $this; 215 - } 216 - 217 - public function getIsNewFile() { 218 - return $this->proxy->getIsNewFile(); 219 - } 220 - 221 - public function setLineNumber($number) { 222 - $this->proxy->setLineNumber($number); 223 - return $this; 224 - } 225 - 226 - public function getLineNumber() { 227 - return $this->proxy->getLineNumber(); 228 - } 229 - 230 - public function setLineLength($length) { 231 - $this->proxy->setLineLength($length); 232 - return $this; 233 - } 234 - 235 - public function getLineLength() { 236 - return $this->proxy->getLineLength(); 237 - } 238 - 239 - public function setCache($cache) { 240 - return $this; 241 - } 242 - 243 - public function getCache() { 244 - return null; 245 - } 246 - 247 - public function setAuthorPHID($phid) { 248 - $this->proxy->setAuthorPHID($phid); 249 - return $this; 250 - } 251 - 252 - public function getAuthorPHID() { 253 - return $this->proxy->getAuthorPHID(); 141 + return $this->getStorageObject()->getPathID(); 254 142 } 255 143 256 144 public function setCommitPHID($commit_phid) { 257 - $this->proxy->setCommitPHID($commit_phid); 145 + $this->getStorageObject()->setCommitPHID($commit_phid); 258 146 return $this; 259 147 } 260 148 261 149 public function getCommitPHID() { 262 - return $this->proxy->getCommitPHID(); 263 - } 264 - 265 - // When setting a comment ID, we also generate a phantom transaction PHID for 266 - // the future transaction. 267 - 268 - public function setAuditCommentID($id) { 269 - $this->proxy->setLegacyCommentID($id); 270 - $this->proxy->setTransactionPHID( 271 - PhabricatorPHID::generateNewPHID( 272 - PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST, 273 - PhabricatorRepositoryCommitPHIDType::TYPECONST)); 274 - return $this; 275 - } 276 - 277 - public function getAuditCommentID() { 278 - return $this->proxy->getLegacyCommentID(); 150 + return $this->getStorageObject()->getCommitPHID(); 279 151 } 280 152 281 153 public function setChangesetID($id) { ··· 284 156 285 157 public function getChangesetID() { 286 158 return $this->getPathID(); 287 - } 288 - 289 - public function setReplyToCommentPHID($phid) { 290 - $this->proxy->setReplyToCommentPHID($phid); 291 - return $this; 292 - } 293 - 294 - public function getReplyToCommentPHID() { 295 - return $this->proxy->getReplyToCommentPHID(); 296 - } 297 - 298 - public function setHasReplies($has_replies) { 299 - $this->proxy->setHasReplies($has_replies); 300 - return $this; 301 - } 302 - 303 - public function getHasReplies() { 304 - return $this->proxy->getHasReplies(); 305 - } 306 - 307 - public function setIsDeleted($is_deleted) { 308 - $this->proxy->setIsDeleted($is_deleted); 309 - return $this; 310 - } 311 - 312 - public function getIsDeleted() { 313 - return $this->proxy->getIsDeleted(); 314 - } 315 - 316 - public function setFixedState($state) { 317 - $this->proxy->setFixedState($state); 318 - return $this; 319 - } 320 - 321 - public function getFixedState() { 322 - return $this->proxy->getFixedState(); 323 - } 324 - 325 - public function setIsGhost($is_ghost) { 326 - $this->isGhost = $is_ghost; 327 - return $this; 328 - } 329 - 330 - public function getIsGhost() { 331 - return $this->isGhost; 332 - } 333 - 334 - public function getDateModified() { 335 - return $this->proxy->getDateModified(); 336 - } 337 - 338 - public function getDateCreated() { 339 - return $this->proxy->getDateCreated(); 340 - } 341 - 342 - 343 - /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ 344 - 345 - 346 - public function getMarkupFieldKey($field) { 347 - return 'AI:'.$this->getID(); 348 - } 349 - 350 - public function newMarkupEngine($field) { 351 - return PhabricatorMarkupEngine::newDifferentialMarkupEngine(); 352 - } 353 - 354 - public function getMarkupText($field) { 355 - return $this->getContent(); 356 - } 357 - 358 - public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 359 - return $output; 360 - } 361 - 362 - public function shouldUseMarkupCache($field) { 363 - // Only cache submitted comments. 364 - return ($this->getID() && $this->getAuditCommentID()); 365 159 } 366 160 367 161 }
+9
src/applications/audit/storage/PhabricatorAuditTransactionComment.php
··· 72 72 return $this->assertAttached($this->replyToComment); 73 73 } 74 74 75 + public function getAttribute($key, $default = null) { 76 + return idx($this->attributes, $key, $default); 77 + } 78 + 79 + public function setAttribute($key, $value) { 80 + $this->attributes[$key] = $value; 81 + return $this; 82 + } 83 + 75 84 }
+1 -1
src/applications/differential/controller/DifferentialChangesetViewController.php
··· 238 238 foreach ($inlines as $inline) { 239 239 $engine->addObject( 240 240 $inline, 241 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 241 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 242 242 } 243 243 244 244 $engine->process();
+10 -4
src/applications/differential/controller/DifferentialInlineCommentEditController.php
··· 80 80 $viewer = $this->getViewer(); 81 81 82 82 $inline = $this->loadComment($id); 83 + if (!$inline) { 84 + throw new Exception( 85 + pht('Unable to load inline "%s".', $id)); 86 + } 87 + 83 88 if (!$this->canEditInlineComment($viewer, $inline)) { 84 89 throw new Exception(pht('That comment is not editable!')); 85 90 } 91 + 86 92 return $inline; 87 93 } 88 94 ··· 161 167 return true; 162 168 } 163 169 164 - protected function deleteComment(PhabricatorInlineCommentInterface $inline) { 170 + protected function deleteComment(PhabricatorInlineComment $inline) { 165 171 $inline->openTransaction(); 166 172 $inline->setIsDeleted(1)->save(); 167 173 $this->syncDraft(); ··· 169 175 } 170 176 171 177 protected function undeleteComment( 172 - PhabricatorInlineCommentInterface $inline) { 178 + PhabricatorInlineComment $inline) { 173 179 $inline->openTransaction(); 174 180 $inline->setIsDeleted(0)->save(); 175 181 $this->syncDraft(); 176 182 $inline->saveTransaction(); 177 183 } 178 184 179 - protected function saveComment(PhabricatorInlineCommentInterface $inline) { 185 + protected function saveComment(PhabricatorInlineComment $inline) { 180 186 $inline->openTransaction(); 181 187 $inline->save(); 182 188 $this->syncDraft(); ··· 184 190 } 185 191 186 192 protected function loadObjectOwnerPHID( 187 - PhabricatorInlineCommentInterface $inline) { 193 + PhabricatorInlineComment $inline) { 188 194 return $this->loadRevision()->getAuthorPHID(); 189 195 } 190 196
+1 -1
src/applications/differential/controller/DifferentialRevisionInlinesController.php
··· 124 124 $inline->getContent()); 125 125 126 126 $state = $inline->getFixedState(); 127 - if ($state == PhabricatorInlineCommentInterface::STATE_DONE) { 127 + if ($state == PhabricatorInlineComment::STATE_DONE) { 128 128 $status_icons[] = id(new PHUIIconView()) 129 129 ->setIcon('fa-check green') 130 130 ->addClass('mmr');
+5 -5
src/applications/differential/parser/DifferentialChangesetParser.php
··· 354 354 } 355 355 356 356 public function parseInlineComment( 357 - PhabricatorInlineCommentInterface $comment) { 357 + PhabricatorInlineComment $comment) { 358 358 359 359 // Parse only comments which are actually visible. 360 360 if ($this->isCommentVisibleOnRenderedDiff($comment)) { ··· 1191 1191 * taking into consideration which halves of which changesets will actually 1192 1192 * be shown. 1193 1193 * 1194 - * @param PhabricatorInlineCommentInterface Comment to test for visibility. 1194 + * @param PhabricatorInlineComment Comment to test for visibility. 1195 1195 * @return bool True if the comment is visible on the rendered diff. 1196 1196 */ 1197 1197 private function isCommentVisibleOnRenderedDiff( 1198 - PhabricatorInlineCommentInterface $comment) { 1198 + PhabricatorInlineComment $comment) { 1199 1199 1200 1200 $changeset_id = $comment->getChangesetID(); 1201 1201 $is_new = $comment->getIsNewFile(); ··· 1219 1219 * Note that the comment must appear somewhere on the rendered changeset, as 1220 1220 * per isCommentVisibleOnRenderedDiff(). 1221 1221 * 1222 - * @param PhabricatorInlineCommentInterface Comment to test for display 1222 + * @param PhabricatorInlineComment Comment to test for display 1223 1223 * location. 1224 1224 * @return bool True for right, false for left. 1225 1225 */ 1226 1226 private function isCommentOnRightSideWhenDisplayed( 1227 - PhabricatorInlineCommentInterface $comment) { 1227 + PhabricatorInlineComment $comment) { 1228 1228 1229 1229 if (!$this->isCommentVisibleOnRenderedDiff($comment)) { 1230 1230 throw new Exception(pht('Comment is not visible on changeset!'));
+6 -6
src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
··· 464 464 } 465 465 466 466 protected function buildInlineComment( 467 - PhabricatorInlineCommentInterface $comment, 467 + PhabricatorInlineComment $comment, 468 468 $on_right = false) { 469 469 470 - $user = $this->getUser(); 471 - $edit = $user && 472 - ($comment->getAuthorPHID() == $user->getPHID()) && 470 + $viewer = $this->getUser(); 471 + $edit = $viewer && 472 + ($comment->getAuthorPHID() == $viewer->getPHID()) && 473 473 ($comment->isDraft()) 474 474 && $this->getShowEditAndReplyLinks(); 475 - $allow_reply = (bool)$user && $this->getShowEditAndReplyLinks(); 475 + $allow_reply = (bool)$viewer && $this->getShowEditAndReplyLinks(); 476 476 $allow_done = !$comment->isDraft() && $this->getCanMarkDone(); 477 477 478 478 return id(new PHUIDiffInlineCommentDetailView()) 479 - ->setUser($user) 479 + ->setViewer($viewer) 480 480 ->setInlineComment($comment) 481 481 ->setIsOnRight($on_right) 482 482 ->setHandles($this->getHandles())
+2 -2
src/applications/differential/render/DifferentialChangesetRenderer.php
··· 263 263 264 264 public function setNewComments(array $new_comments) { 265 265 foreach ($new_comments as $line_number => $comments) { 266 - assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 266 + assert_instances_of($comments, 'PhabricatorInlineComment'); 267 267 } 268 268 $this->newComments = $new_comments; 269 269 return $this; ··· 274 274 275 275 public function setOldComments(array $old_comments) { 276 276 foreach ($old_comments as $line_number => $comments) { 277 - assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 277 + assert_instances_of($comments, 'PhabricatorInlineComment'); 278 278 } 279 279 $this->oldComments = $old_comments; 280 280 return $this;
+16 -214
src/applications/differential/storage/DifferentialInlineComment.php
··· 1 1 <?php 2 2 3 3 final class DifferentialInlineComment 4 - extends Phobject 5 - implements PhabricatorInlineCommentInterface { 4 + extends PhabricatorInlineComment { 6 5 7 - private $proxy; 8 - private $syntheticAuthor; 9 - private $isGhost; 10 - 11 - public function __construct() { 12 - $this->proxy = new DifferentialTransactionComment(); 6 + protected function newStorageObject() { 7 + return new DifferentialTransactionComment(); 13 8 } 14 9 15 - public function __clone() { 16 - $this->proxy = clone $this->proxy; 10 + public function getControllerURI() { 11 + return urisprintf( 12 + '/differential/comment/inline/edit/%s/', 13 + $this->getRevisionID()); 17 14 } 18 15 19 16 public function getTransactionCommentForSave() { 20 17 $content_source = PhabricatorContentSource::newForSource( 21 18 PhabricatorOldWorldContentSource::SOURCECONST); 22 19 23 - $this->proxy 20 + $this->getStorageObject() 24 21 ->setViewPolicy('public') 25 22 ->setEditPolicy($this->getAuthorPHID()) 26 23 ->setContentSource($content_source) 27 24 ->attachIsHidden(false) 28 25 ->setCommentVersion(1); 29 26 30 - return $this->proxy; 31 - } 32 - 33 - public function openTransaction() { 34 - $this->proxy->openTransaction(); 35 - } 36 - 37 - public function saveTransaction() { 38 - $this->proxy->saveTransaction(); 39 - } 40 - 41 - public function save() { 42 - $this->getTransactionCommentForSave()->save(); 43 - 44 - return $this; 45 - } 46 - 47 - public function delete() { 48 - $this->proxy->delete(); 49 - 50 - return $this; 27 + return $this->getStorageObject(); 51 28 } 52 29 53 30 public function supportsHiding() { ··· 61 38 if (!$this->supportsHiding()) { 62 39 return false; 63 40 } 64 - return $this->proxy->getIsHidden(); 65 - } 66 - 67 - public function getID() { 68 - return $this->proxy->getID(); 69 - } 70 - 71 - public function getPHID() { 72 - return $this->proxy->getPHID(); 41 + return $this->getStorageObject()->getIsHidden(); 73 42 } 74 43 75 44 public static function newFromModernComment( 76 45 DifferentialTransactionComment $comment) { 77 46 78 47 $obj = new DifferentialInlineComment(); 79 - $obj->proxy = $comment; 48 + $obj->setStorageObject($comment); 80 49 81 50 return $obj; 82 51 } 83 52 84 - public function setSyntheticAuthor($synthetic_author) { 85 - $this->syntheticAuthor = $synthetic_author; 86 - return $this; 87 - } 88 - 89 - public function getSyntheticAuthor() { 90 - return $this->syntheticAuthor; 91 - } 92 - 93 - public function isCompatible(PhabricatorInlineCommentInterface $comment) { 94 - return 95 - ($this->getAuthorPHID() === $comment->getAuthorPHID()) && 96 - ($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) && 97 - ($this->getContent() === $comment->getContent()); 98 - } 99 - 100 - public function setContent($content) { 101 - $this->proxy->setContent($content); 102 - return $this; 103 - } 104 - 105 - public function getContent() { 106 - return $this->proxy->getContent(); 107 - } 108 - 109 - public function isDraft() { 110 - return !$this->proxy->getTransactionPHID(); 111 - } 112 - 113 53 public function setChangesetID($id) { 114 - $this->proxy->setChangesetID($id); 54 + $this->getStorageObject()->setChangesetID($id); 115 55 return $this; 116 56 } 117 57 118 58 public function getChangesetID() { 119 - return $this->proxy->getChangesetID(); 120 - } 121 - 122 - public function setIsNewFile($is_new) { 123 - $this->proxy->setIsNewFile($is_new); 124 - return $this; 125 - } 126 - 127 - public function getIsNewFile() { 128 - return $this->proxy->getIsNewFile(); 129 - } 130 - 131 - public function setLineNumber($number) { 132 - $this->proxy->setLineNumber($number); 133 - return $this; 134 - } 135 - 136 - public function getLineNumber() { 137 - return $this->proxy->getLineNumber(); 138 - } 139 - 140 - public function setLineLength($length) { 141 - $this->proxy->setLineLength($length); 142 - return $this; 143 - } 144 - 145 - public function getLineLength() { 146 - return $this->proxy->getLineLength(); 147 - } 148 - 149 - public function setCache($cache) { 150 - return $this; 151 - } 152 - 153 - public function getCache() { 154 - return null; 155 - } 156 - 157 - public function setAuthorPHID($phid) { 158 - $this->proxy->setAuthorPHID($phid); 159 - return $this; 160 - } 161 - 162 - public function getAuthorPHID() { 163 - return $this->proxy->getAuthorPHID(); 59 + return $this->getStorageObject()->getChangesetID(); 164 60 } 165 61 166 62 public function setRevision(DifferentialRevision $revision) { 167 - $this->proxy->setRevisionPHID($revision->getPHID()); 63 + $this->getStorageObject()->setRevisionPHID($revision->getPHID()); 168 64 return $this; 169 65 } 170 66 171 67 public function getRevisionPHID() { 172 - return $this->proxy->getRevisionPHID(); 68 + return $this->getStorageObject()->getRevisionPHID(); 173 69 } 174 70 175 71 // Although these are purely transitional, they're also *extra* dumb. ··· 180 76 } 181 77 182 78 public function getRevisionID() { 183 - $phid = $this->proxy->getRevisionPHID(); 79 + $phid = $this->getStorageObject()->getRevisionPHID(); 184 80 if (!$phid) { 185 81 return null; 186 82 } ··· 192 88 return null; 193 89 } 194 90 return $revision->getID(); 195 - } 196 - 197 - // When setting a comment ID, we also generate a phantom transaction PHID for 198 - // the future transaction. 199 - 200 - public function setCommentID($id) { 201 - $this->proxy->setTransactionPHID( 202 - PhabricatorPHID::generateNewPHID( 203 - PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST, 204 - DifferentialRevisionPHIDType::TYPECONST)); 205 - return $this; 206 - } 207 - 208 - public function setReplyToCommentPHID($phid) { 209 - $this->proxy->setReplyToCommentPHID($phid); 210 - return $this; 211 - } 212 - 213 - public function getReplyToCommentPHID() { 214 - return $this->proxy->getReplyToCommentPHID(); 215 - } 216 - 217 - public function setHasReplies($has_replies) { 218 - $this->proxy->setHasReplies($has_replies); 219 - return $this; 220 - } 221 - 222 - public function getHasReplies() { 223 - return $this->proxy->getHasReplies(); 224 - } 225 - 226 - public function setIsDeleted($is_deleted) { 227 - $this->proxy->setIsDeleted($is_deleted); 228 - return $this; 229 - } 230 - 231 - public function getIsDeleted() { 232 - return $this->proxy->getIsDeleted(); 233 - } 234 - 235 - public function setFixedState($state) { 236 - $this->proxy->setFixedState($state); 237 - return $this; 238 - } 239 - 240 - public function getFixedState() { 241 - return $this->proxy->getFixedState(); 242 - } 243 - 244 - public function setIsGhost($is_ghost) { 245 - $this->isGhost = $is_ghost; 246 - return $this; 247 - } 248 - 249 - public function getIsGhost() { 250 - return $this->isGhost; 251 - } 252 - 253 - public function makeEphemeral() { 254 - $this->proxy->makeEphemeral(); 255 - return $this; 256 - } 257 - 258 - public function getDateModified() { 259 - return $this->proxy->getDateModified(); 260 - } 261 - 262 - public function getDateCreated() { 263 - return $this->proxy->getDateCreated(); 264 - } 265 - 266 - /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ 267 - 268 - 269 - public function getMarkupFieldKey($field) { 270 - $content = $this->getMarkupText($field); 271 - return PhabricatorMarkupEngine::digestRemarkupContent($this, $content); 272 - } 273 - 274 - public function newMarkupEngine($field) { 275 - return PhabricatorMarkupEngine::newDifferentialMarkupEngine(); 276 - } 277 - 278 - public function getMarkupText($field) { 279 - return $this->getContent(); 280 - } 281 - 282 - public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 283 - return $output; 284 - } 285 - 286 - public function shouldUseMarkupCache($field) { 287 - // Only cache submitted comments. 288 - return ($this->getID() && !$this->isDraft()); 289 91 } 290 92 291 93 }
+9
src/applications/differential/storage/DifferentialTransactionComment.php
··· 118 118 return $this; 119 119 } 120 120 121 + public function getAttribute($key, $default = null) { 122 + return idx($this->attributes, $key, $default); 123 + } 124 + 125 + public function setAttribute($key, $value) { 126 + $this->attributes[$key] = $value; 127 + return $this; 128 + } 129 + 121 130 }
+2 -2
src/applications/differential/xaction/DifferentialRevisionInlineTransaction.php
··· 40 40 41 41 $is_done = false; 42 42 switch ($comment->getFixedState()) { 43 - case PhabricatorInlineCommentInterface::STATE_DONE: 44 - case PhabricatorInlineCommentInterface::STATE_UNDRAFT: 43 + case PhabricatorInlineComment::STATE_DONE: 44 + case PhabricatorInlineComment::STATE_UNDRAFT: 45 45 $is_done = true; 46 46 break; 47 47 }
+1 -1
src/applications/diffusion/controller/DiffusionDiffController.php
··· 120 120 foreach ($inlines as $inline) { 121 121 $engine->addObject( 122 122 $inline, 123 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 123 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 124 124 } 125 125 126 126 $engine->process();
+5 -5
src/applications/diffusion/controller/DiffusionInlineCommentController.php
··· 105 105 } 106 106 107 107 // Saved comments may not be edited. 108 - if ($inline->getAuditCommentID()) { 108 + if ($inline->getTransactionPHID()) { 109 109 return false; 110 110 } 111 111 ··· 117 117 return true; 118 118 } 119 119 120 - protected function deleteComment(PhabricatorInlineCommentInterface $inline) { 120 + protected function deleteComment(PhabricatorInlineComment $inline) { 121 121 $inline->setIsDeleted(1)->save(); 122 122 } 123 123 124 124 protected function undeleteComment( 125 - PhabricatorInlineCommentInterface $inline) { 125 + PhabricatorInlineComment $inline) { 126 126 $inline->setIsDeleted(0)->save(); 127 127 } 128 128 129 - protected function saveComment(PhabricatorInlineCommentInterface $inline) { 129 + protected function saveComment(PhabricatorInlineComment $inline) { 130 130 return $inline->save(); 131 131 } 132 132 133 133 protected function loadObjectOwnerPHID( 134 - PhabricatorInlineCommentInterface $inline) { 134 + PhabricatorInlineComment $inline) { 135 135 return $this->loadCommit()->getAuthorPHID(); 136 136 } 137 137
+4 -4
src/applications/transactions/constants/PhabricatorTransactions.php
··· 32 32 33 33 public static function getInlineStateMap() { 34 34 return array( 35 - PhabricatorInlineCommentInterface::STATE_DRAFT => 36 - PhabricatorInlineCommentInterface::STATE_DONE, 37 - PhabricatorInlineCommentInterface::STATE_UNDRAFT => 38 - PhabricatorInlineCommentInterface::STATE_UNDONE, 35 + PhabricatorInlineComment::STATE_DRAFT => 36 + PhabricatorInlineComment::STATE_DONE, 37 + PhabricatorInlineComment::STATE_UNDRAFT => 38 + PhabricatorInlineComment::STATE_UNDONE, 39 39 ); 40 40 } 41 41
+1 -1
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 1690 1690 $done = 0; 1691 1691 $undone = 0; 1692 1692 foreach ($new as $phid => $state) { 1693 - $is_done = ($state == PhabricatorInlineCommentInterface::STATE_DONE); 1693 + $is_done = ($state == PhabricatorInlineComment::STATE_DONE); 1694 1694 1695 1695 // See PHI995. If you're marking your own inline comments as "Done", 1696 1696 // don't count them when rendering a timeline story. In the case where
+71 -91
src/infrastructure/diff/PhabricatorInlineCommentController.php
··· 9 9 abstract protected function loadCommentForDone($id); 10 10 abstract protected function loadCommentByPHID($phid); 11 11 abstract protected function loadObjectOwnerPHID( 12 - PhabricatorInlineCommentInterface $inline); 12 + PhabricatorInlineComment $inline); 13 13 abstract protected function deleteComment( 14 - PhabricatorInlineCommentInterface $inline); 14 + PhabricatorInlineComment $inline); 15 15 abstract protected function undeleteComment( 16 - PhabricatorInlineCommentInterface $inline); 16 + PhabricatorInlineComment $inline); 17 17 abstract protected function saveComment( 18 - PhabricatorInlineCommentInterface $inline); 18 + PhabricatorInlineComment $inline); 19 19 20 20 protected function hideComments(array $ids) { 21 21 throw new PhutilMethodNotImplementedException(); ··· 119 119 $is_draft_state = false; 120 120 $is_checked = false; 121 121 switch ($inline->getFixedState()) { 122 - case PhabricatorInlineCommentInterface::STATE_DRAFT: 123 - $next_state = PhabricatorInlineCommentInterface::STATE_UNDONE; 122 + case PhabricatorInlineComment::STATE_DRAFT: 123 + $next_state = PhabricatorInlineComment::STATE_UNDONE; 124 124 break; 125 - case PhabricatorInlineCommentInterface::STATE_UNDRAFT: 126 - $next_state = PhabricatorInlineCommentInterface::STATE_DONE; 125 + case PhabricatorInlineComment::STATE_UNDRAFT: 126 + $next_state = PhabricatorInlineComment::STATE_DONE; 127 127 $is_checked = true; 128 128 break; 129 - case PhabricatorInlineCommentInterface::STATE_DONE: 130 - $next_state = PhabricatorInlineCommentInterface::STATE_UNDRAFT; 129 + case PhabricatorInlineComment::STATE_DONE: 130 + $next_state = PhabricatorInlineComment::STATE_UNDRAFT; 131 131 $is_draft_state = true; 132 132 break; 133 133 default: 134 - case PhabricatorInlineCommentInterface::STATE_UNDONE: 135 - $next_state = PhabricatorInlineCommentInterface::STATE_DRAFT; 134 + case PhabricatorInlineComment::STATE_UNDONE: 135 + $next_state = PhabricatorInlineComment::STATE_DRAFT; 136 136 $is_draft_state = true; 137 137 $is_checked = true; 138 138 break; ··· 187 187 188 188 if ($request->isFormPost()) { 189 189 if (strlen($text)) { 190 - $inline->setContent($text); 190 + $inline 191 + ->setContent($text) 192 + ->setIsEditing(false); 193 + 191 194 $this->saveComment($inline); 192 195 return $this->buildRenderedCommentResponse( 193 196 $inline, ··· 196 199 $this->deleteComment($inline); 197 200 return $this->buildEmptyResponse(); 198 201 } 199 - } 202 + } else { 203 + $inline->setIsEditing(true); 200 204 201 - $edit_dialog = $this->buildEditDialog(); 202 - $edit_dialog->setTitle(pht('Edit Inline Comment')); 205 + if (strlen($text)) { 206 + $inline->setContent($text); 207 + } 203 208 204 - $edit_dialog->addHiddenInput('id', $this->getCommentID()); 205 - $edit_dialog->addHiddenInput('op', 'edit'); 209 + $this->saveComment($inline); 210 + } 206 211 207 - $edit_dialog->appendChild( 208 - $this->renderTextArea( 209 - nonempty($text, $inline->getContent()))); 212 + $edit_dialog = $this->buildEditDialog($inline) 213 + ->setTitle(pht('Edit Inline Comment')); 210 214 211 215 $view = $this->buildScaffoldForView($edit_dialog); 212 216 213 - return id(new AphrontAjaxResponse()) 214 - ->setContent($view->render()); 215 - case 'create': 216 - $text = $this->getCommentText(); 217 - 218 - if (!$request->isFormPost() || !strlen($text)) { 219 - return $this->buildEmptyResponse(); 220 - } 217 + return $this->newInlineResponse($inline, $view); 218 + case 'new': 219 + case 'reply': 220 + default: 221 + // NOTE: We read the values from the client (the display values), not 222 + // the values from the database (the original values) when replying. 223 + // In particular, when replying to a ghost comment which was moved 224 + // across diffs and then moved backward to the most recent visible 225 + // line, we want to reply on the display line (which exists), not on 226 + // the comment's original line (which may not exist in this changeset). 227 + $is_new = $this->getIsNewFile(); 228 + $number = $this->getLineNumber(); 229 + $length = $this->getLineLength(); 221 230 222 231 $inline = $this->createComment() 223 232 ->setChangesetID($this->getChangesetID()) 224 233 ->setAuthorPHID($viewer->getPHID()) 225 - ->setLineNumber($this->getLineNumber()) 226 - ->setLineLength($this->getLineLength()) 227 - ->setIsNewFile($this->getIsNewFile()) 228 - ->setContent($text); 229 - 230 - if ($this->getReplyToCommentPHID()) { 231 - $inline->setReplyToCommentPHID($this->getReplyToCommentPHID()); 232 - } 234 + ->setIsNewFile($is_new) 235 + ->setLineNumber($number) 236 + ->setLineLength($length) 237 + ->setContent($this->getCommentText()) 238 + ->setReplyToCommentPHID($this->getReplyToCommentPHID()) 239 + ->setIsEditing(true); 233 240 234 241 // If you own this object, mark your own inlines as "Done" by default. 235 242 $owner_phid = $this->loadObjectOwnerPHID($inline); 236 243 if ($owner_phid) { 237 244 if ($viewer->getPHID() == $owner_phid) { 238 - $fixed_state = PhabricatorInlineCommentInterface::STATE_DRAFT; 245 + $fixed_state = PhabricatorInlineComment::STATE_DRAFT; 239 246 $inline->setFixedState($fixed_state); 240 247 } 241 248 } 242 249 243 250 $this->saveComment($inline); 244 251 245 - return $this->buildRenderedCommentResponse( 246 - $inline, 247 - $this->getIsOnRight()); 248 - case 'reply': 249 - default: 250 - $edit_dialog = $this->buildEditDialog(); 252 + $edit_dialog = $this->buildEditDialog($inline); 251 253 252 254 if ($this->getOperation() == 'reply') { 253 255 $edit_dialog->setTitle(pht('Reply to Inline Comment')); ··· 255 257 $edit_dialog->setTitle(pht('New Inline Comment')); 256 258 } 257 259 258 - // NOTE: We read the values from the client (the display values), not 259 - // the values from the database (the original values) when replying. 260 - // In particular, when replying to a ghost comment which was moved 261 - // across diffs and then moved backward to the most recent visible 262 - // line, we want to reply on the display line (which exists), not on 263 - // the comment's original line (which may not exist in this changeset). 264 - $is_new = $this->getIsNewFile(); 265 - $number = $this->getLineNumber(); 266 - $length = $this->getLineLength(); 267 - 268 - $edit_dialog->addHiddenInput('op', 'create'); 269 - $edit_dialog->addHiddenInput('is_new', $is_new); 270 - $edit_dialog->addHiddenInput('number', $number); 271 - $edit_dialog->addHiddenInput('length', $length); 272 - 273 - $text_area = $this->renderTextArea($this->getCommentText()); 274 - $edit_dialog->appendChild($text_area); 275 - 276 260 $view = $this->buildScaffoldForView($edit_dialog); 277 261 278 - return id(new AphrontAjaxResponse()) 279 - ->setContent($view->render()); 262 + return $this->newInlineResponse($inline, $view); 280 263 } 281 264 } 282 265 ··· 320 303 } 321 304 } 322 305 323 - private function buildEditDialog() { 306 + private function buildEditDialog(PhabricatorInlineComment $inline) { 324 307 $request = $this->getRequest(); 325 308 $viewer = $this->getViewer(); 326 309 327 310 $edit_dialog = id(new PHUIDiffInlineCommentEditView()) 328 - ->setUser($viewer) 329 - ->setSubmitURI($request->getRequestURI()) 311 + ->setViewer($viewer) 312 + ->setInlineComment($inline) 330 313 ->setIsOnRight($this->getIsOnRight()) 331 - ->setIsNewFile($this->getIsNewFile()) 332 - ->setNumber($this->getLineNumber()) 333 - ->setLength($this->getLineLength()) 334 - ->setRenderer($this->getRenderer()) 335 - ->setReplyToCommentPHID($this->getReplyToCommentPHID()) 336 - ->setChangesetID($this->getChangesetID()); 314 + ->setRenderer($this->getRenderer()); 337 315 338 316 return $edit_dialog; 339 317 } ··· 342 320 return id(new AphrontAjaxResponse()) 343 321 ->setContent( 344 322 array( 345 - 'markup' => '', 323 + 'inline' => array(), 324 + 'view' => null, 346 325 )); 347 326 } 348 327 349 328 private function buildRenderedCommentResponse( 350 - PhabricatorInlineCommentInterface $inline, 329 + PhabricatorInlineComment $inline, 351 330 $on_right) { 352 331 353 332 $request = $this->getRequest(); ··· 357 336 $engine->setViewer($viewer); 358 337 $engine->addObject( 359 338 $inline, 360 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 339 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 361 340 $engine->process(); 362 341 363 342 $phids = array($viewer->getPHID()); ··· 377 356 378 357 $view = $this->buildScaffoldForView($view); 379 358 380 - return id(new AphrontAjaxResponse()) 381 - ->setContent( 382 - array( 383 - 'inlineCommentID' => $inline->getID(), 384 - 'markup' => $view->render(), 385 - )); 386 - } 387 - 388 - private function renderTextArea($text) { 389 - return id(new PhabricatorRemarkupControl()) 390 - ->setViewer($this->getViewer()) 391 - ->setSigil('differential-inline-comment-edit-textarea') 392 - ->setName('text') 393 - ->setValue($text) 394 - ->setDisableFullScreen(true); 359 + return $this->newInlineResponse($inline, $view); 395 360 } 396 361 397 362 private function buildScaffoldForView(PHUIDiffInlineCommentView $view) { ··· 402 367 403 368 return id(new PHUIDiffInlineCommentTableScaffold()) 404 369 ->addRowScaffold($view); 370 + } 371 + 372 + private function newInlineResponse( 373 + PhabricatorInlineComment $inline, 374 + $view) { 375 + 376 + $response = array( 377 + 'inline' => array( 378 + 'id' => $inline->getID(), 379 + ), 380 + 'view' => hsprintf('%s', $view), 381 + ); 382 + 383 + return id(new AphrontAjaxResponse()) 384 + ->setContent($response); 405 385 } 406 386 407 387 }
+2 -2
src/infrastructure/diff/PhabricatorInlineCommentPreviewController.php
··· 11 11 $viewer = $request->getUser(); 12 12 13 13 $inlines = $this->loadInlineComments(); 14 - assert_instances_of($inlines, 'PhabricatorInlineCommentInterface'); 14 + assert_instances_of($inlines, 'PhabricatorInlineComment'); 15 15 16 16 $engine = new PhabricatorMarkupEngine(); 17 17 $engine->setViewer($viewer); 18 18 foreach ($inlines as $inline) { 19 19 $engine->addObject( 20 20 $inline, 21 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 21 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 22 22 } 23 23 $engine->process(); 24 24
+232
src/infrastructure/diff/interface/PhabricatorInlineComment.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorInlineComment 4 + extends Phobject 5 + implements 6 + PhabricatorMarkupInterface { 7 + 8 + const MARKUP_FIELD_BODY = 'markup:body'; 9 + 10 + const STATE_UNDONE = 'undone'; 11 + const STATE_DRAFT = 'draft'; 12 + const STATE_UNDRAFT = 'undraft'; 13 + const STATE_DONE = 'done'; 14 + 15 + private $storageObject; 16 + private $syntheticAuthor; 17 + private $isGhost; 18 + 19 + public function __clone() { 20 + $this->storageObject = clone $this->storageObject; 21 + } 22 + 23 + public function setSyntheticAuthor($synthetic_author) { 24 + $this->syntheticAuthor = $synthetic_author; 25 + return $this; 26 + } 27 + 28 + public function getSyntheticAuthor() { 29 + return $this->syntheticAuthor; 30 + } 31 + 32 + public function setStorageObject($storage_object) { 33 + $this->storageObject = $storage_object; 34 + return $this; 35 + } 36 + 37 + public function getStorageObject() { 38 + if (!$this->storageObject) { 39 + $this->storageObject = $this->newStorageObject(); 40 + } 41 + 42 + return $this->storageObject; 43 + } 44 + 45 + abstract protected function newStorageObject(); 46 + abstract public function getControllerURI(); 47 + 48 + abstract public function setChangesetID($id); 49 + abstract public function getChangesetID(); 50 + 51 + abstract public function supportsHiding(); 52 + abstract public function isHidden(); 53 + 54 + public function isDraft() { 55 + return !$this->getTransactionPHID(); 56 + } 57 + 58 + public function getTransactionPHID() { 59 + return $this->getStorageObject()->getTransactionPHID(); 60 + } 61 + 62 + public function isCompatible(PhabricatorInlineComment $comment) { 63 + return 64 + ($this->getAuthorPHID() === $comment->getAuthorPHID()) && 65 + ($this->getSyntheticAuthor() === $comment->getSyntheticAuthor()) && 66 + ($this->getContent() === $comment->getContent()); 67 + } 68 + 69 + public function setIsGhost($is_ghost) { 70 + $this->isGhost = $is_ghost; 71 + return $this; 72 + } 73 + 74 + public function getIsGhost() { 75 + return $this->isGhost; 76 + } 77 + 78 + public function setContent($content) { 79 + $this->getStorageObject()->setContent($content); 80 + return $this; 81 + } 82 + 83 + public function getContent() { 84 + return $this->getStorageObject()->getContent(); 85 + } 86 + 87 + public function getID() { 88 + return $this->getStorageObject()->getID(); 89 + } 90 + 91 + public function getPHID() { 92 + return $this->getStorageObject()->getPHID(); 93 + } 94 + 95 + public function setIsNewFile($is_new) { 96 + $this->getStorageObject()->setIsNewFile($is_new); 97 + return $this; 98 + } 99 + 100 + public function getIsNewFile() { 101 + return $this->getStorageObject()->getIsNewFile(); 102 + } 103 + 104 + public function setFixedState($state) { 105 + $this->getStorageObject()->setFixedState($state); 106 + return $this; 107 + } 108 + 109 + public function setHasReplies($has_replies) { 110 + $this->getStorageObject()->setHasReplies($has_replies); 111 + return $this; 112 + } 113 + 114 + public function getHasReplies() { 115 + return $this->getStorageObject()->getHasReplies(); 116 + } 117 + 118 + public function getFixedState() { 119 + return $this->getStorageObject()->getFixedState(); 120 + } 121 + 122 + public function setLineNumber($number) { 123 + $this->getStorageObject()->setLineNumber($number); 124 + return $this; 125 + } 126 + 127 + public function getLineNumber() { 128 + return $this->getStorageObject()->getLineNumber(); 129 + } 130 + 131 + public function setLineLength($length) { 132 + $this->getStorageObject()->setLineLength($length); 133 + return $this; 134 + } 135 + 136 + public function getLineLength() { 137 + return $this->getStorageObject()->getLineLength(); 138 + } 139 + 140 + public function setAuthorPHID($phid) { 141 + $this->getStorageObject()->setAuthorPHID($phid); 142 + return $this; 143 + } 144 + 145 + public function getAuthorPHID() { 146 + return $this->getStorageObject()->getAuthorPHID(); 147 + } 148 + 149 + public function setReplyToCommentPHID($phid) { 150 + $this->getStorageObject()->setReplyToCommentPHID($phid); 151 + return $this; 152 + } 153 + 154 + public function getReplyToCommentPHID() { 155 + return $this->getStorageObject()->getReplyToCommentPHID(); 156 + } 157 + 158 + public function setIsDeleted($is_deleted) { 159 + $this->getStorageObject()->setIsDeleted($is_deleted); 160 + return $this; 161 + } 162 + 163 + public function getIsDeleted() { 164 + return $this->getStorageObject()->getIsDeleted(); 165 + } 166 + 167 + public function setIsEditing($is_editing) { 168 + $this->getStorageObject()->setAttribute('editing', (bool)$is_editing); 169 + return $this; 170 + } 171 + 172 + public function getIsEditing() { 173 + return (bool)$this->getStorageObject()->getAttribute('editing', false); 174 + } 175 + 176 + public function getDateModified() { 177 + return $this->getStorageObject()->getDateModified(); 178 + } 179 + 180 + public function getDateCreated() { 181 + return $this->getStorageObject()->getDateCreated(); 182 + } 183 + 184 + public function openTransaction() { 185 + $this->getStorageObject()->openTransaction(); 186 + } 187 + 188 + public function saveTransaction() { 189 + $this->getStorageObject()->saveTransaction(); 190 + } 191 + 192 + public function save() { 193 + $this->getTransactionCommentForSave()->save(); 194 + return $this; 195 + } 196 + 197 + public function delete() { 198 + $this->getStorageObject()->delete(); 199 + return $this; 200 + } 201 + 202 + public function makeEphemeral() { 203 + $this->getStorageObject()->makeEphemeral(); 204 + return $this; 205 + } 206 + 207 + 208 + /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ 209 + 210 + 211 + public function getMarkupFieldKey($field) { 212 + $content = $this->getMarkupText($field); 213 + return PhabricatorMarkupEngine::digestRemarkupContent($this, $content); 214 + } 215 + 216 + public function newMarkupEngine($field) { 217 + return PhabricatorMarkupEngine::newDifferentialMarkupEngine(); 218 + } 219 + 220 + public function getMarkupText($field) { 221 + return $this->getContent(); 222 + } 223 + 224 + public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 225 + return $output; 226 + } 227 + 228 + public function shouldUseMarkupCache($field) { 229 + return !$this->isDraft(); 230 + } 231 + 232 + }
-66
src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php
··· 1 - <?php 2 - 3 - /** 4 - * Shared interface used by Differential and Diffusion inline comments. 5 - */ 6 - interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface { 7 - 8 - const MARKUP_FIELD_BODY = 'markup:body'; 9 - 10 - const STATE_UNDONE = 'undone'; 11 - const STATE_DRAFT = 'draft'; 12 - const STATE_UNDRAFT = 'undraft'; 13 - const STATE_DONE = 'done'; 14 - 15 - public function setChangesetID($id); 16 - public function getChangesetID(); 17 - 18 - public function setIsNewFile($is_new); 19 - public function getIsNewFile(); 20 - 21 - public function setLineNumber($number); 22 - public function getLineNumber(); 23 - 24 - public function setLineLength($length); 25 - public function getLineLength(); 26 - 27 - public function setReplyToCommentPHID($phid); 28 - public function getReplyToCommentPHID(); 29 - 30 - public function setHasReplies($has_replies); 31 - public function getHasReplies(); 32 - 33 - public function setIsDeleted($deleted); 34 - public function getIsDeleted(); 35 - 36 - public function setFixedState($state); 37 - public function getFixedState(); 38 - 39 - public function setContent($content); 40 - public function getContent(); 41 - 42 - public function setCache($cache); 43 - public function getCache(); 44 - 45 - public function setAuthorPHID($phid); 46 - public function getAuthorPHID(); 47 - 48 - public function setSyntheticAuthor($synthetic_author); 49 - public function getSyntheticAuthor(); 50 - 51 - public function isCompatible(PhabricatorInlineCommentInterface $inline); 52 - public function isDraft(); 53 - 54 - public function save(); 55 - public function delete(); 56 - 57 - public function setIsGhost($is_ghost); 58 - public function getIsGhost(); 59 - 60 - public function supportsHiding(); 61 - public function isHidden(); 62 - 63 - public function getDateModified(); 64 - public function getDateCreated(); 65 - 66 - }
+10 -57
src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
··· 3 3 final class PHUIDiffInlineCommentDetailView 4 4 extends PHUIDiffInlineCommentView { 5 5 6 - private $inlineComment; 7 6 private $handles; 8 7 private $markupEngine; 9 8 private $editable; 10 9 private $preview; 11 10 private $allowReply; 12 - private $renderer; 13 11 private $canMarkDone; 14 12 private $objectOwnerPHID; 15 13 16 - public function setInlineComment(PhabricatorInlineCommentInterface $comment) { 17 - $this->inlineComment = $comment; 18 - return $this; 19 - } 20 - 21 14 public function isHidden() { 22 - return $this->inlineComment->isHidden(); 15 + return $this->getInlineComment()->isHidden(); 23 16 } 24 17 25 18 public function setHandles(array $handles) { ··· 48 41 return $this; 49 42 } 50 43 51 - public function setRenderer($renderer) { 52 - $this->renderer = $renderer; 53 - return $this; 54 - } 55 - 56 - public function getRenderer() { 57 - return $this->renderer; 58 - } 59 - 60 44 public function setCanMarkDone($can_mark_done) { 61 45 $this->canMarkDone = $can_mark_done; 62 46 return $this; ··· 76 60 } 77 61 78 62 public function getAnchorName() { 79 - $inline = $this->inlineComment; 63 + $inline = $this->getInlineComment(); 80 64 if ($inline->getID()) { 81 65 return 'inline-'.$inline->getID(); 82 66 } ··· 93 77 94 78 public function render() { 95 79 require_celerity_resource('phui-inline-comment-view-css'); 96 - $inline = $this->inlineComment; 80 + $inline = $this->getInlineComment(); 97 81 98 82 $classes = array( 99 83 'differential-inline-comment', 100 84 ); 101 85 102 - $is_fixed = false; 103 - switch ($inline->getFixedState()) { 104 - case PhabricatorInlineCommentInterface::STATE_DONE: 105 - case PhabricatorInlineCommentInterface::STATE_DRAFT: 106 - $is_fixed = true; 107 - break; 108 - } 109 - 110 - $is_draft_done = false; 111 - switch ($inline->getFixedState()) { 112 - case PhabricatorInlineCommentInterface::STATE_DRAFT: 113 - case PhabricatorInlineCommentInterface::STATE_UNDRAFT: 114 - $is_draft_done = true; 115 - break; 116 - } 117 - 118 86 $is_synthetic = false; 119 87 if ($inline->getSyntheticAuthor()) { 120 88 $is_synthetic = true; 121 89 } 122 90 123 - $metadata = array( 124 - 'id' => $inline->getID(), 125 - 'phid' => $inline->getPHID(), 126 - 'changesetID' => $inline->getChangesetID(), 127 - 'number' => $inline->getLineNumber(), 128 - 'length' => $inline->getLineLength(), 129 - 'isNewFile' => (bool)$inline->getIsNewFile(), 130 - 'on_right' => $this->getIsOnRight(), 131 - 'original' => $inline->getContent(), 132 - 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), 133 - 'isDraft' => $inline->isDraft(), 134 - 'isFixed' => $is_fixed, 135 - 'isGhost' => $inline->getIsGhost(), 136 - 'isSynthetic' => $is_synthetic, 137 - 'isDraftDone' => $is_draft_done, 138 - ); 91 + $metadata = $this->getInlineCommentMetadata(); 139 92 140 93 $sigil = 'differential-inline-comment'; 141 94 if ($this->preview) { ··· 299 252 if (!$is_synthetic) { 300 253 $draft_state = false; 301 254 switch ($inline->getFixedState()) { 302 - case PhabricatorInlineCommentInterface::STATE_DRAFT: 255 + case PhabricatorInlineComment::STATE_DRAFT: 303 256 $is_done = $mark_done; 304 257 $draft_state = true; 305 258 break; 306 - case PhabricatorInlineCommentInterface::STATE_UNDRAFT: 259 + case PhabricatorInlineComment::STATE_UNDRAFT: 307 260 $is_done = !$mark_done; 308 261 $draft_state = true; 309 262 break; 310 - case PhabricatorInlineCommentInterface::STATE_DONE: 263 + case PhabricatorInlineComment::STATE_DONE: 311 264 $is_done = true; 312 265 break; 313 266 default: 314 - case PhabricatorInlineCommentInterface::STATE_UNDONE: 267 + case PhabricatorInlineComment::STATE_UNDONE: 315 268 $is_done = false; 316 269 break; 317 270 } ··· 372 325 373 326 $content = $this->markupEngine->getOutput( 374 327 $inline, 375 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 328 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 376 329 377 330 if ($this->preview) { 378 331 $anchor = null; ··· 490 443 } 491 444 492 445 private function canHide() { 493 - $inline = $this->inlineComment; 446 + $inline = $this->getInlineComment(); 494 447 495 448 if ($inline->isDraft()) { 496 449 return false;
+31 -85
src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php
··· 3 3 final class PHUIDiffInlineCommentEditView 4 4 extends PHUIDiffInlineCommentView { 5 5 6 - private $inputs = array(); 7 - private $uri; 8 6 private $title; 9 - private $number; 10 - private $length; 11 - private $renderer; 12 - private $isNewFile; 13 - private $replyToCommentPHID; 14 - private $changesetID; 15 - 16 - public function setIsNewFile($is_new_file) { 17 - $this->isNewFile = $is_new_file; 18 - return $this; 19 - } 20 - 21 - public function getIsNewFile() { 22 - return $this->isNewFile; 23 - } 24 - 25 - public function setRenderer($renderer) { 26 - $this->renderer = $renderer; 27 - return $this; 28 - } 29 - 30 - public function getRenderer() { 31 - return $this->renderer; 32 - } 33 - 34 - public function addHiddenInput($key, $value) { 35 - $this->inputs[] = array($key, $value); 36 - return $this; 37 - } 38 - 39 - public function setSubmitURI($uri) { 40 - $this->uri = $uri; 41 - return $this; 42 - } 43 7 44 8 public function setTitle($title) { 45 9 $this->title = $title; 46 10 return $this; 47 11 } 48 12 49 - public function setReplyToCommentPHID($reply_to_phid) { 50 - $this->replyToCommentPHID = $reply_to_phid; 51 - return $this; 52 - } 53 - 54 - public function getReplyToCommentPHID() { 55 - return $this->replyToCommentPHID; 56 - } 57 - 58 - public function setChangesetID($changeset_id) { 59 - $this->changesetID = $changeset_id; 60 - return $this; 61 - } 62 - 63 - public function getChangesetID() { 64 - return $this->changesetID; 65 - } 66 - 67 - public function setNumber($number) { 68 - $this->number = $number; 69 - return $this; 70 - } 71 - 72 - public function setLength($length) { 73 - $this->length = $length; 74 - return $this; 75 - } 76 - 77 13 public function render() { 78 - if (!$this->uri) { 79 - throw new PhutilInvalidStateException('setSubmitURI'); 80 - } 81 - 82 14 $viewer = $this->getViewer(); 15 + $inline = $this->getInlineComment(); 83 16 84 17 $content = phabricator_form( 85 18 $viewer, 86 19 array( 87 - 'action' => $this->uri, 88 - 'method' => 'POST', 89 - 'sigil' => 'inline-edit-form', 20 + 'action' => $inline->getControllerURI(), 21 + 'method' => 'POST', 22 + 'sigil' => 'inline-edit-form', 90 23 ), 91 24 array( 92 25 $this->renderInputs(), ··· 97 30 } 98 31 99 32 private function renderInputs() { 100 - $inputs = $this->inputs; 101 - $out = array(); 33 + $inputs = array(); 34 + $inline = $this->getInlineComment(); 102 35 103 - $inputs[] = array('on_right', (bool)$this->getIsOnRight()); 104 - $inputs[] = array('replyToCommentPHID', $this->getReplyToCommentPHID()); 36 + $inputs[] = array('op', 'edit'); 37 + $inputs[] = array('id', $inline->getID()); 38 + 39 + $inputs[] = array('on_right', $this->getIsOnRight()); 105 40 $inputs[] = array('renderer', $this->getRenderer()); 106 - $inputs[] = array('changesetID', $this->getChangesetID()); 41 + 42 + $out = array(); 107 43 108 44 foreach ($inputs as $input) { 109 45 list($name, $value) = $input; ··· 115 51 'value' => $value, 116 52 )); 117 53 } 54 + 118 55 return $out; 119 56 } 120 57 ··· 141 78 array( 142 79 'class' => 'differential-inline-comment-edit-body', 143 80 ), 144 - $this->renderChildren()); 81 + $this->newTextarea()); 145 82 146 83 $edit = phutil_tag( 147 84 'div', ··· 152 89 $buttons, 153 90 )); 154 91 92 + $inline = $this->getInlineComment(); 93 + 155 94 return javelin_tag( 156 95 'div', 157 96 array( 158 97 'class' => 'differential-inline-comment-edit', 159 98 'sigil' => 'differential-inline-comment', 160 - 'meta' => array( 161 - 'changesetID' => $this->getChangesetID(), 162 - 'on_right' => $this->getIsOnRight(), 163 - 'isNewFile' => (bool)$this->getIsNewFile(), 164 - 'number' => $this->number, 165 - 'length' => $this->length, 166 - 'replyToCommentPHID' => $this->getReplyToCommentPHID(), 167 - ), 99 + 'meta' => $this->getInlineCommentMetadata(), 168 100 ), 169 101 array( 170 102 $title, 171 103 $body, 172 104 $edit, 173 105 )); 106 + } 107 + 108 + private function newTextarea() { 109 + $viewer = $this->getViewer(); 110 + $inline = $this->getInlineComment(); 111 + 112 + $text = $inline->getContent(); 113 + 114 + return id(new PhabricatorRemarkupControl()) 115 + ->setViewer($viewer) 116 + ->setSigil('differential-inline-comment-edit-textarea') 117 + ->setName('text') 118 + ->setValue($text) 119 + ->setDisableFullScreen(true); 174 120 } 175 121 176 122 }
+1 -1
src/infrastructure/diff/view/PHUIDiffInlineCommentPreviewListView.php
··· 54 54 foreach ($inlines as $inline) { 55 55 $engine->addObject( 56 56 $inline, 57 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 57 + PhabricatorInlineComment::MARKUP_FIELD_BODY); 58 58 } 59 59 $engine->process(); 60 60
+62
src/infrastructure/diff/view/PHUIDiffInlineCommentView.php
··· 3 3 abstract class PHUIDiffInlineCommentView extends AphrontView { 4 4 5 5 private $isOnRight; 6 + private $renderer; 7 + private $inlineComment; 8 + 9 + public function setInlineComment(PhabricatorInlineComment $comment) { 10 + $this->inlineComment = $comment; 11 + return $this; 12 + } 13 + 14 + public function getInlineComment() { 15 + return $this->inlineComment; 16 + } 6 17 7 18 public function getIsOnRight() { 8 19 return $this->isOnRight; ··· 13 24 return $this; 14 25 } 15 26 27 + public function setRenderer($renderer) { 28 + $this->renderer = $renderer; 29 + return $this; 30 + } 31 + 32 + public function getRenderer() { 33 + return $this->renderer; 34 + } 35 + 16 36 public function getScaffoldCellID() { 17 37 return null; 18 38 } ··· 32 52 return null; 33 53 } 34 54 } 55 + 56 + protected function getInlineCommentMetadata() { 57 + $inline = $this->getInlineComment(); 58 + 59 + $is_synthetic = (bool)$inline->getSyntheticAuthor(); 60 + 61 + $is_fixed = false; 62 + switch ($inline->getFixedState()) { 63 + case PhabricatorInlineComment::STATE_DONE: 64 + case PhabricatorInlineComment::STATE_DRAFT: 65 + $is_fixed = true; 66 + break; 67 + } 68 + 69 + $is_draft_done = false; 70 + switch ($inline->getFixedState()) { 71 + case PhabricatorInlineComment::STATE_DRAFT: 72 + case PhabricatorInlineComment::STATE_UNDRAFT: 73 + $is_draft_done = true; 74 + break; 75 + } 76 + 77 + return array( 78 + 'id' => $inline->getID(), 79 + 'phid' => $inline->getPHID(), 80 + 'changesetID' => $inline->getChangesetID(), 81 + 'number' => $inline->getLineNumber(), 82 + 'length' => $inline->getLineLength(), 83 + 'isNewFile' => (bool)$inline->getIsNewFile(), 84 + 'original' => $inline->getContent(), 85 + 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), 86 + 'isDraft' => $inline->isDraft(), 87 + 'isFixed' => $is_fixed, 88 + 'isGhost' => $inline->getIsGhost(), 89 + 'isSynthetic' => $is_synthetic, 90 + 'isDraftDone' => $is_draft_done, 91 + 'isEditing' => $inline->getIsEditing(), 92 + 93 + 'on_right' => $this->getIsOnRight(), 94 + ); 95 + } 96 + 35 97 36 98 }
+109 -74
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 26 26 var onexpand = JX.bind(this, this._ifawake, this._oncollapse, false); 27 27 JX.Stratcom.listen('click', 'reveal-inline', onexpand); 28 28 29 - var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit'); 30 - JX.Stratcom.listen( 31 - 'click', 32 - ['differential-inline-comment', 'differential-inline-edit'], 33 - onedit); 34 - 35 - var ondone = JX.bind(this, this._ifawake, this._onaction, 'done'); 36 - JX.Stratcom.listen( 37 - 'click', 38 - ['differential-inline-comment', 'differential-inline-done'], 39 - ondone); 40 - 41 - var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete'); 42 - JX.Stratcom.listen( 43 - 'click', 44 - ['differential-inline-comment', 'differential-inline-delete'], 45 - ondelete); 46 - 47 - var onreply = JX.bind(this, this._ifawake, this._onaction, 'reply'); 48 - JX.Stratcom.listen( 49 - 'click', 50 - ['differential-inline-comment', 'differential-inline-reply'], 51 - onreply); 52 - 53 29 var onresize = JX.bind(this, this._ifawake, this._onresize); 54 30 JX.Stratcom.listen('resize', null, onresize); 55 31 ··· 85 61 'mouseup', 86 62 null, 87 63 onrangeup); 64 + 65 + this._setupInlineCommentListeners(); 88 66 }, 89 67 90 68 properties: { ··· 1163 1141 } 1164 1142 }, 1165 1143 1166 - _onaction: function(action, e) { 1167 - e.kill(); 1168 - 1169 - var inline = this._getInlineForEvent(e); 1170 - var is_ref = false; 1171 - 1172 - // If we don't have a natural inline object, the user may have clicked 1173 - // an action (like "Delete") inside a preview element at the bottom of 1174 - // the page. 1175 - 1176 - // If they did, try to find an associated normal inline to act on, and 1177 - // pretend they clicked that instead. This makes the overall state of 1178 - // the page more consistent. 1179 - 1180 - // However, there may be no normal inline (for example, because it is 1181 - // on a version of the diff which is not visible). In this case, we 1182 - // act by reference. 1183 - 1184 - if (inline === null) { 1185 - var data = e.getNodeData('differential-inline-comment'); 1186 - inline = this.getInlineByID(data.id); 1187 - if (inline) { 1188 - is_ref = true; 1189 - } else { 1190 - switch (action) { 1191 - case 'delete': 1192 - this._deleteInlineByID(data.id); 1193 - return; 1194 - } 1195 - } 1196 - } 1197 - 1198 - // TODO: For normal operations, highlight the inline range here. 1199 - 1200 - switch (action) { 1201 - case 'edit': 1202 - inline.edit(); 1203 - break; 1204 - case 'done': 1205 - inline.toggleDone(); 1206 - break; 1207 - case 'delete': 1208 - inline.delete(is_ref); 1209 - break; 1210 - case 'reply': 1211 - inline.reply(); 1212 - break; 1213 - } 1214 - }, 1215 - 1216 1144 redrawPreview: function() { 1217 1145 // TODO: This isn't the cleanest way to find the preview form, but 1218 1146 // rendering no longer has direct access to it. ··· 2138 2066 2139 2067 var tree = this._getTreeView(); 2140 2068 JX.DOM.setContent(flank_body, tree.getNode()); 2069 + }, 2070 + 2071 + _setupInlineCommentListeners: function() { 2072 + var onsave = JX.bind(this, this._onInlineEvent, 'save'); 2073 + JX.Stratcom.listen( 2074 + ['submit', 'didSyntheticSubmit'], 2075 + 'inline-edit-form', 2076 + onsave); 2077 + 2078 + var oncancel = JX.bind(this, this._onInlineEvent, 'cancel'); 2079 + JX.Stratcom.listen( 2080 + 'click', 2081 + 'inline-edit-cancel', 2082 + oncancel); 2083 + 2084 + var onundo = JX.bind(this, this._onInlineEvent, 'undo'); 2085 + JX.Stratcom.listen( 2086 + 'click', 2087 + 'differential-inline-comment-undo', 2088 + onundo); 2089 + 2090 + var onedit = JX.bind(this, this._onInlineEvent, 'edit'); 2091 + JX.Stratcom.listen( 2092 + 'click', 2093 + ['differential-inline-comment', 'differential-inline-edit'], 2094 + onedit); 2095 + 2096 + var ondone = JX.bind(this, this._onInlineEvent, 'done'); 2097 + JX.Stratcom.listen( 2098 + 'click', 2099 + ['differential-inline-comment', 'differential-inline-done'], 2100 + ondone); 2101 + 2102 + var ondelete = JX.bind(this, this._onInlineEvent, 'delete'); 2103 + JX.Stratcom.listen( 2104 + 'click', 2105 + ['differential-inline-comment', 'differential-inline-delete'], 2106 + ondelete); 2107 + 2108 + var onreply = JX.bind(this, this._onInlineEvent, 'reply'); 2109 + JX.Stratcom.listen( 2110 + 'click', 2111 + ['differential-inline-comment', 'differential-inline-reply'], 2112 + onreply); 2113 + }, 2114 + 2115 + _onInlineEvent: function(action, e) { 2116 + if (this.isAsleep()) { 2117 + return; 2118 + } 2119 + 2120 + e.kill(); 2121 + 2122 + var inline = this._getInlineForEvent(e); 2123 + var is_ref = false; 2124 + 2125 + // If we don't have a natural inline object, the user may have clicked 2126 + // an action (like "Delete") inside a preview element at the bottom of 2127 + // the page. 2128 + 2129 + // If they did, try to find an associated normal inline to act on, and 2130 + // pretend they clicked that instead. This makes the overall state of 2131 + // the page more consistent. 2132 + 2133 + // However, there may be no normal inline (for example, because it is 2134 + // on a version of the diff which is not visible). In this case, we 2135 + // act by reference. 2136 + 2137 + if (inline === null) { 2138 + var data = e.getNodeData('differential-inline-comment'); 2139 + inline = this.getInlineByID(data.id); 2140 + if (inline) { 2141 + is_ref = true; 2142 + } else { 2143 + switch (action) { 2144 + case 'delete': 2145 + this._deleteInlineByID(data.id); 2146 + return; 2147 + } 2148 + } 2149 + } 2150 + 2151 + // TODO: For normal operations, highlight the inline range here. 2152 + 2153 + switch (action) { 2154 + case 'save': 2155 + inline.save(e.getTarget()); 2156 + break; 2157 + case 'cancel': 2158 + inline.cancel(); 2159 + break; 2160 + case 'undo': 2161 + inline.undo(); 2162 + break; 2163 + case 'edit': 2164 + inline.edit(); 2165 + break; 2166 + case 'done': 2167 + inline.toggleDone(); 2168 + break; 2169 + case 'delete': 2170 + inline.delete(is_ref); 2171 + break; 2172 + case 'reply': 2173 + inline.reply(); 2174 + break; 2175 + } 2141 2176 } 2142 2177 2143 2178 }
+63 -79
webroot/rsrc/js/application/diff/DiffInline.js
··· 18 18 _length: null, 19 19 _displaySide: null, 20 20 _isNewFile: null, 21 - _undoRow: null, 22 21 _replyToCommentPHID: null, 23 22 _originalText: null, 24 23 _snippet: null, ··· 38 37 _isSynthetic: false, 39 38 _isHidden: false, 40 39 40 + _editRow: null, 41 + _undoRow: null, 42 + _undoType: null, 43 + _undoText: null, 44 + 41 45 bindToRow: function(row) { 42 46 this._row = row; 43 47 ··· 50 54 var comment = JX.DOM.find(row, 'div', 'differential-inline-comment'); 51 55 var data = JX.Stratcom.getData(comment); 52 56 53 - this._id = data.id; 57 + this._readInlineState(data); 54 58 this._phid = data.phid; 55 59 56 - // TODO: This is very, very, very, very, very, very, very hacky. 57 - var td = comment.parentNode; 58 - var th = td.previousSibling; 59 - if (th.parentNode.firstChild != th) { 60 + if (data.on_right) { 60 61 this._displaySide = 'right'; 61 62 } else { 62 63 this._displaySide = 'left'; ··· 65 66 this._number = parseInt(data.number, 10); 66 67 this._length = parseInt(data.length, 10); 67 68 this._originalText = data.original; 68 - this._isNewFile = 69 - (this.getDisplaySide() == 'right') || 70 - (data.left != data.right); 69 + this._isNewFile = data.isNewFile; 71 70 72 71 this._replyToCommentPHID = data.replyToCommentPHID; 73 72 ··· 81 80 this._isNew = false; 82 81 this._snippet = data.snippet; 83 82 84 - this.setInvisible(false); 83 + this._isEditing = data.isEditing; 84 + 85 + if (this._isEditing) { 86 + this.edit(); 87 + } else { 88 + this.setInvisible(false); 89 + } 85 90 86 91 return this; 87 92 }, ··· 397 402 op = 'delete'; 398 403 } 399 404 405 + // If there's an existing "unedit" undo element, remove it. 406 + if (this._undoRow) { 407 + JX.DOM.remove(this._undoRow); 408 + this._undoRow = null; 409 + } 410 + 400 411 var data = this._newRequestData(op); 401 412 402 413 this.setLoading(true); ··· 472 483 }, 473 484 474 485 _oneditresponse: function(response) { 475 - var rows = JX.$H(response).getNode(); 486 + var rows = JX.$H(response.view).getNode(); 476 487 488 + this._readInlineState(response.inline); 477 489 this._drawEditRows(rows); 478 490 479 491 this.setLoading(false); ··· 481 493 }, 482 494 483 495 _oncreateresponse: function(response) { 484 - var rows = JX.$H(response).getNode(); 496 + var rows = JX.$H(response.view).getNode(); 485 497 498 + this._readInlineState(response.inline); 486 499 this._drawEditRows(rows); 487 500 }, 488 501 502 + _readInlineState: function(state) { 503 + this._id = state.id; 504 + }, 505 + 489 506 _ondeleteresponse: function() { 490 507 this._drawUndeleteRows(); 491 508 ··· 496 513 }, 497 514 498 515 _drawUndeleteRows: function() { 516 + this._undoType = 'undelete'; 517 + this._undoText = null; 518 + 499 519 return this._drawUndoRows('undelete', this._row); 500 520 }, 501 521 502 522 _drawUneditRows: function(text) { 523 + this._undoType = 'unedit'; 524 + this._undoText = text; 525 + 503 526 return this._drawUndoRows('unedit', null, text); 504 527 }, 505 528 ··· 523 546 524 547 _drawEditRows: function(rows) { 525 548 this.setEditing(true); 526 - return this._drawRows(rows, null, 'edit'); 549 + this._editRow = this._drawRows(rows, null, 'edit'); 527 550 }, 528 551 529 552 _drawRows: function(rows, cursor, type, text) { 530 553 var first_row = JX.DOM.scry(rows, 'tr')[0]; 531 - var first_meta; 532 554 var row = first_row; 533 555 var anchor = cursor || this._row; 534 556 cursor = cursor || this._row.nextSibling; 535 557 558 + 559 + var result_row; 536 560 var next_row; 537 561 while (row) { 538 562 // Grab this first, since it's going to change once we insert the row ··· 546 570 anchor.parentNode.insertBefore(row, cursor); 547 571 cursor = row; 548 572 549 - var row_meta = { 550 - node: row, 551 - type: type, 552 - text: text || null, 553 - listeners: [] 554 - }; 555 - 556 - if (!first_meta) { 557 - first_meta = row_meta; 558 - } 559 - 560 - if (type == 'edit') { 561 - row_meta.listeners.push( 562 - JX.DOM.listen( 563 - row, 564 - ['submit', 'didSyntheticSubmit'], 565 - 'inline-edit-form', 566 - JX.bind(this, this._onsubmit, row_meta))); 567 - 568 - row_meta.listeners.push( 569 - JX.DOM.listen( 570 - row, 571 - 'click', 572 - 'inline-edit-cancel', 573 - JX.bind(this, this._oncancel, row_meta))); 574 - } else if (type == 'content') { 575 - // No special listeners for these rows. 576 - } else { 577 - row_meta.listeners.push( 578 - JX.DOM.listen( 579 - row, 580 - 'click', 581 - 'differential-inline-comment-undo', 582 - JX.bind(this, this._onundo, row_meta))); 573 + if (!result_row) { 574 + result_row = row; 583 575 } 584 576 585 577 // If the row has a textarea, focus it. This allows the user to start ··· 602 594 603 595 JX.Stratcom.invoke('resize'); 604 596 605 - return first_meta; 597 + return result_row; 606 598 }, 607 599 608 - _onsubmit: function(row, e) { 609 - e.kill(); 610 - 611 - var handler = JX.bind(this, this._onsubmitresponse, row); 600 + save: function(form) { 601 + var handler = JX.bind(this, this._onsubmitresponse); 612 602 613 603 this.setLoading(true); 614 604 615 - JX.Workflow.newFromForm(e.getTarget()) 605 + JX.Workflow.newFromForm(form) 616 606 .setHandler(handler) 617 607 .start(); 618 608 }, 619 609 620 - _onundo: function(row, e) { 621 - e.kill(); 610 + undo: function() { 622 611 623 - this._removeRow(row); 612 + JX.DOM.remove(this._undoRow); 613 + this._undoRow = null; 624 614 625 - if (row.type == 'undelete') { 615 + if (this._undoType === 'undelete') { 626 616 var uri = this._getInlineURI(); 627 617 var data = this._newRequestData('undelete'); 628 618 var handler = JX.bind(this, this._onundelete); ··· 635 625 .send(); 636 626 } 637 627 638 - if (row.type == 'unedit') { 639 - if (this.getID()) { 640 - this.edit(row.text); 641 - } else { 642 - this.create(row.text); 643 - } 628 + if (this._undoType === 'unedit') { 629 + this.edit(this._undoText); 644 630 } 631 + 645 632 }, 646 633 647 634 _onundelete: function() { ··· 649 636 this._didUpdate(); 650 637 }, 651 638 652 - _oncancel: function(row, e) { 653 - e.kill(); 639 + cancel: function() { 640 + var text = this._readText(this._editRow); 654 641 655 - var text = this._readText(row.node); 642 + JX.DOM.remove(this._editRow); 643 + this._editRow = null; 644 + 656 645 if (text && text.length && (text != this._originalText)) { 657 646 this._drawUneditRows(text); 658 647 } 659 648 660 - this._removeRow(row); 661 649 this.setEditing(false); 662 650 663 651 this.setInvisible(false); ··· 679 667 return textarea.value; 680 668 }, 681 669 682 - _onsubmitresponse: function(row, response) { 683 - this._removeRow(row); 670 + _onsubmitresponse: function(response) { 671 + if (this._editRow) { 672 + JX.DOM.remove(this._editRow); 673 + this._editRow = null; 674 + } 684 675 685 676 this.setLoading(false); 686 677 this.setInvisible(false); ··· 691 682 692 683 _onupdate: function(response) { 693 684 var new_row; 694 - if (response.markup) { 695 - new_row = this._drawContentRows(JX.$H(response.markup).getNode()).node; 685 + if (response.view) { 686 + new_row = this._drawContentRows(JX.$H(response.view).getNode()); 696 687 } 697 688 698 689 // TODO: Save the old row so the action it's undo-able if it was a ··· 739 730 JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible); 740 731 JX.DOM.alterClass(row, 'differential-inline-loading', is_loading); 741 732 JX.DOM.alterClass(row, 'inline-hidden', is_collapsed); 742 - }, 743 - 744 - _removeRow: function(row) { 745 - JX.DOM.remove(row.node); 746 - for (var ii = 0; ii < row.listeners.length; ii++) { 747 - row.listeners[ii].remove(); 748 - } 749 733 }, 750 734 751 735 _getInlineURI: function() {