From 6fd55d692f14c9bf8d658b1a35ccf7c9216bb651 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 Mar 2021 11:24:36 -0700 Subject: [PATCH] Formally track "initial", "committed", and "active" states for inline comments Summary: Ref T13559. Various client decisions depend on the "initial" or "committed" states of inline comments. Previously, these were informally constructed from "mostly similar" available values, or glossed over in some cases. On the server, save the initial state when creating a comment. Save the committed state when applying a "save" operation. Send all three states to the client. On the client, load and track all three states explicitly. Test Plan: Created inlines, etc. See followups. Maniphest Tasks: T13559 Differential Revision: https://secure.phabricator.com/D21653 --- resources/celerity/map.php | 14 +-- .../PhabricatorInlineCommentController.php | 101 +++++++++--------- .../interface/PhabricatorInlineComment.php | 90 ++++++++++++++-- .../diff/view/PHUIDiffInlineCommentView.php | 2 +- .../rsrc/js/application/diff/DiffInline.js | 32 ++++-- 5 files changed, 163 insertions(+), 76 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 47f0dfd9ad..af877e264a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -13,7 +13,7 @@ return array( 'core.pkg.js' => '68f29322', 'dark-console.pkg.js' => '187792c2', 'differential.pkg.css' => 'ffb69e3d', - 'differential.pkg.js' => 'fbde899f', + 'differential.pkg.js' => '59453886', 'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.js' => '78c9885d', 'maniphest.pkg.css' => '35995d6d', @@ -385,7 +385,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 'rsrc/js/application/diff/DiffChangeset.js' => 'd7d3ba75', 'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5', - 'rsrc/js/application/diff/DiffInline.js' => '62fff8eb', + 'rsrc/js/application/diff/DiffInline.js' => '26664c24', 'rsrc/js/application/diff/DiffInlineContentState.js' => '68e6339d', 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', 'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b', @@ -788,7 +788,7 @@ return array( 'phabricator-dashboard-css' => '5a205b9d', 'phabricator-diff-changeset' => 'd7d3ba75', 'phabricator-diff-changeset-list' => 'cc2c5de5', - 'phabricator-diff-inline' => '62fff8eb', + 'phabricator-diff-inline' => '26664c24', 'phabricator-diff-inline-content-state' => '68e6339d', 'phabricator-diff-path-view' => '8207abf9', 'phabricator-diff-tree-view' => '5d83623b', @@ -1162,6 +1162,10 @@ return array( 'javelin-json', 'phabricator-prefab', ), + '26664c24' => array( + 'javelin-dom', + 'phabricator-diff-inline-content-state', + ), '289bf236' => array( 'javelin-install', 'javelin-util', @@ -1532,10 +1536,6 @@ return array( 'javelin-request', 'javelin-uri', ), - '62fff8eb' => array( - 'javelin-dom', - 'phabricator-diff-inline-content-state', - ), '65bb0011' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 4930502066..0899c2af94 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -180,56 +180,60 @@ abstract class PhabricatorInlineCommentController $this->saveComment($inline); return $this->buildEmptyResponse(); - case 'edit': case 'save': $inline = $this->loadCommentByIDForEdit($this->getCommentID()); - if ($op === 'save') { - $this->updateCommentContentState($inline); + $this->updateCommentContentState($inline); + + $inline + ->setIsEditing(false) + ->setIsDeleted(0); + + // Since we're saving the comment, update the committed state. + $active_state = $inline->getContentState(); + $inline->setCommittedContentState($active_state); + + $this->saveComment($inline); + + return $this->buildRenderedCommentResponse( + $inline, + $this->getIsOnRight()); + case 'edit': + $inline = $this->loadCommentByIDForEdit($this->getCommentID()); + + // NOTE: At time of writing, the "editing" state of inlines is + // preserved by simulating a click on "Edit" when the inline loads. + + // In this case, we don't want to "saveComment()", because it + // recalculates object drafts and purges versioned drafts. + + // The recalculation is merely unnecessary (state doesn't change) + // but purging drafts means that loading a page and then closing it + // discards your drafts. + + // To avoid the purge, only invoke "saveComment()" if we actually + // have changes to apply. + + $is_dirty = false; + if (!$inline->getIsEditing()) { $inline - ->setIsEditing(false) - ->setIsDeleted(0); + ->setIsDeleted(0) + ->setIsEditing(true); - $this->saveComment($inline); + $is_dirty = true; + } - return $this->buildRenderedCommentResponse( - $inline, - $this->getIsOnRight()); + if ($this->hasContentState()) { + $this->updateCommentContentState($inline); + $is_dirty = true; } else { - // NOTE: At time of writing, the "editing" state of inlines is - // preserved by simulating a click on "Edit" when the inline loads. + PhabricatorInlineComment::loadAndAttachVersionedDrafts( + $viewer, + array($inline)); + } - // In this case, we don't want to "saveComment()", because it - // recalculates object drafts and purges versioned drafts. - - // The recalculation is merely unnecessary (state doesn't change) - // but purging drafts means that loading a page and then closing it - // discards your drafts. - - // To avoid the purge, only invoke "saveComment()" if we actually - // have changes to apply. - - $is_dirty = false; - if (!$inline->getIsEditing()) { - $inline - ->setIsDeleted(0) - ->setIsEditing(true); - - $is_dirty = true; - } - - if ($this->hasContentState()) { - $this->updateCommentContentState($inline); - $is_dirty = true; - } else { - PhabricatorInlineComment::loadAndAttachVersionedDrafts( - $viewer, - array($inline)); - } - - if ($is_dirty) { - $this->saveComment($inline); - } + if ($is_dirty) { + $this->saveComment($inline); } $edit_dialog = $this->buildEditDialog($inline) @@ -333,7 +337,10 @@ abstract class PhabricatorInlineCommentController $state = $inline->getContentState(); $default_suggestion = $inline->getDefaultSuggestionText(); $state->setContentSuggestionText($default_suggestion); + + $inline->setInitialContentState($state); $inline->setContentState($state); + $inline->setIsDeleted(0); $this->saveComment($inline); @@ -461,6 +468,7 @@ abstract class PhabricatorInlineCommentController PhabricatorInlineComment $inline, $view, $is_edit) { + $viewer = $this->getViewer(); if ($inline->getReplyToCommentPHID()) { $can_suggest = false; @@ -469,18 +477,15 @@ abstract class PhabricatorInlineCommentController } if ($is_edit) { - $viewer = $this->getViewer(); - $content_state = $inline->getContentStateForEdit($viewer); + $state = $inline->getContentStateMapForEdit($viewer); } else { - $content_state = $inline->getContentState(); + $state = $inline->getContentStateMap(); } - $state_map = $content_state->newStorageMap(); - $response = array( 'inline' => array( 'id' => $inline->getID(), - 'contentState' => $state_map, + 'state' => $state, 'canSuggestEdit' => $can_suggest, ), 'view' => hsprintf('%s', $view), diff --git a/src/infrastructure/diff/interface/PhabricatorInlineComment.php b/src/infrastructure/diff/interface/PhabricatorInlineComment.php index 1ab3db30b9..613dfb08aa 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineComment.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineComment.php @@ -336,13 +336,29 @@ abstract class PhabricatorInlineComment return $this->newContentState()->readFromRequest($request); } - public function getContentState() { - $state = $this->newContentState(); + public function getInitialContentState() { + return $this->getNamedContentState('inline.state.initial'); + } - $storage = $this->getStorageObject(); - $storage_map = $storage->getAttribute('inline.state'); - if (is_array($storage_map)) { - $state->readStorageMap($storage_map); + public function setInitialContentState( + PhabricatorInlineCommentContentState $state) { + return $this->setNamedContentState('inline.state.initial', $state); + } + + public function getCommittedContentState() { + return $this->getNamedContentState('inline.state.committed'); + } + + public function setCommittedContentState( + PhabricatorInlineCommentContentState $state) { + return $this->setNamedContentState('inline.state.committed', $state); + } + + public function getContentState() { + $state = $this->getNamedContentState('inline.state'); + + if (!$state) { + $state = $this->newContentState(); } $state->setContentText($this->getContent()); @@ -351,11 +367,31 @@ abstract class PhabricatorInlineComment } public function setContentState(PhabricatorInlineCommentContentState $state) { + $this->setContent($state->getContentText()); + + return $this->setNamedContentState('inline.state', $state); + } + + private function getNamedContentState($key) { + $storage = $this->getStorageObject(); + + $storage_map = $storage->getAttribute($key); + if (!is_array($storage_map)) { + return null; + } + + $state = $this->newContentState(); + $state->readStorageMap($storage_map); + return $state; + } + + private function setNamedContentState( + $key, + PhabricatorInlineCommentContentState $state) { + $storage = $this->getStorageObject(); $storage_map = $state->newStorageMap(); - $storage->setAttribute('inline.state', $storage_map); - - $this->setContent($state->getContentText()); + $storage->setAttribute($key, $storage_map); return $this; } @@ -364,6 +400,42 @@ abstract class PhabricatorInlineComment return $this->getStorageObject()->getInlineContext(); } + public function getContentStateMapForEdit(PhabricatorUser $viewer) { + return $this->getWireContentStateMap(true, $viewer); + } + + public function getContentStateMap() { + return $this->getWireContentStateMap(false, null); + } + + private function getWireContentStateMap( + $is_edit, + PhabricatorUser $viewer = null) { + + $initial_state = $this->getInitialContentState(); + $committed_state = $this->getCommittedContentState(); + + if ($is_edit) { + $active_state = $this->getContentStateForEdit($viewer); + } else { + $active_state = $this->getContentState(); + } + + return array( + 'initial' => $this->getWireContentState($initial_state), + 'committed' => $this->getWireContentState($committed_state), + 'active' => $this->getWireContentState($active_state), + ); + } + + private function getWireContentState($content_state) { + if ($content_state === null) { + return null; + } + + return $content_state->newStorageMap(); + } + public function getDefaultSuggestionText() { $context = $this->getInlineContext(); diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index 11be8d0ec1..448a7f2f50 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -93,7 +93,7 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { 'startOffset' => $inline->getStartOffset(), 'endOffset' => $inline->getEndOffset(), 'on_right' => $this->getIsOnRight(), - 'contentState' => $inline->getContentState()->newStorageMap(), + 'state' => $inline->getContentStateMap(), ); } diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js index dde96ea0a3..6c54217403 100644 --- a/webroot/rsrc/js/application/diff/DiffInline.js +++ b/webroot/rsrc/js/application/diff/DiffInline.js @@ -8,8 +8,7 @@ JX.install('DiffInline', { construct : function() { - this._activeContentState = new JX.DiffInlineContentState(); - this._committedContentState = new JX.DiffInlineContentState(); + this._state = {}; }, members: { @@ -56,8 +55,7 @@ JX.install('DiffInline', { _isSelected: false, _canSuggestEdit: false, - _committedContentState: null, - _activeContentState: null, + _state: null, bindToRow: function(row) { this._row = row; @@ -602,15 +600,23 @@ JX.install('DiffInline', { _readInlineState: function(state) { this._id = state.id; - // TODO: This is not the correct content state after a reload: it is - // the draft state. - this._getCommittedContentState().readWireFormat(state.contentState); - - this._getActiveContentState().readWireFormat(state.contentState); + this._state = { + initial: this._newContentStateFromWireFormat(state.state.initial), + committed: this._newContentStateFromWireFormat(state.state.committed), + active: this._newContentStateFromWireFormat(state.state.active) + }; this._canSuggestEdit = state.canSuggestEdit; }, + _newContentStateFromWireFormat: function(map) { + if (map === null) { + return null; + } + + return new JX.DiffInlineContentState().readWireFormat(map); + }, + _ondeleteresponse: function() { // If there's an existing "unedit" undo element, remove it. if (this._undoRow) { @@ -787,7 +793,7 @@ JX.install('DiffInline', { }, _getActiveContentState: function() { - var state = this._activeContentState; + var state = this._state.active; if (this._editRow) { state.readForm(this._editRow); @@ -797,7 +803,11 @@ JX.install('DiffInline', { }, _getCommittedContentState: function() { - return this._committedContentState; + return this._state.committed; + }, + + _getInitialContentState: function() { + return this._state.initial; }, setHasSuggestion: function(has_suggestion) {