1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-23 15:22:41 +01:00

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
This commit is contained in:
Bob Trahan 2015-03-10 12:20:29 -07:00
parent dd501117e8
commit 2373185e9b
8 changed files with 466 additions and 417 deletions

View file

@ -11,7 +11,7 @@ return array(
'core.pkg.js' => '5a1c336d', 'core.pkg.js' => '5a1c336d',
'darkconsole.pkg.js' => '8ab24e01', 'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => '1940be3f', 'differential.pkg.css' => '1940be3f',
'differential.pkg.js' => 'e62fe1cf', 'differential.pkg.js' => '53c1ccc2',
'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => 'bfc0737b', 'diffusion.pkg.js' => 'bfc0737b',
'maniphest.pkg.css' => '68d4dd3d', 'maniphest.pkg.css' => '68d4dd3d',
@ -351,17 +351,18 @@ return array(
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de',
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'e4affa94', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'd0742f48',
'rsrc/js/application/conpherence/behavior-menu.js' => '869e3445', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '8cf41980',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '86df5915', 'rsrc/js/application/conpherence/behavior-menu.js' => '6bc52765',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90', '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/countdown/timer.js' => 'e4cc26b3',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', '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-move-panels.js' => '82439934',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
'rsrc/js/application/differential/ChangesetViewManager.js' => '88be0133', '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-add-reviewers-and-ccs.js' => 'e10f8e18',
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5', 'rsrc/js/application/differential/behavior-comment-preview.js' => '8e1389b5',
@ -516,11 +517,12 @@ return array(
'conpherence-menu-css' => 'c6ac5299', 'conpherence-menu-css' => 'c6ac5299',
'conpherence-message-pane-css' => '5930260a', 'conpherence-message-pane-css' => '5930260a',
'conpherence-notification-css' => '04a6e10a', 'conpherence-notification-css' => '04a6e10a',
'conpherence-thread-manager' => 'd0742f48',
'conpherence-update-css' => '1099a660', 'conpherence-update-css' => '1099a660',
'conpherence-widget-pane-css' => '3d575438', 'conpherence-widget-pane-css' => '3d575438',
'differential-changeset-view-css' => '6a8b172a', 'differential-changeset-view-css' => '6a8b172a',
'differential-core-view-css' => '7ac3cabc', 'differential-core-view-css' => '7ac3cabc',
'differential-inline-comment-editor' => '1b772f31', 'differential-inline-comment-editor' => '0286a1db',
'differential-results-table-css' => '181aa9d9', 'differential-results-table-css' => '181aa9d9',
'differential-revision-add-comment-css' => 'c478bcaa', 'differential-revision-add-comment-css' => 'c478bcaa',
'differential-revision-comment-css' => '48186045', 'differential-revision-comment-css' => '48186045',
@ -555,9 +557,9 @@ return array(
'javelin-behavior-boards-dropdown' => '0ec56e1d', 'javelin-behavior-boards-dropdown' => '0ec56e1d',
'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-choose-control' => '6153c708',
'javelin-behavior-config-reorder-fields' => '14a827de', 'javelin-behavior-config-reorder-fields' => '14a827de',
'javelin-behavior-conpherence-menu' => '869e3445', 'javelin-behavior-conpherence-menu' => '6bc52765',
'javelin-behavior-conpherence-pontificate' => '86df5915', 'javelin-behavior-conpherence-pontificate' => '21ba5861',
'javelin-behavior-conpherence-widget-pane' => '40b1ff90', 'javelin-behavior-conpherence-widget-pane' => '2c1cd7f5',
'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-countdown-timer' => 'e4cc26b3',
'javelin-behavior-dark-console' => '08883e8b', 'javelin-behavior-dark-console' => '08883e8b',
'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-async-panel' => '469c0d9e',
@ -582,7 +584,7 @@ return array(
'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => '2b228192', 'javelin-behavior-diffusion-pull-lastmodified' => '2b228192',
'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-doorkeeper-tag' => 'e5822781',
'javelin-behavior-durable-column' => 'e4affa94', 'javelin-behavior-durable-column' => '8cf41980',
'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-fancy-datepicker' => 'c51ae228', 'javelin-behavior-fancy-datepicker' => 'c51ae228',
'javelin-behavior-global-drag-and-drop' => '07f199d8', 'javelin-behavior-global-drag-and-drop' => '07f199d8',
@ -830,6 +832,14 @@ return array(
'unhandled-exception-css' => '37d4f9a2', 'unhandled-exception-css' => '37d4f9a2',
), ),
'requires' => array( 'requires' => array(
'0286a1db' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-request',
'javelin-workflow',
),
'029a133d' => array( '029a133d' => array(
'aphront-dialog-view-css', 'aphront-dialog-view-css',
), ),
@ -931,14 +941,6 @@ return array(
'javelin-util', 'javelin-util',
'phabricator-keyboard-shortcut-manager', 'phabricator-keyboard-shortcut-manager',
), ),
'1b772f31' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-request',
'javelin-workflow',
),
'1d298e3a' => array( '1d298e3a' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@ -968,6 +970,14 @@ return array(
'phabricator-phtize', 'phabricator-phtize',
'changeset-view-manager', 'changeset-view-manager',
), ),
'21ba5861' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
'conpherence-thread-manager',
),
'2290aeef' => array( '2290aeef' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@ -1012,6 +1022,19 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'javelin-dom', '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( '2c426492' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',
@ -1067,18 +1090,6 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-reactor-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( 42126667 => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',
@ -1238,6 +1249,18 @@ return array(
'69adf288' => array( '69adf288' => array(
'javelin-install', '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( '6c2b09a2' => array(
'javelin-install', 'javelin-install',
'javelin-util', 'javelin-util',
@ -1447,24 +1470,6 @@ return array(
'phabricator-tooltip', 'phabricator-tooltip',
'changeset-view-manager', '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( '87cb6b51' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',
@ -1530,6 +1535,15 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'javelin-behavior', 'javelin-behavior',
), ),
'8cf41980' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
'conpherence-thread-manager',
),
'8e1389b5' => array( '8e1389b5' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1754,6 +1768,16 @@ return array(
'javelin-stratcom', 'javelin-stratcom',
'phabricator-phtize', 'phabricator-phtize',
), ),
'd0742f48' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
'd19198c8' => array( 'd19198c8' => array(
'javelin-install', 'javelin-install',
'javelin-dom', 'javelin-dom',
@ -1836,14 +1860,6 @@ return array(
'javelin-dom', 'javelin-dom',
'javelin-uri', 'javelin-uri',
), ),
'e4affa94' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
),
'e4cc26b3' => array( 'e4cc26b3' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',

View file

@ -46,7 +46,7 @@ final class ConpherenceViewController extends
$content = array('messages' => $messages); $content = array('messages' => $messages);
} else { } else {
$header = $this->buildHeaderPaneContent($conpherence); $header = $this->buildHeaderPaneContent($conpherence);
$form = $this->renderFormContent($data['latest_transaction_id']); $form = $this->renderFormContent();
$content = array( $content = array(
'header' => $header, 'header' => $header,
'messages' => $messages, 'messages' => $messages,
@ -77,7 +77,7 @@ final class ConpherenceViewController extends
)); ));
} }
private function renderFormContent($latest_transaction_id) { private function renderFormContent() {
$conpherence = $this->getConpherence(); $conpherence = $this->getConpherence();
$user = $this->getRequest()->getUser(); $user = $this->getRequest()->getUser();
@ -103,20 +103,6 @@ final class ConpherenceViewController extends
->appendChild( ->appendChild(
id(new AphrontFormSubmitControl()) id(new AphrontFormSubmitControl())
->setValue(pht('Send'))) ->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(); ->render();
return $form; return $form;

View file

@ -82,10 +82,18 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
public function getShowDurableColumn() { public function getShowDurableColumn() {
$request = $this->getRequest(); $request = $this->getRequest();
if ($request) { if ($request) {
if (strncmp(
$request->getRequestURI()->getPath(),
'/conpherence',
strlen('/conpherence')) === 0) {
return false;
}
$viewer = $request->getUser(); $viewer = $request->getUser();
return PhabricatorApplication::isClassInstalledForViewer( if ($viewer->isLoggedIn()) {
'PhabricatorConpherenceApplication', return PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorConpherenceApplication',
$viewer); $viewer);
}
} }
return false; return false;
} }

View file

@ -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;
}
}
});

