@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.

Add comment previews to Maniphest

Summary:
Moves shared code from Differential and Maniphest comment previews into
PhabricatorShapedRequest, and then implements Maniphest previews.

This doesn't implement comment drafts, I'll follow up with that but it requires
this and is completely separable.

This also always shows the preview as "commented" rather than previewing the
actual transaction. I'll follow up with that but I think it will require a
little factoring and this is useful even without transaction details.

I need to tweak the styling a bit too.

Test Plan:
Typed text in Maniphest and Differential. Toggled Differential action. Made
comments.

Reviewed By: rm
Reviewers: rm, tuomaspelkonen, jungejason, aran
CC: aran, rm
Differential Revision: 258

+323 -68
+57 -31
src/__celerity_resource_map__.php
··· 61 61 ), 62 62 'disk' => '/rsrc/css/aphront/headsup-action-list-view.css', 63 63 ), 64 + 0 => 65 + array( 66 + 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', 67 + 'type' => 'js', 68 + 'requires' => 69 + array( 70 + 0 => 'javelin-install', 71 + ), 72 + 'disk' => '/rsrc/js/javelin/docs/Base.js', 73 + ), 64 74 'aphront-list-filter-view-css' => 65 75 array( 66 76 'uri' => '/res/e6cff171/rsrc/css/aphront/list-filter-view.css', ··· 284 294 ), 285 295 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', 286 296 ), 287 - 0 => 288 - array( 289 - 'uri' => '/res/39de799e/rsrc/js/javelin/docs/Base.js', 290 - 'type' => 'js', 291 - 'requires' => 292 - array( 293 - 0 => 'javelin-install', 294 - ), 295 - 'disk' => '/rsrc/js/javelin/docs/Base.js', 296 - ), 297 297 'javelin-behavior-dark-console' => 298 298 array( 299 299 'uri' => '/res/044c171f/rsrc/js/application/core/behavior-dark-console.js', ··· 350 350 ), 351 351 'javelin-behavior-differential-feedback-preview' => 352 352 array( 353 - 'uri' => '/res/79e7f18d/rsrc/js/application/differential/behavior-comment-preview.js', 353 + 'uri' => '/res/ab8a7d60/rsrc/js/application/differential/behavior-comment-preview.js', 354 354 'type' => 'js', 355 355 'requires' => 356 356 array( ··· 359 359 2 => 'javelin-dom', 360 360 3 => 'javelin-request', 361 361 4 => 'javelin-util', 362 + 5 => 'phabricator-shaped-request', 362 363 ), 363 364 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', 364 365 ), ··· 461 462 4 => 'javelin-typeahead-preloaded-source', 462 463 ), 463 464 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js', 465 + ), 466 + 'javelin-behavior-maniphest-transaction-preview' => 467 + array( 468 + 'uri' => '/res/b211faf2/rsrc/js/application/maniphest/behavior-transaction-preview.js', 469 + 'type' => 'js', 470 + 'requires' => 471 + array( 472 + 0 => 'javelin-behavior', 473 + 1 => 'javelin-dom', 474 + 2 => 'javelin-util', 475 + 3 => 'phabricator-shaped-request', 476 + ), 477 + 'disk' => '/rsrc/js/application/maniphest/behavior-transaction-preview.js', 464 478 ), 465 479 'javelin-behavior-owners-path-editor' => 466 480 array( ··· 720 734 ), 721 735 'mainphest-task-detail-css' => 722 736 array( 723 - 'uri' => '/res/dbefc148/rsrc/css/application/maniphest/task-detail.css', 737 + 'uri' => '/res/fa248d0b/rsrc/css/application/maniphest/task-detail.css', 724 738 'type' => 'css', 725 739 'requires' => 726 740 array( ··· 851 865 ), 852 866 'disk' => '/rsrc/css/core/remarkup.css', 853 867 ), 868 + 'phabricator-shaped-request' => 869 + array( 870 + 'uri' => '/res/1f0ef02b/rsrc/js/application/core/ShapedRequest.js', 871 + 'type' => 'js', 872 + 'requires' => 873 + array( 874 + 0 => 'javelin-install', 875 + 1 => 'javelin-util', 876 + 2 => 'javelin-request', 877 + ), 878 + 'disk' => '/rsrc/js/application/core/ShapedRequest.js', 879 + ), 854 880 'phabricator-standard-page-view' => 855 881 array( 856 882 'uri' => '/res/9c468a70/rsrc/css/application/base/standard-page-view.css', ··· 936 962 'uri' => '/res/pkg/33f413ef/typeahead.pkg.js', 937 963 'type' => 'js', 938 964 ), 939 - '711616db' => 940 - array ( 941 - 'name' => 'differential.pkg.js', 942 - 'symbols' => 943 - array ( 944 - 0 => 'javelin-behavior-differential-feedback-preview', 945 - 1 => 'javelin-behavior-differential-edit-inline-comments', 946 - 2 => 'javelin-behavior-differential-populate', 947 - 3 => 'javelin-behavior-differential-show-more', 948 - 4 => 'javelin-behavior-differential-diff-radios', 949 - ), 950 - 'uri' => '/res/pkg/711616db/differential.pkg.js', 951 - 'type' => 'js', 952 - ), 953 965 '7d23deb1' => 954 966 array ( 955 967 'name' => 'javelin.pkg.js', ··· 993 1005 'uri' => '/res/pkg/ac70e6b7/core.pkg.css', 994 1006 'type' => 'css', 995 1007 ), 1008 + 'b17926e1' => 1009 + array ( 1010 + 'name' => 'differential.pkg.js', 1011 + 'symbols' => 1012 + array ( 1013 + 0 => 'javelin-behavior-differential-feedback-preview', 1014 + 1 => 'javelin-behavior-differential-edit-inline-comments', 1015 + 2 => 'javelin-behavior-differential-populate', 1016 + 3 => 'javelin-behavior-differential-show-more', 1017 + 4 => 'javelin-behavior-differential-diff-radios', 1018 + ), 1019 + 'uri' => '/res/pkg/b17926e1/differential.pkg.js', 1020 + 'type' => 'js', 1021 + ), 996 1022 ), 997 1023 'reverse' => 998 1024 array ( ··· 1016 1042 'diffusion-commit-view-css' => '03ef179e', 1017 1043 'javelin-behavior' => '7d23deb1', 1018 1044 'javelin-behavior-aphront-basic-tokenizer' => '33f413ef', 1019 - 'javelin-behavior-differential-diff-radios' => '711616db', 1020 - 'javelin-behavior-differential-edit-inline-comments' => '711616db', 1021 - 'javelin-behavior-differential-feedback-preview' => '711616db', 1022 - 'javelin-behavior-differential-populate' => '711616db', 1023 - 'javelin-behavior-differential-show-more' => '711616db', 1045 + 'javelin-behavior-differential-diff-radios' => 'b17926e1', 1046 + 'javelin-behavior-differential-edit-inline-comments' => 'b17926e1', 1047 + 'javelin-behavior-differential-feedback-preview' => 'b17926e1', 1048 + 'javelin-behavior-differential-populate' => 'b17926e1', 1049 + 'javelin-behavior-differential-show-more' => 'b17926e1', 1024 1050 'javelin-behavior-workflow' => '122a6b6d', 1025 1051 'javelin-dom' => '7d23deb1', 1026 1052 'javelin-event' => '7d23deb1',
+2
src/__phutil_library_map__.php
··· 261 261 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 262 262 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 263 263 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 264 + 'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/transactionpreview', 264 265 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 265 266 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 266 267 'Phabricator404Controller' => 'applications/base/controller/404', ··· 684 685 'ManiphestTransaction' => 'ManiphestDAO', 685 686 'ManiphestTransactionDetailView' => 'AphrontView', 686 687 'ManiphestTransactionListView' => 'AphrontView', 688 + 'ManiphestTransactionPreviewController' => 'ManiphestController', 687 689 'ManiphestTransactionSaveController' => 'ManiphestController', 688 690 'Phabricator404Controller' => 'PhabricatorController', 689 691 'PhabricatorAuthController' => 'PhabricatorController',
+1
src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
··· 159 159 ), 160 160 'transaction/' => array( 161 161 'save/' => 'ManiphestTransactionSaveController', 162 + 'preview/(?P<id>\d+)/$' => 'ManiphestTransactionPreviewController', 162 163 ), 163 164 'select/search/$' => 'ManiphestTaskSelectorSearchController', 164 165 ),
+19 -1
src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php
··· 268 268 id(new AphrontFormTextAreaControl()) 269 269 ->setLabel('Comments') 270 270 ->setName('comments') 271 - ->setValue('')) 271 + ->setID('transaction-comments')) 272 272 ->appendChild( 273 273 id(new AphrontFormSubmitControl()) 274 274 ->setValue('Avast!')); ··· 300 300 ), 301 301 ), 302 302 )); 303 + 304 + 305 + Javelin::initBehavior('maniphest-transaction-preview', array( 306 + 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 307 + 'preview' => 'transaction-preview', 308 + 'comments' => 'transaction-comments', 309 + )); 310 + 303 311 304 312 $comment_panel = new AphrontPanelView(); 305 313 $comment_panel->appendChild($comment_form); 306 314 $comment_panel->setHeader('Leap Into Action'); 307 315 316 + $preview_panel = 317 + '<div class="maniphest-transaction-preview"> 318 + <div id="transaction-preview"> 319 + <div class="maniphest-loading-text"> 320 + Loading preview... 321 + </div> 322 + </div> 323 + </div>'; 324 + 308 325 $transaction_view = new ManiphestTransactionListView(); 309 326 $transaction_view->setTransactions($transactions); 310 327 $transaction_view->setHandles($handles); ··· 316 333 $panel, 317 334 $transaction_view, 318 335 $comment_panel, 336 + $preview_panel, 319 337 ), 320 338 array( 321 339 'title' => 'T'.$task->getID().' '.$task->getTitle(),
+59
src/applications/maniphest/controller/transactionpreview/ManiphestTransactionPreviewController.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2011 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + class ManiphestTransactionPreviewController extends ManiphestController { 20 + 21 + private $id; 22 + 23 + public function willProcessRequest(array $data) { 24 + $this->id = $data['id']; 25 + } 26 + 27 + public function processRequest() { 28 + 29 + $request = $this->getRequest(); 30 + $user = $request->getUser(); 31 + 32 + $comments = $request->getStr('comments'); 33 + 34 + $handles = id(new PhabricatorObjectHandleData(array($user->getPHID()))) 35 + ->loadHandles(); 36 + 37 + $transaction = new ManiphestTransaction(); 38 + $transaction->setAuthorPHID($user->getPHID()); 39 + $transaction->setComments($comments); 40 + $transaction->setTransactionType(ManiphestTransactionType::TYPE_NONE); 41 + 42 + $transactions = array(); 43 + $transactions[] = $transaction; 44 + 45 + $factory = new DifferentialMarkupEngineFactory(); 46 + $engine = $factory->newDifferentialCommentMarkupEngine(); 47 + 48 + $transaction_view = new ManiphestTransactionListView(); 49 + $transaction_view->setTransactions($transactions); 50 + $transaction_view->setHandles($handles); 51 + $transaction_view->setUser($user); 52 + $transaction_view->setMarkupEngine($engine); 53 + $transaction_view->setPreview(true); 54 + 55 + return id(new AphrontAjaxResponse()) 56 + ->setContent($transaction_view->render()); 57 + } 58 + 59 + }
+20
src/applications/maniphest/controller/transactionpreview/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'aphront/response/ajax'); 10 + phutil_require_module('phabricator', 'applications/differential/parser/markup'); 11 + phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype'); 12 + phutil_require_module('phabricator', 'applications/maniphest/controller/base'); 13 + phutil_require_module('phabricator', 'applications/maniphest/storage/transaction'); 14 + phutil_require_module('phabricator', 'applications/maniphest/view/transactionlist'); 15 + phutil_require_module('phabricator', 'applications/phid/handle/data'); 16 + 17 + phutil_require_module('phutil', 'utils'); 18 + 19 + 20 + phutil_require_source('ManiphestTransactionPreviewController.php');
+14 -2
src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
··· 22 22 private $handles; 23 23 private $markupEngine; 24 24 private $forEmail; 25 + private $preview; 25 26 26 27 public function setTransactionGroup(array $transactions) { 27 28 $this->transactions = $transactions; ··· 35 36 36 37 public function setMarkupEngine(PhutilMarkupEngine $engine) { 37 38 $this->markupEngine = $engine; 39 + return $this; 40 + } 41 + 42 + public function setPreview($preview) { 43 + $this->preview = $preview; 38 44 return $this; 39 45 } 40 46 ··· 107 113 if (strlen($comments)) { 108 114 $comments = $this->markupEngine->markupText($comments); 109 115 $comment_transaction->setCache($comments); 110 - if ($comment_transaction->getID()) { 116 + if ($comment_transaction->getID() && !$this->preview) { 111 117 $comment_transaction->save(); 112 118 } 113 119 } ··· 120 126 $comment_block = null; 121 127 } 122 128 129 + if ($this->preview) { 130 + $timestamp = 'COMMENT PREVIEW'; 131 + } else { 132 + $timestamp = phabricator_format_timestamp($transaction->getDateCreated()); 133 + } 134 + 123 135 return phutil_render_tag( 124 136 'div', 125 137 array( ··· 129 141 '<div class="maniphest-transaction-detail-view '.$more_classes.'">'. 130 142 '<div class="maniphest-transaction-header">'. 131 143 '<div class="maniphest-transaction-timestamp">'. 132 - phabricator_format_timestamp($transaction->getDateCreated()). 144 + $timestamp. 133 145 '</div>'. 134 146 $descs. 135 147 '</div>'.
+7
src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php
··· 22 22 private $handles; 23 23 private $user; 24 24 private $markupEngine; 25 + private $preview; 25 26 26 27 public function setTransactions(array $transactions) { 27 28 $this->transactions = $transactions; ··· 40 41 41 42 public function setMarkupEngine(PhutilMarkupEngine $engine) { 42 43 $this->markupEngine = $engine; 44 + return $this; 45 + } 46 + 47 + public function setPreview($preview) { 48 + $this->preview = $preview; 43 49 return $this; 44 50 } 45 51 ··· 76 82 $view->setTransactionGroup($group); 77 83 $view->setHandles($this->handles); 78 84 $view->setMarkupEngine($this->markupEngine); 85 + $view->setPreview($this->preview); 79 86 $views[] = $view->render(); 80 87 } 81 88
+12
webroot/rsrc/css/application/maniphest/task-detail.css
··· 42 42 .maniphest-task-detail-core { 43 43 margin-right: 265px; 44 44 } 45 + 46 + .maniphest-transaction-preview { 47 + background: #f0f0f0; 48 + border-top: 1px solid #dddddd; 49 + border-bottom: 1px solid #aaaaaa; 50 + margin: -1em 2em 2em; 51 + padding: 15px 20px; 52 + } 53 + 54 + .maniphest-loading-text { 55 + color: #666666; 56 + }
+88
webroot/rsrc/js/application/core/ShapedRequest.js
··· 1 + /** 2 + * @requires javelin-install 3 + * javelin-util 4 + * javelin-request 5 + * @provides phabricator-shaped-request 6 + * @javelin 7 + */ 8 + 9 + /** 10 + * Send requests with rate limiting and retries, in response to some application 11 + * trigger. This is used to implement comment previews in Differential and 12 + * Maniphest. 13 + */ 14 + JX.install('PhabricatorShapedRequest', { 15 + 16 + construct : function(uri, callback, data_callback) { 17 + this._uri = uri; 18 + this._callback = callback; 19 + this._dataCallback = data_callback; 20 + }, 21 + 22 + members : { 23 + _callback : null, 24 + _dataCallback : null, 25 + _request : null, 26 + _min : null, 27 + _defer : null, 28 + _last : null, 29 + start : function() { 30 + this.trigger(); 31 + }, 32 + 33 + trigger : function() { 34 + 35 + if (this._request) { 36 + // Waiting on a request, rate-limit. 37 + return; 38 + } 39 + 40 + if (this._min && (new Date().getTime() < this._min)) { 41 + // Just got a request back, rate-limit. 42 + return; 43 + } 44 + 45 + this._defer && this._defer.stop(); 46 + 47 + var data = this._dataCallback(); 48 + 49 + if (this.shouldSendRequest(this._last, data)) { 50 + this._last = data; 51 + var request = new JX.Request(this._uri, JX.bind(this, function(r) { 52 + this._callback(r); 53 + 54 + this._min = new Date().getTime() + this._rateLimit; 55 + this._defer && this._defer.stop(); 56 + this._defer = JX.defer(JX.bind(this, this.trigger), this._rateLimit); 57 + })); 58 + request.listen('finally', JX.bind(this, function() { 59 + this._request = null; 60 + })); 61 + request.setData(data); 62 + request.setTimeout(this.getFrequency()); 63 + request.send(); 64 + } else { 65 + this._defer = JX.defer(JX.bind(this, this.trigger), this._frequency); 66 + } 67 + }, 68 + 69 + shouldSendRequest : function(last, data) { 70 + if (last === null) { 71 + return true; 72 + } 73 + 74 + for (var k in last) { 75 + if (data[k] !== last[k]) { 76 + return true; 77 + } 78 + } 79 + return false; 80 + } 81 + 82 + }, 83 + 84 + properties : { 85 + rateLimit : 250, 86 + frequency : 750 87 + } 88 + });
+15 -34
webroot/rsrc/js/application/differential/behavior-comment-preview.js
··· 5 5 * javelin-dom 6 6 * javelin-request 7 7 * javelin-util 8 + * phabricator-shaped-request 8 9 */ 9 10 10 11 JX.behavior('differential-feedback-preview', function(config) { 11 12 12 13 var action = JX.$(config.action); 13 14 var content = JX.$(config.content); 14 - var preview = JX.$(config.preview); 15 15 16 - var aval = null;//action.value; 17 - var cval = null;//content.value; 18 - var defer = null; 19 - var min = null; 20 - var request = null; 16 + var callback = function(r) { 17 + JX.DOM.setContent(JX.$(config.preview), JX.$H(r)); 18 + }; 21 19 22 - function check() { 23 - if (request || (min && (new Date().getTime() < min))) { 24 - // Waiting on an async or just got one back, rate-limit. 25 - return; 26 - } 20 + var getdata = function() { 21 + return { 22 + content : content.value, 23 + action : action.value 24 + }; 25 + }; 27 26 28 - defer && defer.stop(); 27 + var request = new JX.PhabricatorShapedRequest(config.uri, callback, getdata); 28 + var trigger = JX.bind(request, request.trigger); 29 29 30 - if (action.value !== aval || content.value !== cval) { 31 - aval = action.value; 32 - cval = content.value; 33 - 34 - request = new JX.Request(config.uri, function(r) { 35 - preview && JX.DOM.setContent(preview, JX.$H(r)); 36 - min = new Date().getTime() + 500; 37 - defer && defer.stop(); 38 - defer = JX.defer(check, 500); 39 - }); 40 - request.listen('finally', function() { request = null; }); 41 - request.setData({action : aval, content : cval}); 42 - // If we don't get a response back soon, retry on the next action. 43 - request.setTimeout(2000); 44 - request.send(); 45 - } else { 46 - defer = JX.defer(check, 2000); 47 - } 48 - } 30 + JX.DOM.listen(content, 'keydown', null, trigger); 31 + JX.DOM.listen(action, 'change', null, trigger); 49 32 50 - JX.DOM.listen(content, 'keydown', null, check); 51 - JX.DOM.listen(action, 'change', null, check); 33 + request.start(); 52 34 53 - check(); 54 35 55 36 56 37 function refreshInlinePreview() {
+29
webroot/rsrc/js/application/maniphest/behavior-transaction-preview.js
··· 1 + /** 2 + * @provides javelin-behavior-maniphest-transaction-preview 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + * javelin-util 6 + * phabricator-shaped-request 7 + */ 8 + 9 + JX.behavior('maniphest-transaction-preview', function(config) { 10 + 11 + var comments = JX.$(config.comments); 12 + 13 + var callback = function(r) { 14 + JX.DOM.setContent(JX.$(config.preview), JX.$H(r)); 15 + }; 16 + 17 + var getdata = function() { 18 + return { 19 + comments : comments.value 20 + }; 21 + } 22 + 23 + var request = new JX.PhabricatorShapedRequest(config.uri, callback, getdata); 24 + var trigger = JX.bind(request, request.trigger); 25 + 26 + JX.DOM.listen(comments, 'keydown', null, trigger); 27 + 28 + request.start(); 29 + });