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

Conpherence - add support for linking directly to messages regardless of age of message

Summary: Fixes T7757. Since anchor links can't be processed server side, we have to detect the message is old in javascript, then re-loaded the page. This opens up a new corner case where we have to paginate in newer messages, so this also adds support for that.

Test Plan:
- set main query limit to 8 and then visited ZXX#YYY. noted a second quick load of YYY, that YYY ended up highlighted and scrolled to.
- used "show newer messages" and "show older messages" successfully, taking care to make sure transaction ids were all correct with no off by one errors, etc.
- opened and closed durable column to make sure that still works too

Reviewers: chad, epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T7757

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

+237 -67
+30 -30
resources/celerity/map.php
··· 46 46 'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2', 47 47 'rsrc/css/application/conpherence/durable-column.css' => '2e68a92f', 48 48 'rsrc/css/application/conpherence/menu.css' => 'f389e048', 49 - 'rsrc/css/application/conpherence/message-pane.css' => 'e7c09fda', 49 + 'rsrc/css/application/conpherence/message-pane.css' => '3150e2a2', 50 50 'rsrc/css/application/conpherence/notification.css' => 'd208f806', 51 51 'rsrc/css/application/conpherence/transaction.css' => '25138b7f', 52 52 'rsrc/css/application/conpherence/update.css' => '1099a660', ··· 355 355 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 356 356 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 357 357 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 358 - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '0a5192c4', 358 + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '6709c934', 359 359 'rsrc/js/application/conpherence/behavior-durable-column.js' => '657c2b50', 360 - 'rsrc/js/application/conpherence/behavior-menu.js' => '077a1dab', 360 + 'rsrc/js/application/conpherence/behavior-menu.js' => '804b0773', 361 361 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 362 362 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 363 363 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '93568464', ··· 519 519 'config-welcome-css' => '6abd79be', 520 520 'conpherence-durable-column-view' => '2e68a92f', 521 521 'conpherence-menu-css' => 'f389e048', 522 - 'conpherence-message-pane-css' => 'e7c09fda', 522 + 'conpherence-message-pane-css' => '3150e2a2', 523 523 'conpherence-notification-css' => 'd208f806', 524 - 'conpherence-thread-manager' => '0a5192c4', 524 + 'conpherence-thread-manager' => '6709c934', 525 525 'conpherence-transaction-css' => '25138b7f', 526 526 'conpherence-update-css' => '1099a660', 527 527 'conpherence-widget-pane-css' => '2af42ebe', ··· 561 561 'javelin-behavior-audit-preview' => 'd835b03a', 562 562 'javelin-behavior-choose-control' => '6153c708', 563 563 'javelin-behavior-config-reorder-fields' => '14a827de', 564 - 'javelin-behavior-conpherence-menu' => '077a1dab', 564 + 'javelin-behavior-conpherence-menu' => '804b0773', 565 565 'javelin-behavior-conpherence-pontificate' => '21ba5861', 566 566 'javelin-behavior-conpherence-widget-pane' => '93568464', 567 567 'javelin-behavior-countdown-timer' => 'e4cc26b3', ··· 873 873 'javelin-stratcom', 874 874 'javelin-workflow', 875 875 ), 876 - '077a1dab' => array( 877 - 'javelin-behavior', 878 - 'javelin-dom', 879 - 'javelin-util', 880 - 'javelin-stratcom', 881 - 'javelin-workflow', 882 - 'javelin-behavior-device', 883 - 'javelin-history', 884 - 'javelin-vector', 885 - 'javelin-scrollbar', 886 - 'phabricator-title', 887 - 'phabricator-shaped-request', 888 - 'conpherence-thread-manager', 889 - ), 890 876 '07de8873' => array( 891 877 'javelin-install', 892 878 'javelin-util', ··· 902 888 'javelin-dom', 903 889 'javelin-router', 904 890 ), 905 - '0a5192c4' => array( 906 - 'javelin-dom', 907 - 'javelin-util', 908 - 'javelin-stratcom', 909 - 'javelin-install', 910 - 'javelin-workflow', 911 - 'javelin-router', 912 - 'javelin-behavior-device', 913 - 'javelin-vector', 914 - ), 915 891 '0c6946e7' => array( 916 892 'javelin-install', 917 893 'javelin-dom', ··· 1311 1287 'phabricator-keyboard-shortcut', 1312 1288 'conpherence-thread-manager', 1313 1289 ), 1290 + '6709c934' => array( 1291 + 'javelin-dom', 1292 + 'javelin-util', 1293 + 'javelin-stratcom', 1294 + 'javelin-install', 1295 + 'javelin-workflow', 1296 + 'javelin-router', 1297 + 'javelin-behavior-device', 1298 + 'javelin-vector', 1299 + ), 1314 1300 '6882e80a' => array( 1315 1301 'javelin-dom', 1316 1302 ), ··· 1439 1425 '7ee2b591' => array( 1440 1426 'javelin-behavior', 1441 1427 'javelin-history', 1428 + ), 1429 + '804b0773' => array( 1430 + 'javelin-behavior', 1431 + 'javelin-dom', 1432 + 'javelin-util', 1433 + 'javelin-stratcom', 1434 + 'javelin-workflow', 1435 + 'javelin-behavior-device', 1436 + 'javelin-history', 1437 + 'javelin-vector', 1438 + 'javelin-scrollbar', 1439 + 'phabricator-title', 1440 + 'phabricator-shaped-request', 1441 + 'conpherence-thread-manager', 1442 1442 ), 1443 1443 82439934 => array( 1444 1444 'javelin-behavior',
+55 -10
src/applications/conpherence/ConpherenceTransactionRenderer.php
··· 5 5 public static function renderTransactions( 6 6 PhabricatorUser $user, 7 7 ConpherenceThread $conpherence, 8 - $full_display = true) { 8 + $full_display = true, 9 + $marker_type = 'older') { 9 10 10 11 $transactions = $conpherence->getTransactions(); 12 + 11 13 $oldest_transaction_id = 0; 14 + $newest_transaction_id = 0; 12 15 $too_many = ConpherenceThreadQuery::TRANSACTION_LIMIT + 1; 13 16 if (count($transactions) == $too_many) { 14 - $last_transaction = end($transactions); 15 - unset($transactions[$last_transaction->getID()]); 16 - $oldest_transaction = end($transactions); 17 - $oldest_transaction_id = $oldest_transaction->getID(); 17 + if ($marker_type == 'olderandnewer') { 18 + $last_transaction = end($transactions); 19 + $first_transaction = reset($transactions); 20 + unset($transactions[$last_transaction->getID()]); 21 + unset($transactions[$first_transaction->getID()]); 22 + $oldest_transaction_id = $last_transaction->getID(); 23 + $newest_transaction_id = $first_transaction->getID(); 24 + } else if ($marker_type == 'newer') { 25 + $first_transaction = reset($transactions); 26 + unset($transactions[$first_transaction->getID()]); 27 + $newest_transaction_id = $first_transaction->getID(); 28 + } else if ($marker_type == 'older') { 29 + $last_transaction = end($transactions); 30 + unset($transactions[$last_transaction->getID()]); 31 + $oldest_transaction = end($transactions); 32 + $oldest_transaction_id = $oldest_transaction->getID(); 33 + } 34 + // we need **at least** the newer marker in this mode even if 35 + // we didn't get a full set of transactions 36 + } else if ($marker_type == 'olderandnewer') { 37 + $first_transaction = reset($transactions); 38 + unset($transactions[$first_transaction->getID()]); 39 + $newest_transaction_id = $first_transaction->getID(); 18 40 } 41 + 19 42 $transactions = array_reverse($transactions); 20 43 $handles = $conpherence->getHandles(); 21 44 $rendered_transactions = array(); ··· 98 121 'latest_transaction' => $transaction, 99 122 'latest_transaction_id' => $latest_transaction_id, 100 123 'oldest_transaction_id' => $oldest_transaction_id, 124 + 'newest_transaction_id' => $newest_transaction_id, 101 125 ); 102 126 } 103 127 104 128 public static function renderMessagePaneContent( 105 129 array $transactions, 106 - $oldest_transaction_id) { 130 + $oldest_transaction_id, 131 + $newest_transaction_id) { 107 132 108 - $scrollbutton = ''; 133 + $oldscrollbutton = ''; 109 134 if ($oldest_transaction_id) { 110 - $scrollbutton = javelin_tag( 135 + $oldscrollbutton = javelin_tag( 111 136 'a', 112 137 array( 113 138 'href' => '#', 114 139 'mustcapture' => true, 115 140 'sigil' => 'show-older-messages', 116 - 'class' => 'conpherence-show-older-messages', 141 + 'class' => 'conpherence-show-more-messages', 117 142 'meta' => array( 118 143 'oldest_transaction_id' => $oldest_transaction_id, 119 144 ), ··· 121 146 pht('Show Older Messages')); 122 147 } 123 148 124 - return hsprintf('%s%s', $scrollbutton, $transactions); 149 + $newscrollbutton = ''; 150 + if ($newest_transaction_id) { 151 + $newscrollbutton = javelin_tag( 152 + 'a', 153 + array( 154 + 'href' => '#', 155 + 'mustcapture' => true, 156 + 'sigil' => 'show-newer-messages', 157 + 'class' => 'conpherence-show-more-messages', 158 + 'meta' => array( 159 + 'newest_transaction_id' => $newest_transaction_id, 160 + ), 161 + ), 162 + pht('Show Newer Messages')); 163 + } 164 + 165 + return hsprintf( 166 + '%s%s%s', 167 + $oldscrollbutton, 168 + $transactions, 169 + $newscrollbutton); 125 170 } 126 171 127 172 }
+2
src/applications/conpherence/application/PhabricatorConpherenceApplication.php
··· 41 41 '' => 'ConpherenceListController', 42 42 'thread/(?P<id>[1-9]\d*)/' => 'ConpherenceListController', 43 43 '(?P<id>[1-9]\d*)/' => 'ConpherenceViewController', 44 + '(?P<id>[1-9]\d*)/(?P<messageID>[1-9]\d*)/' 45 + => 'ConpherenceViewController', 44 46 'columnview/' => 'ConpherenceColumnViewController', 45 47 'new/' => 'ConpherenceNewController', 46 48 'room/new/' => 'ConpherenceNewRoomController',
+68 -5
src/applications/conpherence/controller/ConpherenceViewController.php
··· 3 3 final class ConpherenceViewController extends 4 4 ConpherenceController { 5 5 6 + const OLDER_FETCH_LIMIT = 5; 7 + 6 8 public function handleRequest(AphrontRequest $request) { 7 9 $user = $request->getUser(); 8 10 ··· 15 17 ->withIDs(array($conpherence_id)) 16 18 ->needParticipantCache(true) 17 19 ->needTransactions(true) 18 - ->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT); 20 + ->setTransactionLimit($this->getMainQueryLimit()); 21 + 19 22 $before_transaction_id = $request->getInt('oldest_transaction_id'); 23 + $after_transaction_id = $request->getInt('newest_transaction_id'); 24 + $old_message_id = $request->getURIData('messageID'); 25 + if ($before_transaction_id && ($old_message_id || $after_transaction_id)) { 26 + throw new Aphront400Response(); 27 + } 28 + if ($old_message_id && $after_transaction_id) { 29 + throw new Aphront400Response(); 30 + } 31 + 32 + $marker_type = 'older'; 20 33 if ($before_transaction_id) { 21 34 $query 22 35 ->setBeforeTransactionID($before_transaction_id); 23 36 } 37 + if ($old_message_id) { 38 + $marker_type = 'olderandnewer'; 39 + $query 40 + ->setAfterTransactionID($old_message_id - 1); 41 + } 42 + if ($after_transaction_id) { 43 + $marker_type = 'newer'; 44 + $query 45 + ->setAfterTransactionID($after_transaction_id); 46 + } 47 + 24 48 $conpherence = $query->executeOne(); 25 49 if (!$conpherence) { 26 50 return new Aphront404Response(); 27 51 } 28 52 $this->setConpherence($conpherence); 29 53 30 - $transactions = $conpherence->getTransactions(); 54 + $transactions = $this->getNeededTransactions( 55 + $conpherence, 56 + $old_message_id); 31 57 $latest_transaction = head($transactions); 32 58 $participant = $conpherence->getParticipantIfExists($user->getPHID()); 33 59 if ($participant) { ··· 38 64 39 65 $data = ConpherenceTransactionRenderer::renderTransactions( 40 66 $user, 41 - $conpherence); 67 + $conpherence, 68 + $full_display = true, 69 + $marker_type); 42 70 $messages = ConpherenceTransactionRenderer::renderMessagePaneContent( 43 71 $data['transactions'], 44 - $data['oldest_transaction_id']); 45 - if ($before_transaction_id) { 72 + $data['oldest_transaction_id'], 73 + $data['newest_transaction_id']); 74 + if ($before_transaction_id || $after_transaction_id) { 46 75 $header = null; 47 76 $form = null; 48 77 $content = array('messages' => $messages); ··· 138 167 return $form; 139 168 } 140 169 170 + private function getNeededTransactions( 171 + ConpherenceThread $conpherence, 172 + $message_id) { 141 173 174 + if ($message_id) { 175 + $newer_transactions = $conpherence->getTransactions(); 176 + $query = id(new ConpherenceTransactionQuery()) 177 + ->setViewer($this->getRequest()->getUser()) 178 + ->withObjectPHIDs(array($conpherence->getPHID())) 179 + ->setAfterID($message_id) 180 + ->needHandles(true) 181 + ->setLimit(self::OLDER_FETCH_LIMIT); 182 + $older_transactions = $query->execute(); 183 + $handles = array(); 184 + foreach ($older_transactions as $transaction) { 185 + $handles += $transaction->getHandles(); 186 + } 187 + $conpherence->attachHandles($conpherence->getHandles() + $handles); 188 + $transactions = array_merge($newer_transactions, $older_transactions); 189 + $conpherence->attachTransactions($transactions); 190 + } else { 191 + $transactions = $conpherence->getTransactions(); 192 + } 193 + 194 + return $transactions; 195 + } 196 + 197 + private function getMainQueryLimit() { 198 + $request = $this->getRequest(); 199 + $base_limit = ConpherenceThreadQuery::TRANSACTION_LIMIT; 200 + if ($request->getURIData('messageID')) { 201 + $base_limit = $base_limit - self::OLDER_FETCH_LIMIT; 202 + } 203 + return $base_limit; 204 + } 142 205 }
+2 -1
src/applications/conpherence/view/ConpherenceDurableColumnView.php
··· 463 463 $full_display = false); 464 464 $messages = ConpherenceTransactionRenderer::renderMessagePaneContent( 465 465 $data['transactions'], 466 - $data['oldest_transaction_id']); 466 + $data['oldest_transaction_id'], 467 + $data['newest_transaction_id']); 467 468 468 469 return $messages; 469 470 }
+23 -9
src/view/page/PhabricatorStandardPageView.php
··· 97 97 return false; 98 98 } 99 99 100 + if ($this->isQuicksandBlacklistURI()) { 101 + return false; 102 + } 103 + 104 + return true; 105 + } 106 + 107 + private function isQuicksandBlacklistURI() { 108 + $request = $this->getRequest(); 109 + if (!$request) { 110 + return false; 111 + } 112 + 100 113 $patterns = $this->getQuicksandURIPatternBlacklist(); 101 114 $path = $request->getRequestURI()->getPath(); 102 115 foreach ($patterns as $pattern) { 103 116 if (preg_match('(^'.$pattern.'$)', $path)) { 104 - return false; 117 + return true; 105 118 } 106 119 } 107 - 108 - return true; 120 + return false; 109 121 } 110 122 111 123 public function getDurableColumnVisible() { ··· 365 377 } 366 378 } 367 379 368 - Javelin::initBehavior( 369 - 'scrollbar', 370 - array( 371 - 'nodeID' => 'phabricator-standard-page', 372 - 'isMainContent' => true, 373 - )); 380 + if (!$this->isQuicksandBlacklistURI()) { 381 + Javelin::initBehavior( 382 + 'scrollbar', 383 + array( 384 + 'nodeID' => 'phabricator-standard-page', 385 + 'isMainContent' => true, 386 + )); 387 + } 374 388 375 389 $main_page = phutil_tag( 376 390 'div',
+2 -2
webroot/rsrc/css/application/conpherence/message-pane.css
··· 37 37 background: #EBECEE; 38 38 } 39 39 40 - .conpherence-show-older-messages { 40 + .conpherence-show-more-messages { 41 41 display: block; 42 42 background: #e0e3ec; 43 43 margin: 10px; ··· 46 46 color: {$bluetext}; 47 47 } 48 48 49 - .conpherence-show-older-messages-loading { 49 + .conpherence-show-more-messages-loading { 50 50 font-style: italic; 51 51 } 52 52
+7 -2
webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
··· 254 254 this.syncWorkflow(workflow, params.stage); 255 255 }, 256 256 257 - loadThreadByID: function(thread_id) { 257 + loadThreadByID: function(thread_id, force_reload) { 258 258 if (this.isThreadLoaded() && 259 - this.isThreadIDLoaded(thread_id)) { 259 + this.isThreadIDLoaded(thread_id) && 260 + !force_reload) { 260 261 return; 261 262 } 262 263 ··· 277 278 JX.Stratcom.invoke('notification-panel-update', null, {}); 278 279 279 280 this._didLoadThreadCallback(r); 281 + 282 + if (force_reload) { 283 + JX.Stratcom.invoke('hashchange'); 284 + } 280 285 }); 281 286 282 287 // should this be sync'd too?
+48 -8
webroot/rsrc/js/application/conpherence/behavior-menu.js
··· 119 119 var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); 120 120 var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages'); 121 121 scrollbar = new JX.Scrollbar(messages); 122 + scrollbar.setAsScrollFrame(); 122 123 } 123 124 init(); 124 125 ··· 317 318 buildDeviceWidgetSelector : build_device_widget_selector 318 319 }); 319 320 } 321 + 320 322 var _firstScroll = true; 321 323 function _scrollMessageWindow() { 322 324 if (_firstScroll) { 323 325 _firstScroll = false; 324 - // let the standard #anchor tech take over 326 + 327 + // We want to let the standard #anchor tech take over after we make sure 328 + // we don't have to present the user with a "load older message?" dialog 325 329 if (window.location.hash) { 330 + var hash = window.location.hash.replace(/^#/, ''); 331 + try { 332 + JX.$('anchor-' + hash); 333 + } catch (ex) { 334 + var uri = '/conpherence/' + 335 + _thread.selected + '/' + hash + '/'; 336 + threadManager.setLoadThreadURI(uri); 337 + threadManager.loadThreadByID(_thread.selected, true); 338 + _firstScroll = true; 339 + return; 340 + } 326 341 return; 327 342 } 328 343 } ··· 374 389 var form = JX.DOM.find(root, 'form', 'conpherence-pontificate'); 375 390 var data = e.getNodeData('conpherence-edit-metadata'); 376 391 var header = JX.DOM.find(root, 'div', 'conpherence-header-pane'); 377 - var messages = JX.DOM.find(root, 'div', 'conpherence-messages'); 392 + var messages = scrollbar.getContentNode(); 378 393 379 394 new JX.Workflow.newFromForm(form, data) 380 395 .setHandler(JX.bind(this, function(r) { ··· 400 415 .start(); 401 416 }); 402 417 403 - var _loadingTransactionID = null; 418 + var _oldLoadingTransactionID = null; 404 419 JX.Stratcom.listen('click', 'show-older-messages', function(e) { 405 420 e.kill(); 406 421 var data = e.getNodeData('show-older-messages'); 407 - if (data.oldest_transaction_id == _loadingTransactionID) { 422 + if (data.oldest_transaction_id == _oldLoadingTransactionID) { 408 423 return; 409 424 } 410 - _loadingTransactionID = data.oldest_transaction_id; 425 + _oldLoadingTransactionID = data.oldest_transaction_id; 426 + 411 427 var node = e.getNode('show-older-messages'); 412 428 JX.DOM.setContent(node, 'Loading...'); 413 - JX.DOM.alterClass(node, 'conpherence-show-older-messages-loading', true); 429 + JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true); 414 430 415 431 var conf_id = _thread.selected; 416 - var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 417 - var messages_root = JX.DOM.find(root, 'div', 'conpherence-messages'); 432 + var messages_root = scrollbar.getContentNode(); 418 433 new JX.Workflow(config.baseURI + conf_id + '/', data) 419 434 .setHandler(function(r) { 420 435 JX.DOM.remove(node); 421 436 var messages = JX.$H(r.messages); 422 437 JX.DOM.prependContent( 438 + messages_root, 439 + JX.$H(messages)); 440 + }).start(); 441 + }); 442 + 443 + var _newLoadingTransactionID = null; 444 + JX.Stratcom.listen('click', 'show-newer-messages', function(e) { 445 + e.kill(); 446 + var data = e.getNodeData('show-newer-messages'); 447 + if (data.newest_transaction_id == _newLoadingTransactionID) { 448 + return; 449 + } 450 + _newLoadingTransactionID = data.newest_transaction_id; 451 + 452 + var node = e.getNode('show-newer-messages'); 453 + JX.DOM.setContent(node, 'Loading...'); 454 + JX.DOM.alterClass(node, 'conpherence-show-more-messages-loading', true); 455 + 456 + var conf_id = _thread.selected; 457 + var messages_root = scrollbar.getContentNode(); 458 + new JX.Workflow(config.baseURI + conf_id + '/', data) 459 + .setHandler(function(r) { 460 + JX.DOM.remove(node); 461 + var messages = JX.$H(r.messages); 462 + JX.DOM.appendContent( 423 463 messages_root, 424 464 JX.$H(messages)); 425 465 }).start();