View file

@ -6,100 +6,20 @@
* javelin-scrollbar * javelin-scrollbar
* javelin-quicksand * javelin-quicksand
* phabricator-keyboard-shortcut * phabricator-keyboard-shortcut
* conpherence-thread-manager
*/ */
JX.behavior('durable-column', function() { JX.behavior('durable-column', function() {
var shouldInit = true; var show = false;
var loadThreadID = null; var loadThreadID = null;
var loadedThreadID = null;
var loadedThreadPHID = null;
var latestTransactionID = null;
var frame = JX.$('phabricator-standard-page'); var frame = JX.$('phabricator-standard-page');
var quick = JX.$('phabricator-standard-page-body'); var quick = JX.$('phabricator-standard-page-body');
var show = false;
function _getColumnContentNode() {
// TODO - this "upating" stuff is a copy from behavior-pontificate return JX.$('conpherence-durable-column-content');
// 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 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() { function _toggleColumn() {
if (window.location.pathname.indexOf('/conpherence/') === 0) { if (window.location.pathname.indexOf('/conpherence/') === 0) {
@ -110,7 +30,7 @@ JX.behavior('durable-column', function() {
var column = JX.$('conpherence-durable-column'); var column = JX.$('conpherence-durable-column');
if (show) { if (show) {
JX.DOM.show(column); JX.DOM.show(column);
loadThreadContent(loadThreadID); threadManager.loadThreadByID(loadThreadID);
} else { } else {
JX.DOM.hide(column); JX.DOM.hide(column);
} }
@ -122,10 +42,48 @@ JX.behavior('durable-column', function() {
.setHandler(_toggleColumn) .setHandler(_toggleColumn)
.register(); .register();
new JX.Scrollbar(JX.$('conpherence-durable-column-content')); new JX.Scrollbar(_getColumnContentNode());
JX.Quicksand.start(); 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( JX.Stratcom.listen(
'click', 'click',
'conpherence-durable-column-header-action', 'conpherence-durable-column-header-action',
@ -139,61 +97,22 @@ JX.behavior('durable-column', function() {
switch (action) { switch (action) {
case 'metadata': case 'metadata':
JX.Stratcom.invoke('notification-panel-close'); JX.Stratcom.invoke('notification-panel-close');
params = { threadManager.runUpdateWorkflowFromLink(
action: action, link,
latest_transaction_id: latestTransactionID, {
minimal_display: true, action: action,
force_ajax: true force_ajax: true,
}; stage: 'submit'
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
};
}
}); });
sync_workflow(workflow);
break; break;
case 'add_person': case 'add_person':
JX.Stratcom.invoke('notification-panel-close'); JX.Stratcom.invoke('notification-panel-close');
params = { threadManager.runUpdateWorkflowFromLink(
action: action, link,
latest_transaction_id: latestTransactionID, {
minimal_display: true action: action,
}; stage: 'submit'
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
};
}
}); });
sync_workflow(workflow);
break; break;
case 'go_conpherence': case 'go_conpherence':
JX.$U(link.href).go(); JX.$U(link.href).go();
@ -217,6 +136,7 @@ JX.behavior('durable-column', function() {
'conpherence-durable-column-body'); 'conpherence-durable-column-body');
} }
function _getColumnMessagesNode() { function _getColumnMessagesNode() {
var column = JX.$('conpherence-durable-column'); var column = JX.$('conpherence-durable-column');
return JX.DOM.find( return JX.DOM.find(
@ -259,65 +179,10 @@ JX.behavior('durable-column', function() {
JX.DOM.alterClass(column, 'loading', loading); 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) { function _sendMessage(e) {
e.kill(); e.kill();
_markLoading(true);
var form = _getColumnFormNode(); var form = _getColumnFormNode();
var params = { threadManager.sendMessage(form, { minimal_display: true });
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);
} }
JX.Stratcom.listen( JX.Stratcom.listen(

View file

@ -9,10 +9,10 @@
* javelin-history * javelin-history
* javelin-vector * javelin-vector
* phabricator-shaped-request * phabricator-shaped-request
* conpherence-thread-manager
*/ */
JX.behavior('conpherence-menu', function(config) { JX.behavior('conpherence-menu', function(config) {
/** /**
* State for displayed thread. * State for displayed thread.
*/ */
@ -22,6 +22,45 @@ JX.behavior('conpherence-menu', function(config) {
node: null 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' * 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 * 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) { function selectThread(node, update_page_data) {
if (_thread.node) { if (_thread.node) {
JX.DOM.alterClass(_thread.node, 'conpherence-selected', false); 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); JX.DOM.alterClass(node, 'conpherence-selected', true);

View file

@ -5,149 +5,16 @@
* javelin-util * javelin-util
* javelin-workflow * javelin-workflow
* javelin-stratcom * javelin-stratcom
* conpherence-thread-manager
*/ */
JX.behavior('conpherence-pontificate', function() { 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) { var onsubmit = function(e) {
e.kill(); e.kill();
var form = e.getNode('tag:form'); var form = e.getNode('tag:form');
var threadManager = JX.ConpherenceThreadManager.getInstance();
var root = e.getNode('conpherence-layout'); threadManager.sendMessage(form, {});
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());
}; };
JX.Stratcom.listen( JX.Stratcom.listen(

View file

@ -9,6 +9,7 @@
* phuix-dropdown-menu * phuix-dropdown-menu
* phuix-action-list-view * phuix-action-list-view
* phuix-action-view * phuix-action-view
* conpherence-thread-manager
* @provides javelin-behavior-conpherence-widget-pane * @provides javelin-behavior-conpherence-widget-pane
*/ */
@ -270,19 +271,18 @@ JX.behavior('conpherence-widget-pane', function(config) {
href = create_data.customHref; href = create_data.customHref;
} }
var root = JX.DOM.find(document, 'div', 'conpherence-layout'); var threadManager = JX.ConpherenceThreadManager.getInstance();
var latest_transaction_dom = JX.DOM.find( var latest_transaction_id = threadManager.getLatestTransactionID();
root,
'input',
'latest-transaction-id');
var data = { var data = {
latest_transaction_id : latest_transaction_dom.value, latest_transaction_id : latest_transaction_id,
action : create_data.action action : create_data.action
}; };
new JX.Workflow(href, data) var workflow = new JX.Workflow(href, data)
.setHandler(function (r) { .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) { if (create_data.refreshFromResponse) {
var messages = null; var messages = null;
try { try {
@ -315,8 +315,8 @@ JX.behavior('conpherence-widget-pane', function(config) {
widget : widget_to_update widget : widget_to_update
}); });
} }
}) });
.start(); threadManager.syncWorkflow(workflow, 'submit');
} }
); );