@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 - introduce ConpherenceThreadManager

Summary:
Ref T7014. Fixes T7473. This adds a class to handle thread state about what thread is loaded and what transaction we've seen last. It is deployed 100% in the durable column and only partially deployed in the regular view. Future diff(s) should clean up regular view. Note ConpherenceThreadManager API might change a bit at that time.

Also includes a bonus bug fix so logged out users can't toggle this column

Test Plan: tried to use durable column while logged out and nothing happened. sent messages, aphlict-received messages, added people, and changed title from both views

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T7473, T7014

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

+466 -417
+73 -57
resources/celerity/map.php
··· 11 11 'core.pkg.js' => '5a1c336d', 12 12 'darkconsole.pkg.js' => '8ab24e01', 13 13 'differential.pkg.css' => '1940be3f', 14 - 'differential.pkg.js' => 'e62fe1cf', 14 + 'differential.pkg.js' => '53c1ccc2', 15 15 'diffusion.pkg.css' => '591664fa', 16 16 'diffusion.pkg.js' => 'bfc0737b', 17 17 'maniphest.pkg.css' => '68d4dd3d', ··· 351 351 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 352 352 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 353 353 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 354 - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'e4affa94', 355 - 'rsrc/js/application/conpherence/behavior-menu.js' => '869e3445', 356 - 'rsrc/js/application/conpherence/behavior-pontificate.js' => '86df5915', 357 - 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', 354 + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'd0742f48', 355 + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '8cf41980', 356 + 'rsrc/js/application/conpherence/behavior-menu.js' => '6bc52765', 357 + 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 358 + 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '2c1cd7f5', 358 359 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 359 360 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 360 361 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', 361 362 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 362 363 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 363 364 'rsrc/js/application/differential/ChangesetViewManager.js' => '88be0133', 364 - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '1b772f31', 365 + 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '0286a1db', 365 366 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 366 367 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 367 368 'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5', ··· 516 517 'conpherence-menu-css' => 'c6ac5299', 517 518 'conpherence-message-pane-css' => '5930260a', 518 519 'conpherence-notification-css' => '04a6e10a', 520 + 'conpherence-thread-manager' => 'd0742f48', 519 521 'conpherence-update-css' => '1099a660', 520 522 'conpherence-widget-pane-css' => '3d575438', 521 523 'differential-changeset-view-css' => '6a8b172a', 522 524 'differential-core-view-css' => '7ac3cabc', 523 - 'differential-inline-comment-editor' => '1b772f31', 525 + 'differential-inline-comment-editor' => '0286a1db', 524 526 'differential-results-table-css' => '181aa9d9', 525 527 'differential-revision-add-comment-css' => 'c478bcaa', 526 528 'differential-revision-comment-css' => '48186045', ··· 555 557 'javelin-behavior-boards-dropdown' => '0ec56e1d', 556 558 'javelin-behavior-choose-control' => '6153c708', 557 559 'javelin-behavior-config-reorder-fields' => '14a827de', 558 - 'javelin-behavior-conpherence-menu' => '869e3445', 559 - 'javelin-behavior-conpherence-pontificate' => '86df5915', 560 - 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 560 + 'javelin-behavior-conpherence-menu' => '6bc52765', 561 + 'javelin-behavior-conpherence-pontificate' => '21ba5861', 562 + 'javelin-behavior-conpherence-widget-pane' => '2c1cd7f5', 561 563 'javelin-behavior-countdown-timer' => 'e4cc26b3', 562 564 'javelin-behavior-dark-console' => '08883e8b', 563 565 'javelin-behavior-dashboard-async-panel' => '469c0d9e', ··· 582 584 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 583 585 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 584 586 'javelin-behavior-doorkeeper-tag' => 'e5822781', 585 - 'javelin-behavior-durable-column' => 'e4affa94', 587 + 'javelin-behavior-durable-column' => '8cf41980', 586 588 'javelin-behavior-error-log' => '6882e80a', 587 589 'javelin-behavior-fancy-datepicker' => 'c51ae228', 588 590 'javelin-behavior-global-drag-and-drop' => '07f199d8', ··· 830 832 'unhandled-exception-css' => '37d4f9a2', 831 833 ), 832 834 'requires' => array( 835 + '0286a1db' => array( 836 + 'javelin-dom', 837 + 'javelin-util', 838 + 'javelin-stratcom', 839 + 'javelin-install', 840 + 'javelin-request', 841 + 'javelin-workflow', 842 + ), 833 843 '029a133d' => array( 834 844 'aphront-dialog-view-css', 835 845 ), ··· 931 941 'javelin-util', 932 942 'phabricator-keyboard-shortcut-manager', 933 943 ), 934 - '1b772f31' => array( 935 - 'javelin-dom', 936 - 'javelin-util', 937 - 'javelin-stratcom', 938 - 'javelin-install', 939 - 'javelin-request', 940 - 'javelin-workflow', 941 - ), 942 944 '1d298e3a' => array( 943 945 'javelin-install', 944 946 'javelin-util', ··· 967 969 'phuix-action-view', 968 970 'phabricator-phtize', 969 971 'changeset-view-manager', 972 + ), 973 + '21ba5861' => array( 974 + 'javelin-behavior', 975 + 'javelin-dom', 976 + 'javelin-util', 977 + 'javelin-workflow', 978 + 'javelin-stratcom', 979 + 'conpherence-thread-manager', 970 980 ), 971 981 '2290aeef' => array( 972 982 'javelin-install', ··· 1012 1022 'javelin-stratcom', 1013 1023 'javelin-dom', 1014 1024 ), 1025 + '2c1cd7f5' => array( 1026 + 'javelin-behavior', 1027 + 'javelin-dom', 1028 + 'javelin-stratcom', 1029 + 'javelin-workflow', 1030 + 'javelin-util', 1031 + 'phabricator-notification', 1032 + 'javelin-behavior-device', 1033 + 'phuix-dropdown-menu', 1034 + 'phuix-action-list-view', 1035 + 'phuix-action-view', 1036 + 'conpherence-thread-manager', 1037 + ), 1015 1038 '2c426492' => array( 1016 1039 'javelin-behavior', 1017 1040 'javelin-dom', ··· 1067 1090 'javelin-dom', 1068 1091 'javelin-reactor-dom', 1069 1092 ), 1070 - '40b1ff90' => array( 1071 - 'javelin-behavior', 1072 - 'javelin-dom', 1073 - 'javelin-stratcom', 1074 - 'javelin-workflow', 1075 - 'javelin-util', 1076 - 'phabricator-notification', 1077 - 'javelin-behavior-device', 1078 - 'phuix-dropdown-menu', 1079 - 'phuix-action-list-view', 1080 - 'phuix-action-view', 1081 - ), 1082 1093 42126667 => array( 1083 1094 'javelin-behavior', 1084 1095 'javelin-dom', ··· 1237 1248 ), 1238 1249 '69adf288' => array( 1239 1250 'javelin-install', 1251 + ), 1252 + '6bc52765' => array( 1253 + 'javelin-behavior', 1254 + 'javelin-dom', 1255 + 'javelin-util', 1256 + 'javelin-stratcom', 1257 + 'javelin-workflow', 1258 + 'javelin-behavior-device', 1259 + 'javelin-history', 1260 + 'javelin-vector', 1261 + 'phabricator-shaped-request', 1262 + 'conpherence-thread-manager', 1240 1263 ), 1241 1264 '6c2b09a2' => array( 1242 1265 'javelin-install', ··· 1447 1470 'phabricator-tooltip', 1448 1471 'changeset-view-manager', 1449 1472 ), 1450 - '869e3445' => array( 1451 - 'javelin-behavior', 1452 - 'javelin-dom', 1453 - 'javelin-util', 1454 - 'javelin-stratcom', 1455 - 'javelin-workflow', 1456 - 'javelin-behavior-device', 1457 - 'javelin-history', 1458 - 'javelin-vector', 1459 - 'phabricator-shaped-request', 1460 - ), 1461 - '86df5915' => array( 1462 - 'javelin-behavior', 1463 - 'javelin-dom', 1464 - 'javelin-util', 1465 - 'javelin-workflow', 1466 - 'javelin-stratcom', 1467 - ), 1468 1473 '87cb6b51' => array( 1469 1474 'javelin-behavior', 1470 1475 'javelin-dom', ··· 1529 1534 'phabricator-notification', 1530 1535 'javelin-stratcom', 1531 1536 'javelin-behavior', 1537 + ), 1538 + '8cf41980' => array( 1539 + 'javelin-behavior', 1540 + 'javelin-dom', 1541 + 'javelin-stratcom', 1542 + 'javelin-scrollbar', 1543 + 'javelin-quicksand', 1544 + 'phabricator-keyboard-shortcut', 1545 + 'conpherence-thread-manager', 1532 1546 ), 1533 1547 '8e1389b5' => array( 1534 1548 'javelin-behavior', ··· 1754 1768 'javelin-stratcom', 1755 1769 'phabricator-phtize', 1756 1770 ), 1771 + 'd0742f48' => array( 1772 + 'javelin-dom', 1773 + 'javelin-util', 1774 + 'javelin-stratcom', 1775 + 'javelin-install', 1776 + 'javelin-workflow', 1777 + 'javelin-router', 1778 + 'javelin-behavior-device', 1779 + 'javelin-vector', 1780 + ), 1757 1781 'd19198c8' => array( 1758 1782 'javelin-install', 1759 1783 'javelin-dom', ··· 1835 1859 'javelin-vector', 1836 1860 'javelin-dom', 1837 1861 'javelin-uri', 1838 - ), 1839 - 'e4affa94' => array( 1840 - 'javelin-behavior', 1841 - 'javelin-dom', 1842 - 'javelin-stratcom', 1843 - 'javelin-scrollbar', 1844 - 'javelin-quicksand', 1845 - 'phabricator-keyboard-shortcut', 1846 1862 ), 1847 1863 'e4cc26b3' => array( 1848 1864 'javelin-behavior',
+2 -16
src/applications/conpherence/controller/ConpherenceViewController.php
··· 46 46 $content = array('messages' => $messages); 47 47 } else { 48 48 $header = $this->buildHeaderPaneContent($conpherence); 49 - $form = $this->renderFormContent($data['latest_transaction_id']); 49 + $form = $this->renderFormContent(); 50 50 $content = array( 51 51 'header' => $header, 52 52 'messages' => $messages, ··· 77 77 )); 78 78 } 79 79 80 - private function renderFormContent($latest_transaction_id) { 80 + private function renderFormContent() { 81 81 82 82 $conpherence = $this->getConpherence(); 83 83 $user = $this->getRequest()->getUser(); ··· 103 103 ->appendChild( 104 104 id(new AphrontFormSubmitControl()) 105 105 ->setValue(pht('Send'))) 106 - ->appendChild( 107 - javelin_tag( 108 - 'input', 109 - array( 110 - 'type' => 'hidden', 111 - 'name' => 'latest_transaction_id', 112 - 'value' => $latest_transaction_id, 113 - 'sigil' => 'latest-transaction-id', 114 - 'meta' => array( 115 - 'threadPHID' => $conpherence->getPHID(), 116 - 'threadID' => $conpherence->getID(), 117 - ), 118 - ), 119 - '')) 120 106 ->render(); 121 107 122 108 return $form;
+10 -2
src/view/page/PhabricatorStandardPageView.php
··· 82 82 public function getShowDurableColumn() { 83 83 $request = $this->getRequest(); 84 84 if ($request) { 85 + if (strncmp( 86 + $request->getRequestURI()->getPath(), 87 + '/conpherence', 88 + strlen('/conpherence')) === 0) { 89 + return false; 90 + } 85 91 $viewer = $request->getUser(); 86 - return PhabricatorApplication::isClassInstalledForViewer( 87 - 'PhabricatorConpherenceApplication', 92 + if ($viewer->isLoggedIn()) { 93 + return PhabricatorApplication::isClassInstalledForViewer( 94 + 'PhabricatorConpherenceApplication', 88 95 $viewer); 96 + } 89 97 } 90 98 return false; 91 99 }
+271
webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js
··· 1 + /** 2 + * @provides conpherence-thread-manager 3 + * @requires javelin-dom 4 + * javelin-util 5 + * javelin-stratcom 6 + * javelin-install 7 + * javelin-workflow 8 + * javelin-router 9 + * javelin-behavior-device 10 + * javelin-vector 11 + */ 12 + JX.install('ConpherenceThreadManager', { 13 + 14 + construct : function() { 15 + if (__DEV__) { 16 + if (JX.ConpherenceThreadManager._instance) { 17 + JX.$E('ConpherenceThreadManager object is a singleton.'); 18 + } 19 + } 20 + JX.ConpherenceThreadManager._instance = this; 21 + return this; 22 + }, 23 + 24 + members: { 25 + _loadThreadURI: null, 26 + _loadedThreadID: null, 27 + _loadedThreadPHID: null, 28 + _latestTransactionID: null, 29 + _updating: null, 30 + _minimalDisplay: false, 31 + _getMessagesNodeFunction: JX.bag, 32 + _getTitleNodeFunction: JX.bag, 33 + _willLoadThreadCallback: JX.bag, 34 + _didLoadThreadCallback: JX.bag, 35 + _willSendMessageCallback: JX.bag, 36 + _didSendMessageCallback: JX.bag, 37 + 38 + setLoadThreadURI: function(uri) { 39 + this._loadThreadURI = uri; 40 + return this; 41 + }, 42 + 43 + getLoadThreadURI: function() { 44 + return this._loadThreadURI; 45 + }, 46 + 47 + isThreadLoaded: function() { 48 + return Boolean(this._loadedThreadID); 49 + }, 50 + 51 + isThreadIDLoaded: function(thread_id) { 52 + return this._loadedThreadID == thread_id; 53 + }, 54 + 55 + getLoadedThreadID: function() { 56 + return this._loadedThreadID; 57 + }, 58 + 59 + getLoadedThreadPHID: function() { 60 + return this._loadedThreadPHID; 61 + }, 62 + 63 + getLatestTransactionID: function() { 64 + return this._latestTransactionID; 65 + }, 66 + 67 + setLatestTransactionID: function(id) { 68 + this._latestTransactionID = id; 69 + return this; 70 + }, 71 + 72 + setMessagesNodeFunction: function(callback) { 73 + this._getMessagesNodeFunction = callback; 74 + return this; 75 + }, 76 + 77 + _getMessagesNode: function() { 78 + return this._getMessagesNodeFunction(); 79 + }, 80 + 81 + setTitleNodeFunction: function(callback) { 82 + this._getTitleNodeFunction = callback; 83 + return this; 84 + }, 85 + 86 + _getTitleNode: function() { 87 + return this._getTitleNodeFunction(); 88 + }, 89 + 90 + setMinimalDisplay: function(bool) { 91 + this._minimalDisplay = bool; 92 + return this; 93 + }, 94 + 95 + setWillLoadThreadCallback: function(callback) { 96 + this._willLoadThreadCallback = callback; 97 + return this; 98 + }, 99 + 100 + setDidLoadThreadCallback: function(callback) { 101 + this._didLoadThreadCallback = callback; 102 + return this; 103 + }, 104 + 105 + setWillSendMessageCallback: function(callback) { 106 + this._willSendMessageCallback = callback; 107 + return this; 108 + }, 109 + 110 + setDidSendMessageCallback: function(callback) { 111 + this._didSendMessageCallback = callback; 112 + return this; 113 + }, 114 + 115 + _getParams: function(base_params) { 116 + if (this._minimalDisplay) { 117 + base_params.minimal_display = true; 118 + } 119 + if (this._latestTransactionID) { 120 + base_params.latest_transaction_id = this._latestTransactionID; 121 + } 122 + return base_params; 123 + }, 124 + start: function() { 125 + JX.Stratcom.listen( 126 + 'aphlict-server-message', 127 + null, 128 + JX.bind(this, function(e) { 129 + var message = e.getData(); 130 + 131 + if (message.type != 'message') { 132 + // Not a message event. 133 + return; 134 + } 135 + 136 + if (message.threadPHID != this._loadedThreadPHID) { 137 + // Message event for some thread other than the visible one. 138 + return; 139 + } 140 + 141 + if (message.messageID <= this._latestTransactionID) { 142 + // Message event for something we already know about. 143 + return; 144 + } 145 + 146 + // If we're currently updating, wait for the update to complete. 147 + // If this notification tells us about a message which is newer than 148 + // the newest one we know to exist, keep track of it so we can 149 + // update once the in-flight update finishes. 150 + if (this._updating && 151 + this._updating.threadPHID == this._loadedThreadPHID) { 152 + if (message.messageID > this._updating.knownID) { 153 + this._updating.knownID = message.messageID; 154 + return; 155 + } 156 + } 157 + 158 + this._updateThread(); 159 + })); 160 + }, 161 + 162 + _updateThread: function() { 163 + var params = this._getParams({ 164 + action: 'load', 165 + }); 166 + 167 + var uri = '/conpherence/update/' + this._loadedThreadID + '/'; 168 + 169 + var workflow = new JX.Workflow(uri) 170 + .setData(params) 171 + .setHandler(JX.bind(this, function(r) { 172 + this._latestTransactionID = r.latest_transaction_id; 173 + 174 + var messages = this._getMessagesNode(); 175 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 176 + messages.scrollTop = messages.scrollHeight; 177 + })); 178 + 179 + this.syncWorkflow(workflow, 'finally'); 180 + }, 181 + 182 + syncWorkflow: function(workflow, stage) { 183 + this._updating = { 184 + threadPHID: this._loadedThreadPHID, 185 + knownID: this._latestTransactionID 186 + }; 187 + workflow.listen(stage, JX.bind(this, function() { 188 + // TODO - do we need to handle if we switch threads somehow? 189 + var need_sync = (this._updating.knownID > this._latestTransactionID); 190 + this._updating = null; 191 + if (need_sync) { 192 + this._updateThread(); 193 + } 194 + })); 195 + workflow.start(); 196 + }, 197 + 198 + runUpdateWorkflowFromLink: function(link, params) { 199 + params = this._getParams(params); 200 + 201 + var workflow = new JX.Workflow.newFromLink(link) 202 + .setData(params) 203 + .setHandler(JX.bind(this, function(r) { 204 + this._latestTransactionID = r.latest_transaction_id; 205 + 206 + var messages = this._getMessagesNode(); 207 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 208 + messages.scrollTop = messages.scrollHeight; 209 + 210 + JX.DOM.setContent(this._getTitleNode(), r.conpherence_title); 211 + })); 212 + this.syncWorkflow(workflow, params.stage); 213 + }, 214 + 215 + loadThreadByID: function(thread_id) { 216 + if (this.isThreadLoaded() && 217 + this.isThreadIDLoaded(thread_id)) { 218 + return; 219 + } 220 + 221 + this._willLoadThreadCallback(); 222 + 223 + var params = {}; 224 + // We pick a thread from the server if not specified 225 + if (thread_id) { 226 + params.id = thread_id; 227 + } 228 + params = this._getParams(params); 229 + 230 + var handler = JX.bind(this, function(r) { 231 + this._loadedThreadID = r.threadID; 232 + this._loadedThreadPHID = r.threadPHID; 233 + this._loadThreadID = r.threadID; 234 + this._latestTransactionID = r.latestTransactionID; 235 + 236 + this._didLoadThreadCallback(r); 237 + }); 238 + 239 + // should this be sync'd too? 240 + new JX.Workflow(this.getLoadThreadURI()) 241 + .setData(params) 242 + .setHandler(handler) 243 + .start(); 244 + }, 245 + 246 + sendMessage: function(form, params) { 247 + params = this._getParams(params); 248 + 249 + this._willSendMessageCallback(); 250 + var workflow = JX.Workflow.newFromForm(form, params) 251 + .setHandler(JX.bind(this, function(r) { 252 + this._latestTransactionID = r.latest_transaction_id; 253 + this._didSendMessageCallback(r); 254 + })); 255 + this.syncWorkflow(workflow, 'finally'); 256 + } 257 + }, 258 + 259 + statics: { 260 + _instance: null, 261 + 262 + getInstance: function() { 263 + var self = JX.ConpherenceThreadManager; 264 + if (!self._instance) { 265 + return null; 266 + } 267 + return self._instance; 268 + } 269 + } 270 + 271 + });
+57 -192
webroot/rsrc/js/application/conpherence/behavior-durable-column.js
··· 6 6 * javelin-scrollbar 7 7 * javelin-quicksand 8 8 * phabricator-keyboard-shortcut 9 + * conpherence-thread-manager 9 10 */ 10 11 11 12 JX.behavior('durable-column', function() { 12 13 13 - var shouldInit = true; 14 + var show = false; 14 15 var loadThreadID = null; 15 - var loadedThreadID = null; 16 - var loadedThreadPHID = null; 17 - var latestTransactionID = null; 18 16 19 17 var frame = JX.$('phabricator-standard-page'); 20 18 var quick = JX.$('phabricator-standard-page-body'); 21 - var show = false; 22 - 23 19 24 - // TODO - this "upating" stuff is a copy from behavior-pontificate 25 - // TODO: This isn't very clean. When you submit a message, you may get a 26 - // notification about it back before you get the rendered message back. To 27 - // prevent this, we keep track of whether we're currently updating the 28 - // thread. If we are, we hold further updates until the response comes 29 - // back. 30 - 31 - // After the response returns, we'll do another update if we know about 32 - // a transaction newer than the one we got back from the server. 33 - var updating = null; 34 - // Copy continues with slight modifications for how we store data now 35 - JX.Stratcom.listen('aphlict-server-message', null, function(e) { 36 - var message = e.getData(); 37 - 38 - if (message.type != 'message') { 39 - // Not a message event. 40 - return; 41 - } 42 - 43 - if (message.threadPHID != loadedThreadPHID) { 44 - // Message event for some thread other than the visible one. 45 - return; 46 - } 47 - 48 - if (message.messageID <= latestTransactionID) { 49 - // Message event for something we already know about. 50 - return; 51 - } 52 - 53 - // If we're currently updating, wait for the update to complete. 54 - // If this notification tells us about a message which is newer than the 55 - // newest one we know to exist, keep track of it so we can update once 56 - // the in-flight update finishes. 57 - if (updating && updating.threadPHID == loadedThreadPHID) { 58 - if (message.messageID > updating.knownID) { 59 - updating.knownID = message.messageID; 60 - return; 61 - } 62 - } 63 - 64 - update_thread(); 65 - }); 66 - function update_thread() { 67 - var params = { 68 - action: 'load', 69 - latest_transaction_id: latestTransactionID, 70 - minimal_display: true 71 - }; 72 - 73 - var uri = '/conpherence/update/' + loadedThreadID + '/'; 74 - 75 - var workflow = new JX.Workflow(uri) 76 - .setData(params) 77 - .setHandler(function(r) { 78 - var messages = _getColumnMessagesNode(); 79 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 80 - messages.scrollTop = messages.scrollHeight; 81 - 82 - latestTransactionID = r.latest_transaction_id; 83 - }); 84 - 85 - sync_workflow(workflow); 20 + function _getColumnContentNode() { 21 + return JX.$('conpherence-durable-column-content'); 86 22 } 87 - function sync_workflow(workflow) { 88 - updating = { 89 - threadPHID: loadedThreadPHID, 90 - knownID: latestTransactionID 91 - }; 92 - workflow.listen('finally', function() { 93 - var need_sync = (updating && updating.knownID > latestTransactionID); 94 - updating = null; 95 - if (need_sync) { 96 - update_thread(); 97 - } 98 - }); 99 - workflow.start(); 100 - } 101 - // end copy / hack of stuff with big ole TODO on it 102 - 103 23 104 24 function _toggleColumn() { 105 25 if (window.location.pathname.indexOf('/conpherence/') === 0) { ··· 110 30 var column = JX.$('conpherence-durable-column'); 111 31 if (show) { 112 32 JX.DOM.show(column); 113 - loadThreadContent(loadThreadID); 33 + threadManager.loadThreadByID(loadThreadID); 114 34 } else { 115 35 JX.DOM.hide(column); 116 36 } ··· 122 42 .setHandler(_toggleColumn) 123 43 .register(); 124 44 125 - new JX.Scrollbar(JX.$('conpherence-durable-column-content')); 45 + new JX.Scrollbar(_getColumnContentNode()); 126 46 127 47 JX.Quicksand.start(); 128 48 49 + /* Conpherence Thread Manager configuration - lots of display 50 + * callbacks. 51 + */ 52 + var threadManager = new JX.ConpherenceThreadManager(); 53 + threadManager.setMinimalDisplay(true); 54 + threadManager.setMessagesNodeFunction(_getColumnMessagesNode); 55 + threadManager.setTitleNodeFunction(_getColumnTitleNode); 56 + threadManager.setLoadThreadURI('/conpherence/columnview/'); 57 + threadManager.setWillLoadThreadCallback(function () { 58 + _markLoading(true); 59 + }); 60 + threadManager.setDidLoadThreadCallback(function (r) { 61 + var column = _getColumnNode(); 62 + var new_column = JX.$H(r.content); 63 + JX.DOM.replace(column, new_column); 64 + JX.DOM.show(_getColumnNode()); 65 + new JX.Scrollbar(_getColumnContentNode()); 66 + _markLoading(false); 67 + loadThreadID = threadManager.getLoadedThreadID(); 68 + }); 69 + threadManager.setWillSendMessageCallback(function () { 70 + _markLoading(true); 71 + }); 72 + threadManager.setDidSendMessageCallback(function (r) { 73 + var messages = _getColumnMessagesNode(); 74 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 75 + var content = _getColumnContentNode(); 76 + content.scrollTop = content.scrollHeight; 77 + 78 + var textarea = _getColumnTextareaNode(); 79 + textarea.value = ''; 80 + 81 + _markLoading(false); 82 + 83 + _focusColumnTextareaNode(); 84 + }); 85 + threadManager.start(); 86 + 129 87 JX.Stratcom.listen( 130 88 'click', 131 89 'conpherence-durable-column-header-action', ··· 139 97 switch (action) { 140 98 case 'metadata': 141 99 JX.Stratcom.invoke('notification-panel-close'); 142 - params = { 143 - action: action, 144 - latest_transaction_id: latestTransactionID, 145 - minimal_display: true, 146 - force_ajax: true 147 - }; 148 - var workflow = new JX.Workflow.newFromLink(link) 149 - .setData(params) 150 - .setHandler(function(r) { 151 - var messages = _getColumnMessagesNode(); 152 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 153 - messages.scrollTop = messages.scrollHeight; 154 - 155 - var title = _getColumnTitleNode(); 156 - JX.DOM.setContent(title, r.conpherence_title); 157 - 158 - latestTransactionID = r.latest_transaction_id; 159 - // since this is a two step workflow, and the "finally" method 160 - // gets called on the first form load, restore "updating" if 161 - // necessary 162 - if (updating === null) { 163 - updating = { 164 - threadPHID: loadedThreadPHID, 165 - knownID: latestTransactionID 166 - }; 167 - } 100 + threadManager.runUpdateWorkflowFromLink( 101 + link, 102 + { 103 + action: action, 104 + force_ajax: true, 105 + stage: 'submit' 168 106 }); 169 - sync_workflow(workflow); 170 107 break; 171 108 case 'add_person': 172 109 JX.Stratcom.invoke('notification-panel-close'); 173 - params = { 174 - action: action, 175 - latest_transaction_id: latestTransactionID, 176 - minimal_display: true 177 - }; 178 - var workflow = new JX.Workflow.newFromLink(link) 179 - .setData(params) 180 - .setHandler(function(r) { 181 - var messages = _getColumnMessagesNode(); 182 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 183 - messages.scrollTop = messages.scrollHeight; 184 - 185 - latestTransactionID = r.latest_transaction_id; 186 - // since this is a two step workflow, and the "finally" method 187 - // gets called on the first form load, restore "updating" if 188 - // necessary 189 - if (updating === null) { 190 - updating = { 191 - threadPHID: loadedThreadPHID, 192 - knownID: latestTransactionID 193 - }; 194 - } 110 + threadManager.runUpdateWorkflowFromLink( 111 + link, 112 + { 113 + action: action, 114 + stage: 'submit' 195 115 }); 196 - sync_workflow(workflow); 197 116 break; 198 117 case 'go_conpherence': 199 118 JX.$U(link.href).go(); ··· 216 135 'div', 217 136 'conpherence-durable-column-body'); 218 137 } 138 + 219 139 220 140 function _getColumnMessagesNode() { 221 141 var column = JX.$('conpherence-durable-column'); ··· 259 179 JX.DOM.alterClass(column, 'loading', loading); 260 180 } 261 181 262 - function loadThreadContent(thread_id) { 263 - // loaded this thread already 264 - if (loadedThreadID !== null && loadedThreadID == thread_id) { 265 - return; 266 - } 267 - _markLoading(true); 268 - 269 - var uri = '/conpherence/columnview/'; 270 - var params = null; 271 - // We can pick a thread from the server the first time 272 - if (shouldInit) { 273 - shouldInit = false; 274 - } else { 275 - params = { id : thread_id }; 276 - } 277 - var handler = function(r) { 278 - var column = _getColumnNode(); 279 - var new_column = JX.$H(r.content); 280 - loadedThreadID = r.threadID; 281 - loadedThreadPHID = r.threadPHID; 282 - loadThreadID = r.threadID; 283 - latestTransactionID = r.latestTransactionID; 284 - JX.DOM.replace(column, new_column); 285 - JX.DOM.show(_getColumnNode()); 286 - new JX.Scrollbar(JX.$('conpherence-durable-column-content')); 287 - _markLoading(false); 288 - }; 289 - 290 - new JX.Workflow(uri) 291 - .setData(params) 292 - .setHandler(handler) 293 - .start(); 294 - } 295 - 296 182 function _sendMessage(e) { 297 183 e.kill(); 298 - _markLoading(true); 299 - 300 184 var form = _getColumnFormNode(); 301 - var params = { 302 - latest_transaction_id : latestTransactionID, 303 - minimal_display : true 304 - }; 305 - var workflow = JX.Workflow.newFromForm(form, params) 306 - .setHandler(function(r) { 307 - var messages = _getColumnMessagesNode(); 308 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 309 - messages.scrollTop = messages.scrollHeight; 310 - 311 - var textarea = _getColumnTextareaNode(); 312 - textarea.value = ''; 313 - 314 - latestTransactionID = r.latest_transaction_id; 315 - 316 - _markLoading(false); 317 - 318 - _focusColumnTextareaNode(); 319 - }); 320 - sync_workflow(workflow); 185 + threadManager.sendMessage(form, { minimal_display: true }); 321 186 } 322 187 323 188 JX.Stratcom.listen(
+40 -4
webroot/rsrc/js/application/conpherence/behavior-menu.js
··· 9 9 * javelin-history 10 10 * javelin-vector 11 11 * phabricator-shaped-request 12 + * conpherence-thread-manager 12 13 */ 13 14 14 15 JX.behavior('conpherence-menu', function(config) { 15 - 16 16 /** 17 17 * State for displayed thread. 18 18 */ ··· 22 22 node: null 23 23 }; 24 24 25 + // TODO - move more logic into the ThreadManager 26 + var threadManager = new JX.ConpherenceThreadManager(); 27 + threadManager.setMessagesNodeFunction(function () { 28 + return JX.DOM.find(document, 'div', 'conpherence-messages'); 29 + }); 30 + threadManager.setWillSendMessageCallback(function () { 31 + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 32 + var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); 33 + markThreadLoading(true); 34 + JX.DOM.alterClass(form_root, 'loading', true); 35 + }); 36 + threadManager.setDidSendMessageCallback(function (r) { 37 + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 38 + var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); 39 + var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); 40 + var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages'); 41 + var fileWidget = null; 42 + try { 43 + fileWidget = JX.DOM.find(root, 'div', 'widgets-files'); 44 + } catch (ex) { 45 + // Ignore; maybe no files widget 46 + } 47 + JX.DOM.appendContent(messages, JX.$H(r.transactions)); 48 + messages.scrollTop = messages.scrollHeight; 49 + 50 + if (fileWidget) { 51 + JX.DOM.setContent( 52 + fileWidget, 53 + JX.$H(r.file_widget) 54 + ); 55 + } 56 + var textarea = JX.DOM.find(form_root, 'textarea'); 57 + textarea.value = ''; 58 + markThreadLoading(false); 59 + 60 + setTimeout(function() { JX.DOM.focus(textarea); }, 100); 61 + }); 62 + threadManager.start(); 63 + 25 64 /** 26 65 * Current role of this behavior. The two possible roles are to show a 'list' 27 66 * of threads or a specific 'thread'. On devices, this behavior stays in the ··· 65 104 function selectThread(node, update_page_data) { 66 105 if (_thread.node) { 67 106 JX.DOM.alterClass(_thread.node, 'conpherence-selected', false); 68 - // keep the unread-count hidden still. big TODO once we ajax in updates 69 - // to threads to make this work right and move threads between read / 70 - // unread 71 107 } 72 108 73 109 JX.DOM.alterClass(node, 'conpherence-selected', true);
+3 -136
webroot/rsrc/js/application/conpherence/behavior-pontificate.js
··· 5 5 * javelin-util 6 6 * javelin-workflow 7 7 * javelin-stratcom 8 + * conpherence-thread-manager 8 9 */ 9 10 10 11 JX.behavior('conpherence-pontificate', function() { 11 12 12 - // TODO: This isn't very clean. When you submit a message, you may get a 13 - // notification about it back before you get the rendered message back. To 14 - // prevent this, we keep track of whether we're currently updating the 15 - // thread. If we are, we hold further updates until the response comes 16 - // back. 17 - 18 - // After the response returns, we'll do another update if we know about 19 - // a transaction newer than the one we got back from the server. 20 - var updating = null; 21 - 22 - function get_thread_data() { 23 - // TODO: This is really, really gross. 24 - var infonode = JX.DOM.find(document, 'input', 'latest-transaction-id'); 25 - var data = JX.Stratcom.getData(infonode); 26 - data.latestID = infonode.value; 27 - return data; 28 - } 29 - 30 - function update_latest_transaction_id(id) { 31 - // TODO: Continued grossness from above. 32 - var infonode = JX.DOM.find(document, 'input', 'latest-transaction-id'); 33 - infonode.value = id; 34 - } 35 - 36 - JX.Stratcom.listen('aphlict-server-message', null, function(e) { 37 - var message = e.getData(); 38 - 39 - if (message.type != 'message') { 40 - // Not a message event. 41 - return; 42 - } 43 - 44 - var data = get_thread_data(); 45 - 46 - if (message.threadPHID != data.threadPHID) { 47 - // Message event for some thread other than the visible one. 48 - return; 49 - } 50 - 51 - if (message.messageID <= data.latestID) { 52 - // Message event for something we already know about. 53 - return; 54 - } 55 - 56 - // If we're currently updating, wait for the update to complete. 57 - // If this notification tells us about a message which is newer than the 58 - // newest one we know to exist, keep track of it so we can update once 59 - // the in-flight update finishes. 60 - if (updating && updating.threadPHID == data.threadPHID) { 61 - if (message.messageID > updating.knownID) { 62 - updating.knownID = message.messageID; 63 - return; 64 - } 65 - } 66 - 67 - update_thread(data); 68 - }); 69 - 70 - function update_thread(data) { 71 - var params = { 72 - action: 'load', 73 - latest_transaction_id: data.latestID 74 - }; 75 - 76 - var uri = '/conpherence/update/' + data.threadID + '/'; 77 - 78 - var workflow = new JX.Workflow(uri) 79 - .setData(params) 80 - .setHandler(function(r) { 81 - var messages = JX.DOM.find(document, 'div', 'conpherence-messages'); 82 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 83 - messages.scrollTop = messages.scrollHeight; 84 - 85 - update_latest_transaction_id(r.latest_transaction_id); 86 - }); 87 - 88 - sync_workflow(workflow, data); 89 - } 90 - 91 - function sync_workflow(workflow, data) { 92 - updating = { 93 - threadPHID: data.threadPHID, 94 - knownID: data.latestID 95 - }; 96 - 97 - workflow.listen('finally', function() { 98 - var new_data = get_thread_data(); 99 - var need_sync = (updating.knownID > new_data.latestID); 100 - 101 - updating = null; 102 - 103 - if (need_sync) { 104 - update_thread(new_data); 105 - } 106 - }); 107 - 108 - workflow.start(); 109 - } 110 - 111 13 var onsubmit = function(e) { 112 14 e.kill(); 113 - 114 15 var form = e.getNode('tag:form'); 115 - 116 - var root = e.getNode('conpherence-layout'); 117 - var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); 118 - var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); 119 - var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages'); 120 - var fileWidget = null; 121 - try { 122 - fileWidget = JX.DOM.find(root, 'div', 'widgets-files'); 123 - } catch (ex) { 124 - // Ignore; maybe no files widget 125 - } 126 - JX.DOM.alterClass(form_root, 'loading', true); 127 - 128 - var workflow = JX.Workflow.newFromForm(form) 129 - .setHandler(JX.bind(this, function(r) { 130 - JX.DOM.appendContent(messages, JX.$H(r.transactions)); 131 - messages.scrollTop = messages.scrollHeight; 132 - 133 - if (fileWidget) { 134 - JX.DOM.setContent( 135 - fileWidget, 136 - JX.$H(r.file_widget) 137 - ); 138 - } 139 - 140 - update_latest_transaction_id(r.latest_transaction_id); 141 - 142 - var textarea = JX.DOM.find(form, 'textarea'); 143 - textarea.value = ''; 144 - 145 - JX.DOM.alterClass(form_root, 'loading', false); 146 - 147 - setTimeout(function() { JX.DOM.focus(textarea); }, 100); 148 - })); 149 - 150 - sync_workflow(workflow, get_thread_data()); 16 + var threadManager = JX.ConpherenceThreadManager.getInstance(); 17 + threadManager.sendMessage(form, {}); 151 18 }; 152 19 153 20 JX.Stratcom.listen(
+10 -10
webroot/rsrc/js/application/conpherence/behavior-widget-pane.js
··· 9 9 * phuix-dropdown-menu 10 10 * phuix-action-list-view 11 11 * phuix-action-view 12 + * conpherence-thread-manager 12 13 * @provides javelin-behavior-conpherence-widget-pane 13 14 */ 14 15 ··· 270 271 href = create_data.customHref; 271 272 } 272 273 273 - var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 274 - var latest_transaction_dom = JX.DOM.find( 275 - root, 276 - 'input', 277 - 'latest-transaction-id'); 274 + var threadManager = JX.ConpherenceThreadManager.getInstance(); 275 + var latest_transaction_id = threadManager.getLatestTransactionID(); 278 276 var data = { 279 - latest_transaction_id : latest_transaction_dom.value, 277 + latest_transaction_id : latest_transaction_id, 280 278 action : create_data.action 281 279 }; 282 280 283 - new JX.Workflow(href, data) 281 + var workflow = new JX.Workflow(href, data) 284 282 .setHandler(function (r) { 285 - latest_transaction_dom.value = r.latest_transaction_id; 283 + var threadManager = JX.ConpherenceThreadManager.getInstance(); 284 + threadManager.setLatestTransactionID(r.latest_transaction_id); 285 + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 286 286 if (create_data.refreshFromResponse) { 287 287 var messages = null; 288 288 try { ··· 315 315 widget : widget_to_update 316 316 }); 317 317 } 318 - }) 319 - .start(); 318 + }); 319 + threadManager.syncWorkflow(workflow, 'submit'); 320 320 } 321 321 ); 322 322