From 2373185e9b6217eee9d19fc1a3d223bd3d209981 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Tue, 10 Mar 2015 12:20:29 -0700 Subject: [PATCH] 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 --- resources/celerity/map.php | 130 +++++---- .../controller/ConpherenceViewController.php | 18 +- src/view/page/PhabricatorStandardPageView.php | 12 +- .../conpherence/ConpherenceThreadManager.js | 271 ++++++++++++++++++ .../conpherence/behavior-durable-column.js | 249 ++++------------ .../application/conpherence/behavior-menu.js | 44 ++- .../conpherence/behavior-pontificate.js | 139 +-------- .../conpherence/behavior-widget-pane.js | 20 +- 8 files changed, 466 insertions(+), 417 deletions(-) create mode 100644 webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ed08feeb47..7fea049c43 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -11,7 +11,7 @@ return array( 'core.pkg.js' => '5a1c336d', 'darkconsole.pkg.js' => '8ab24e01', 'differential.pkg.css' => '1940be3f', - 'differential.pkg.js' => 'e62fe1cf', + 'differential.pkg.js' => '53c1ccc2', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => 'bfc0737b', 'maniphest.pkg.css' => '68d4dd3d', @@ -351,17 +351,18 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', - 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'e4affa94', - 'rsrc/js/application/conpherence/behavior-menu.js' => '869e3445', - 'rsrc/js/application/conpherence/behavior-pontificate.js' => '86df5915', - 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'd0742f48', + 'rsrc/js/application/conpherence/behavior-durable-column.js' => '8cf41980', + 'rsrc/js/application/conpherence/behavior-menu.js' => '6bc52765', + 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', + 'rsrc/js/application/conpherence/behavior-widget-pane.js' => '2c1cd7f5', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/differential/ChangesetViewManager.js' => '88be0133', - 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '1b772f31', + 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '0286a1db', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5', @@ -516,11 +517,12 @@ return array( 'conpherence-menu-css' => 'c6ac5299', 'conpherence-message-pane-css' => '5930260a', 'conpherence-notification-css' => '04a6e10a', + 'conpherence-thread-manager' => 'd0742f48', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '3d575438', 'differential-changeset-view-css' => '6a8b172a', 'differential-core-view-css' => '7ac3cabc', - 'differential-inline-comment-editor' => '1b772f31', + 'differential-inline-comment-editor' => '0286a1db', 'differential-results-table-css' => '181aa9d9', 'differential-revision-add-comment-css' => 'c478bcaa', 'differential-revision-comment-css' => '48186045', @@ -555,9 +557,9 @@ return array( 'javelin-behavior-boards-dropdown' => '0ec56e1d', 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => '14a827de', - 'javelin-behavior-conpherence-menu' => '869e3445', - 'javelin-behavior-conpherence-pontificate' => '86df5915', - 'javelin-behavior-conpherence-widget-pane' => '40b1ff90', + 'javelin-behavior-conpherence-menu' => '6bc52765', + 'javelin-behavior-conpherence-pontificate' => '21ba5861', + 'javelin-behavior-conpherence-widget-pane' => '2c1cd7f5', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => '08883e8b', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', @@ -582,7 +584,7 @@ return array( 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-doorkeeper-tag' => 'e5822781', - 'javelin-behavior-durable-column' => 'e4affa94', + 'javelin-behavior-durable-column' => '8cf41980', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-fancy-datepicker' => 'c51ae228', 'javelin-behavior-global-drag-and-drop' => '07f199d8', @@ -830,6 +832,14 @@ return array( 'unhandled-exception-css' => '37d4f9a2', ), 'requires' => array( + '0286a1db' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-request', + 'javelin-workflow', + ), '029a133d' => array( 'aphront-dialog-view-css', ), @@ -931,14 +941,6 @@ return array( 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), - '1b772f31' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-request', - 'javelin-workflow', - ), '1d298e3a' => array( 'javelin-install', 'javelin-util', @@ -968,6 +970,14 @@ return array( 'phabricator-phtize', 'changeset-view-manager', ), + '21ba5861' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-workflow', + 'javelin-stratcom', + 'conpherence-thread-manager', + ), '2290aeef' => array( 'javelin-install', 'javelin-dom', @@ -1012,6 +1022,19 @@ return array( 'javelin-stratcom', 'javelin-dom', ), + '2c1cd7f5' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-util', + 'phabricator-notification', + 'javelin-behavior-device', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'conpherence-thread-manager', + ), '2c426492' => array( 'javelin-behavior', 'javelin-dom', @@ -1067,18 +1090,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - '40b1ff90' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-util', - 'phabricator-notification', - 'javelin-behavior-device', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - ), 42126667 => array( 'javelin-behavior', 'javelin-dom', @@ -1238,6 +1249,18 @@ return array( '69adf288' => array( 'javelin-install', ), + '6bc52765' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '6c2b09a2' => array( 'javelin-install', 'javelin-util', @@ -1447,24 +1470,6 @@ return array( 'phabricator-tooltip', 'changeset-view-manager', ), - '869e3445' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'phabricator-shaped-request', - ), - '86df5915' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-workflow', - 'javelin-stratcom', - ), '87cb6b51' => array( 'javelin-behavior', 'javelin-dom', @@ -1530,6 +1535,15 @@ return array( 'javelin-stratcom', 'javelin-behavior', ), + '8cf41980' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-scrollbar', + 'javelin-quicksand', + 'phabricator-keyboard-shortcut', + 'conpherence-thread-manager', + ), '8e1389b5' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1754,6 +1768,16 @@ return array( 'javelin-stratcom', 'phabricator-phtize', ), + 'd0742f48' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), 'd19198c8' => array( 'javelin-install', 'javelin-dom', @@ -1836,14 +1860,6 @@ return array( 'javelin-dom', 'javelin-uri', ), - 'e4affa94' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-scrollbar', - 'javelin-quicksand', - 'phabricator-keyboard-shortcut', - ), 'e4cc26b3' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php index 160a1f0ab5..ecba5c7f28 100644 --- a/src/applications/conpherence/controller/ConpherenceViewController.php +++ b/src/applications/conpherence/controller/ConpherenceViewController.php @@ -46,7 +46,7 @@ final class ConpherenceViewController extends $content = array('messages' => $messages); } else { $header = $this->buildHeaderPaneContent($conpherence); - $form = $this->renderFormContent($data['latest_transaction_id']); + $form = $this->renderFormContent(); $content = array( 'header' => $header, 'messages' => $messages, @@ -77,7 +77,7 @@ final class ConpherenceViewController extends )); } - private function renderFormContent($latest_transaction_id) { + private function renderFormContent() { $conpherence = $this->getConpherence(); $user = $this->getRequest()->getUser(); @@ -103,20 +103,6 @@ final class ConpherenceViewController extends ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Send'))) - ->appendChild( - javelin_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'latest_transaction_id', - 'value' => $latest_transaction_id, - 'sigil' => 'latest-transaction-id', - 'meta' => array( - 'threadPHID' => $conpherence->getPHID(), - 'threadID' => $conpherence->getID(), - ), - ), - '')) ->render(); return $form; diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index 3dfb8f881f..ca33060986 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -82,10 +82,18 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView { public function getShowDurableColumn() { $request = $this->getRequest(); if ($request) { + if (strncmp( + $request->getRequestURI()->getPath(), + '/conpherence', + strlen('/conpherence')) === 0) { + return false; + } $viewer = $request->getUser(); - return PhabricatorApplication::isClassInstalledForViewer( - 'PhabricatorConpherenceApplication', + if ($viewer->isLoggedIn()) { + return PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorConpherenceApplication', $viewer); + } } return false; } diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js new file mode 100644 index 0000000000..07bdb73fd1 --- /dev/null +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -0,0 +1,271 @@ +/** + * @provides conpherence-thread-manager + * @requires javelin-dom + * javelin-util + * javelin-stratcom + * javelin-install + * javelin-workflow + * javelin-router + * javelin-behavior-device + * javelin-vector + */ +JX.install('ConpherenceThreadManager', { + + construct : function() { + if (__DEV__) { + if (JX.ConpherenceThreadManager._instance) { + JX.$E('ConpherenceThreadManager object is a singleton.'); + } + } + JX.ConpherenceThreadManager._instance = this; + return this; + }, + + members: { + _loadThreadURI: null, + _loadedThreadID: null, + _loadedThreadPHID: null, + _latestTransactionID: null, + _updating: null, + _minimalDisplay: false, + _getMessagesNodeFunction: JX.bag, + _getTitleNodeFunction: JX.bag, + _willLoadThreadCallback: JX.bag, + _didLoadThreadCallback: JX.bag, + _willSendMessageCallback: JX.bag, + _didSendMessageCallback: JX.bag, + + setLoadThreadURI: function(uri) { + this._loadThreadURI = uri; + return this; + }, + + getLoadThreadURI: function() { + return this._loadThreadURI; + }, + + isThreadLoaded: function() { + return Boolean(this._loadedThreadID); + }, + + isThreadIDLoaded: function(thread_id) { + return this._loadedThreadID == thread_id; + }, + + getLoadedThreadID: function() { + return this._loadedThreadID; + }, + + getLoadedThreadPHID: function() { + return this._loadedThreadPHID; + }, + + getLatestTransactionID: function() { + return this._latestTransactionID; + }, + + setLatestTransactionID: function(id) { + this._latestTransactionID = id; + return this; + }, + + setMessagesNodeFunction: function(callback) { + this._getMessagesNodeFunction = callback; + return this; + }, + + _getMessagesNode: function() { + return this._getMessagesNodeFunction(); + }, + + setTitleNodeFunction: function(callback) { + this._getTitleNodeFunction = callback; + return this; + }, + + _getTitleNode: function() { + return this._getTitleNodeFunction(); + }, + + setMinimalDisplay: function(bool) { + this._minimalDisplay = bool; + return this; + }, + + setWillLoadThreadCallback: function(callback) { + this._willLoadThreadCallback = callback; + return this; + }, + + setDidLoadThreadCallback: function(callback) { + this._didLoadThreadCallback = callback; + return this; + }, + + setWillSendMessageCallback: function(callback) { + this._willSendMessageCallback = callback; + return this; + }, + + setDidSendMessageCallback: function(callback) { + this._didSendMessageCallback = callback; + return this; + }, + + _getParams: function(base_params) { + if (this._minimalDisplay) { + base_params.minimal_display = true; + } + if (this._latestTransactionID) { + base_params.latest_transaction_id = this._latestTransactionID; + } + return base_params; + }, + start: function() { + JX.Stratcom.listen( + 'aphlict-server-message', + null, + JX.bind(this, function(e) { + var message = e.getData(); + + if (message.type != 'message') { + // Not a message event. + return; + } + + if (message.threadPHID != this._loadedThreadPHID) { + // Message event for some thread other than the visible one. + return; + } + + if (message.messageID <= this._latestTransactionID) { + // Message event for something we already know about. + return; + } + + // If we're currently updating, wait for the update to complete. + // If this notification tells us about a message which is newer than + // the newest one we know to exist, keep track of it so we can + // update once the in-flight update finishes. + if (this._updating && + this._updating.threadPHID == this._loadedThreadPHID) { + if (message.messageID > this._updating.knownID) { + this._updating.knownID = message.messageID; + return; + } + } + + this._updateThread(); + })); + }, + + _updateThread: function() { + var params = this._getParams({ + action: 'load', + }); + + var uri = '/conpherence/update/' + this._loadedThreadID + '/'; + + var workflow = new JX.Workflow(uri) + .setData(params) + .setHandler(JX.bind(this, function(r) { + this._latestTransactionID = r.latest_transaction_id; + + var messages = this._getMessagesNode(); + JX.DOM.appendContent(messages, JX.$H(r.transactions)); + messages.scrollTop = messages.scrollHeight; + })); + + this.syncWorkflow(workflow, 'finally'); + }, + + syncWorkflow: function(workflow, stage) { + this._updating = { + threadPHID: this._loadedThreadPHID, + knownID: this._latestTransactionID + }; + workflow.listen(stage, JX.bind(this, function() { + // TODO - do we need to handle if we switch threads somehow? + var need_sync = (this._updating.knownID > this._latestTransactionID); + this._updating = null; + if (need_sync) { + this._updateThread(); + } + })); + workflow.start(); + }, + + runUpdateWorkflowFromLink: function(link, params) { + params = this._getParams(params); + + var workflow = new JX.Workflow.newFromLink(link) + .setData(params) + .setHandler(JX.bind(this, function(r) { + this._latestTransactionID = r.latest_transaction_id; + + var messages = this._getMessagesNode(); + JX.DOM.appendContent(messages, JX.$H(r.transactions)); + messages.scrollTop = messages.scrollHeight; + + JX.DOM.setContent(this._getTitleNode(), r.conpherence_title); + })); + this.syncWorkflow(workflow, params.stage); + }, + + loadThreadByID: function(thread_id) { + if (this.isThreadLoaded() && + this.isThreadIDLoaded(thread_id)) { + return; + } + + this._willLoadThreadCallback(); + + var params = {}; + // We pick a thread from the server if not specified + if (thread_id) { + params.id = thread_id; + } + params = this._getParams(params); + + var handler = JX.bind(this, function(r) { + this._loadedThreadID = r.threadID; + this._loadedThreadPHID = r.threadPHID; + this._loadThreadID = r.threadID; + this._latestTransactionID = r.latestTransactionID; + + this._didLoadThreadCallback(r); + }); + + // should this be sync'd too? + new JX.Workflow(this.getLoadThreadURI()) + .setData(params) + .setHandler(handler) + .start(); + }, + + sendMessage: function(form, params) { + params = this._getParams(params); + + this._willSendMessageCallback(); + var workflow = JX.Workflow.newFromForm(form, params) + .setHandler(JX.bind(this, function(r) { + this._latestTransactionID = r.latest_transaction_id; + this._didSendMessageCallback(r); + })); + this.syncWorkflow(workflow, 'finally'); + } + }, + + statics: { + _instance: null, + + getInstance: function() { + var self = JX.ConpherenceThreadManager; + if (!self._instance) { + return null; + } + return self._instance; + } + } + +}); diff --git a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js index fd4c8368bf..8a74ef9167 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-durable-column.js +++ b/webroot/rsrc/js/application/conpherence/behavior-durable-column.js @@ -6,100 +6,20 @@ * javelin-scrollbar * javelin-quicksand * phabricator-keyboard-shortcut + * conpherence-thread-manager */ JX.behavior('durable-column', function() { - var shouldInit = true; + var show = false; var loadThreadID = null; - var loadedThreadID = null; - var loadedThreadPHID = null; - var latestTransactionID = null; var frame = JX.$('phabricator-standard-page'); var quick = JX.$('phabricator-standard-page-body'); - var show = false; - - // TODO - this "upating" stuff is a copy from behavior-pontificate - // TODO: This isn't very clean. When you submit a message, you may get a - // notification about it back before you get the rendered message back. To - // prevent this, we keep track of whether we're currently updating the - // thread. If we are, we hold further updates until the response comes - // back. - - // After the response returns, we'll do another update if we know about - // a transaction newer than the one we got back from the server. - var updating = null; - // Copy continues with slight modifications for how we store data now - JX.Stratcom.listen('aphlict-server-message', null, function(e) { - var message = e.getData(); - - if (message.type != 'message') { - // Not a message event. - return; - } - - if (message.threadPHID != loadedThreadPHID) { - // Message event for some thread other than the visible one. - return; - } - - if (message.messageID <= latestTransactionID) { - // Message event for something we already know about. - return; - } - - // If we're currently updating, wait for the update to complete. - // If this notification tells us about a message which is newer than the - // newest one we know to exist, keep track of it so we can update once - // the in-flight update finishes. - if (updating && updating.threadPHID == loadedThreadPHID) { - if (message.messageID > updating.knownID) { - updating.knownID = message.messageID; - return; - } - } - - update_thread(); - }); - function update_thread() { - var params = { - action: 'load', - latest_transaction_id: latestTransactionID, - minimal_display: true - }; - - var uri = '/conpherence/update/' + loadedThreadID + '/'; - - var workflow = new JX.Workflow(uri) - .setData(params) - .setHandler(function(r) { - var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - latestTransactionID = r.latest_transaction_id; - }); - - sync_workflow(workflow); + function _getColumnContentNode() { + return JX.$('conpherence-durable-column-content'); } - function sync_workflow(workflow) { - updating = { - threadPHID: loadedThreadPHID, - knownID: latestTransactionID - }; - workflow.listen('finally', function() { - var need_sync = (updating && updating.knownID > latestTransactionID); - updating = null; - if (need_sync) { - update_thread(); - } - }); - workflow.start(); - } - // end copy / hack of stuff with big ole TODO on it - function _toggleColumn() { if (window.location.pathname.indexOf('/conpherence/') === 0) { @@ -110,7 +30,7 @@ JX.behavior('durable-column', function() { var column = JX.$('conpherence-durable-column'); if (show) { JX.DOM.show(column); - loadThreadContent(loadThreadID); + threadManager.loadThreadByID(loadThreadID); } else { JX.DOM.hide(column); } @@ -122,10 +42,48 @@ JX.behavior('durable-column', function() { .setHandler(_toggleColumn) .register(); - new JX.Scrollbar(JX.$('conpherence-durable-column-content')); + new JX.Scrollbar(_getColumnContentNode()); JX.Quicksand.start(); + /* Conpherence Thread Manager configuration - lots of display + * callbacks. + */ + var threadManager = new JX.ConpherenceThreadManager(); + threadManager.setMinimalDisplay(true); + threadManager.setMessagesNodeFunction(_getColumnMessagesNode); + threadManager.setTitleNodeFunction(_getColumnTitleNode); + threadManager.setLoadThreadURI('/conpherence/columnview/'); + threadManager.setWillLoadThreadCallback(function () { + _markLoading(true); + }); + threadManager.setDidLoadThreadCallback(function (r) { + var column = _getColumnNode(); + var new_column = JX.$H(r.content); + JX.DOM.replace(column, new_column); + JX.DOM.show(_getColumnNode()); + new JX.Scrollbar(_getColumnContentNode()); + _markLoading(false); + loadThreadID = threadManager.getLoadedThreadID(); + }); + threadManager.setWillSendMessageCallback(function () { + _markLoading(true); + }); + threadManager.setDidSendMessageCallback(function (r) { + var messages = _getColumnMessagesNode(); + JX.DOM.appendContent(messages, JX.$H(r.transactions)); + var content = _getColumnContentNode(); + content.scrollTop = content.scrollHeight; + + var textarea = _getColumnTextareaNode(); + textarea.value = ''; + + _markLoading(false); + + _focusColumnTextareaNode(); + }); + threadManager.start(); + JX.Stratcom.listen( 'click', 'conpherence-durable-column-header-action', @@ -139,61 +97,22 @@ JX.behavior('durable-column', function() { switch (action) { case 'metadata': JX.Stratcom.invoke('notification-panel-close'); - params = { - action: action, - latest_transaction_id: latestTransactionID, - minimal_display: true, - force_ajax: true - }; - var workflow = new JX.Workflow.newFromLink(link) - .setData(params) - .setHandler(function(r) { - var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - var title = _getColumnTitleNode(); - JX.DOM.setContent(title, r.conpherence_title); - - latestTransactionID = r.latest_transaction_id; - // since this is a two step workflow, and the "finally" method - // gets called on the first form load, restore "updating" if - // necessary - if (updating === null) { - updating = { - threadPHID: loadedThreadPHID, - knownID: latestTransactionID - }; - } + threadManager.runUpdateWorkflowFromLink( + link, + { + action: action, + force_ajax: true, + stage: 'submit' }); - sync_workflow(workflow); break; case 'add_person': JX.Stratcom.invoke('notification-panel-close'); - params = { - action: action, - latest_transaction_id: latestTransactionID, - minimal_display: true - }; - var workflow = new JX.Workflow.newFromLink(link) - .setData(params) - .setHandler(function(r) { - var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - latestTransactionID = r.latest_transaction_id; - // since this is a two step workflow, and the "finally" method - // gets called on the first form load, restore "updating" if - // necessary - if (updating === null) { - updating = { - threadPHID: loadedThreadPHID, - knownID: latestTransactionID - }; - } + threadManager.runUpdateWorkflowFromLink( + link, + { + action: action, + stage: 'submit' }); - sync_workflow(workflow); break; case 'go_conpherence': JX.$U(link.href).go(); @@ -217,6 +136,7 @@ JX.behavior('durable-column', function() { 'conpherence-durable-column-body'); } + function _getColumnMessagesNode() { var column = JX.$('conpherence-durable-column'); return JX.DOM.find( @@ -259,65 +179,10 @@ JX.behavior('durable-column', function() { JX.DOM.alterClass(column, 'loading', loading); } - function loadThreadContent(thread_id) { - // loaded this thread already - if (loadedThreadID !== null && loadedThreadID == thread_id) { - return; - } - _markLoading(true); - - var uri = '/conpherence/columnview/'; - var params = null; - // We can pick a thread from the server the first time - if (shouldInit) { - shouldInit = false; - } else { - params = { id : thread_id }; - } - var handler = function(r) { - var column = _getColumnNode(); - var new_column = JX.$H(r.content); - loadedThreadID = r.threadID; - loadedThreadPHID = r.threadPHID; - loadThreadID = r.threadID; - latestTransactionID = r.latestTransactionID; - JX.DOM.replace(column, new_column); - JX.DOM.show(_getColumnNode()); - new JX.Scrollbar(JX.$('conpherence-durable-column-content')); - _markLoading(false); - }; - - new JX.Workflow(uri) - .setData(params) - .setHandler(handler) - .start(); - } - function _sendMessage(e) { e.kill(); - _markLoading(true); - var form = _getColumnFormNode(); - var params = { - latest_transaction_id : latestTransactionID, - minimal_display : true - }; - var workflow = JX.Workflow.newFromForm(form, params) - .setHandler(function(r) { - var messages = _getColumnMessagesNode(); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - var textarea = _getColumnTextareaNode(); - textarea.value = ''; - - latestTransactionID = r.latest_transaction_id; - - _markLoading(false); - - _focusColumnTextareaNode(); - }); - sync_workflow(workflow); + threadManager.sendMessage(form, { minimal_display: true }); } JX.Stratcom.listen( diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 8c84318084..4d085bec4a 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -9,10 +9,10 @@ * javelin-history * javelin-vector * phabricator-shaped-request + * conpherence-thread-manager */ JX.behavior('conpherence-menu', function(config) { - /** * State for displayed thread. */ @@ -22,6 +22,45 @@ JX.behavior('conpherence-menu', function(config) { node: null }; + // TODO - move more logic into the ThreadManager + var threadManager = new JX.ConpherenceThreadManager(); + threadManager.setMessagesNodeFunction(function () { + return JX.DOM.find(document, 'div', 'conpherence-messages'); + }); + threadManager.setWillSendMessageCallback(function () { + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); + var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); + markThreadLoading(true); + JX.DOM.alterClass(form_root, 'loading', true); + }); + threadManager.setDidSendMessageCallback(function (r) { + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); + var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); + var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); + var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages'); + var fileWidget = null; + try { + fileWidget = JX.DOM.find(root, 'div', 'widgets-files'); + } catch (ex) { + // Ignore; maybe no files widget + } + JX.DOM.appendContent(messages, JX.$H(r.transactions)); + messages.scrollTop = messages.scrollHeight; + + if (fileWidget) { + JX.DOM.setContent( + fileWidget, + JX.$H(r.file_widget) + ); + } + var textarea = JX.DOM.find(form_root, 'textarea'); + textarea.value = ''; + markThreadLoading(false); + + setTimeout(function() { JX.DOM.focus(textarea); }, 100); + }); + threadManager.start(); + /** * Current role of this behavior. The two possible roles are to show a 'list' * of threads or a specific 'thread'. On devices, this behavior stays in the @@ -65,9 +104,6 @@ JX.behavior('conpherence-menu', function(config) { function selectThread(node, update_page_data) { if (_thread.node) { JX.DOM.alterClass(_thread.node, 'conpherence-selected', false); - // keep the unread-count hidden still. big TODO once we ajax in updates - // to threads to make this work right and move threads between read / - // unread } JX.DOM.alterClass(node, 'conpherence-selected', true); diff --git a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js index 50b4506ace..b810a24241 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-pontificate.js +++ b/webroot/rsrc/js/application/conpherence/behavior-pontificate.js @@ -5,149 +5,16 @@ * javelin-util * javelin-workflow * javelin-stratcom + * conpherence-thread-manager */ JX.behavior('conpherence-pontificate', function() { - // TODO: This isn't very clean. When you submit a message, you may get a - // notification about it back before you get the rendered message back. To - // prevent this, we keep track of whether we're currently updating the - // thread. If we are, we hold further updates until the response comes - // back. - - // After the response returns, we'll do another update if we know about - // a transaction newer than the one we got back from the server. - var updating = null; - - function get_thread_data() { - // TODO: This is really, really gross. - var infonode = JX.DOM.find(document, 'input', 'latest-transaction-id'); - var data = JX.Stratcom.getData(infonode); - data.latestID = infonode.value; - return data; - } - - function update_latest_transaction_id(id) { - // TODO: Continued grossness from above. - var infonode = JX.DOM.find(document, 'input', 'latest-transaction-id'); - infonode.value = id; - } - - JX.Stratcom.listen('aphlict-server-message', null, function(e) { - var message = e.getData(); - - if (message.type != 'message') { - // Not a message event. - return; - } - - var data = get_thread_data(); - - if (message.threadPHID != data.threadPHID) { - // Message event for some thread other than the visible one. - return; - } - - if (message.messageID <= data.latestID) { - // Message event for something we already know about. - return; - } - - // If we're currently updating, wait for the update to complete. - // If this notification tells us about a message which is newer than the - // newest one we know to exist, keep track of it so we can update once - // the in-flight update finishes. - if (updating && updating.threadPHID == data.threadPHID) { - if (message.messageID > updating.knownID) { - updating.knownID = message.messageID; - return; - } - } - - update_thread(data); - }); - - function update_thread(data) { - var params = { - action: 'load', - latest_transaction_id: data.latestID - }; - - var uri = '/conpherence/update/' + data.threadID + '/'; - - var workflow = new JX.Workflow(uri) - .setData(params) - .setHandler(function(r) { - var messages = JX.DOM.find(document, 'div', 'conpherence-messages'); - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - update_latest_transaction_id(r.latest_transaction_id); - }); - - sync_workflow(workflow, data); - } - - function sync_workflow(workflow, data) { - updating = { - threadPHID: data.threadPHID, - knownID: data.latestID - }; - - workflow.listen('finally', function() { - var new_data = get_thread_data(); - var need_sync = (updating.knownID > new_data.latestID); - - updating = null; - - if (need_sync) { - update_thread(new_data); - } - }); - - workflow.start(); - } - var onsubmit = function(e) { e.kill(); - var form = e.getNode('tag:form'); - - var root = e.getNode('conpherence-layout'); - var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); - var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); - var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages'); - var fileWidget = null; - try { - fileWidget = JX.DOM.find(root, 'div', 'widgets-files'); - } catch (ex) { - // Ignore; maybe no files widget - } - JX.DOM.alterClass(form_root, 'loading', true); - - var workflow = JX.Workflow.newFromForm(form) - .setHandler(JX.bind(this, function(r) { - JX.DOM.appendContent(messages, JX.$H(r.transactions)); - messages.scrollTop = messages.scrollHeight; - - if (fileWidget) { - JX.DOM.setContent( - fileWidget, - JX.$H(r.file_widget) - ); - } - - update_latest_transaction_id(r.latest_transaction_id); - - var textarea = JX.DOM.find(form, 'textarea'); - textarea.value = ''; - - JX.DOM.alterClass(form_root, 'loading', false); - - setTimeout(function() { JX.DOM.focus(textarea); }, 100); - })); - - sync_workflow(workflow, get_thread_data()); + var threadManager = JX.ConpherenceThreadManager.getInstance(); + threadManager.sendMessage(form, {}); }; JX.Stratcom.listen( diff --git a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js index d2bdea3d00..a63306ecdb 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js +++ b/webroot/rsrc/js/application/conpherence/behavior-widget-pane.js @@ -9,6 +9,7 @@ * phuix-dropdown-menu * phuix-action-list-view * phuix-action-view + * conpherence-thread-manager * @provides javelin-behavior-conpherence-widget-pane */ @@ -270,19 +271,18 @@ JX.behavior('conpherence-widget-pane', function(config) { href = create_data.customHref; } - var root = JX.DOM.find(document, 'div', 'conpherence-layout'); - var latest_transaction_dom = JX.DOM.find( - root, - 'input', - 'latest-transaction-id'); + var threadManager = JX.ConpherenceThreadManager.getInstance(); + var latest_transaction_id = threadManager.getLatestTransactionID(); var data = { - latest_transaction_id : latest_transaction_dom.value, + latest_transaction_id : latest_transaction_id, action : create_data.action }; - new JX.Workflow(href, data) + var workflow = new JX.Workflow(href, data) .setHandler(function (r) { - latest_transaction_dom.value = r.latest_transaction_id; + var threadManager = JX.ConpherenceThreadManager.getInstance(); + threadManager.setLatestTransactionID(r.latest_transaction_id); + var root = JX.DOM.find(document, 'div', 'conpherence-layout'); if (create_data.refreshFromResponse) { var messages = null; try { @@ -315,8 +315,8 @@ JX.behavior('conpherence-widget-pane', function(config) { widget : widget_to_update }); } - }) - .start(); + }); + threadManager.syncWorkflow(workflow, 'submit'); } );