Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/conpherence/behavior-menu.js
12242 views
1
/**
2
* @provides javelin-behavior-conpherence-menu
3
* @requires javelin-behavior
4
* javelin-dom
5
* javelin-util
6
* javelin-stratcom
7
* javelin-workflow
8
* javelin-behavior-device
9
* javelin-history
10
* javelin-vector
11
* javelin-scrollbar
12
* phabricator-title
13
* phabricator-shaped-request
14
* conpherence-thread-manager
15
*/
16
17
JX.behavior('conpherence-menu', function(config) {
18
/**
19
* State for displayed thread.
20
*/
21
var _thread = {
22
selected: null,
23
visible: null,
24
node: null
25
};
26
27
var scrollbar = null;
28
var cur_theme = config.theme;
29
30
// TODO - move more logic into the ThreadManager
31
var threadManager = new JX.ConpherenceThreadManager();
32
threadManager.setMessagesRootCallback(function() {
33
return scrollbar.getContentNode();
34
});
35
threadManager.setWillLoadThreadCallback(function() {
36
markThreadsLoading(true);
37
});
38
threadManager.setDidLoadThreadCallback(function(r) {
39
var header = JX.$H(r.header);
40
var search = JX.$H(r.search);
41
var messages = JX.$H(r.transactions);
42
var form = JX.$H(r.form);
43
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
44
var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane');
45
var search_root = JX.DOM.find(root, 'div', 'conpherence-search-main');
46
var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
47
JX.DOM.setContent(header_root, header);
48
JX.DOM.setContent(search_root, search);
49
JX.DOM.setContent(scrollbar.getContentNode(), messages);
50
JX.DOM.setContent(form_root, form);
51
52
markThreadsLoading(false);
53
54
didRedrawThread(true);
55
});
56
57
threadManager.setDidUpdateThreadCallback(function(r) {
58
_scrollMessageWindow();
59
});
60
61
threadManager.setWillSendMessageCallback(function () {
62
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
63
var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
64
markThreadLoading(true);
65
JX.DOM.alterClass(form_root, 'loading', true);
66
});
67
68
threadManager.setDidSendMessageCallback(function (r, non_update) {
69
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
70
var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
71
var textarea = JX.DOM.find(form_root, 'textarea');
72
if (!non_update) {
73
_scrollMessageWindow();
74
}
75
markThreadLoading(false);
76
77
setTimeout(function() { JX.DOM.focus(textarea); }, 100);
78
});
79
threadManager.start();
80
81
/**
82
* Current role of this behavior. The two possible roles are to show a 'list'
83
* of threads or a specific 'thread'. On devices, this behavior stays in the
84
* 'list' role indefinitely, treating clicks normally and the next page
85
* loads the behavior with role = 'thread'. On desktop, this behavior
86
* auto-loads a thread as part of the 'list' role. As the thread loads the
87
* role is changed to 'thread'.
88
*/
89
var _currentRole = null;
90
91
/**
92
* When _oldDevice is null the code is executing for the first time.
93
*/
94
var _oldDevice = null;
95
96
/**
97
* Initializes this behavior based on all the configuration jonx and the
98
* result of JX.Device.getDevice();
99
*/
100
function init() {
101
_currentRole = config.role;
102
103
if (_currentRole == 'thread') {
104
markThreadsLoading(true);
105
} else {
106
markThreadLoading(true);
107
}
108
markWidgetLoading(true);
109
onDeviceChange();
110
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
111
var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane');
112
var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages');
113
scrollbar = new JX.Scrollbar(messages);
114
scrollbar.setAsScrollFrame();
115
}
116
init();
117
118
/**
119
* Selecting threads
120
*/
121
function selectThreadByID(id, update_page_data) {
122
var thread = JX.$(id);
123
selectThread(thread, update_page_data);
124
}
125
126
function selectThread(node, update_page_data) {
127
if (_thread.node) {
128
JX.DOM.alterClass(_thread.node, 'phui-list-item-selected', false);
129
}
130
131
JX.DOM.alterClass(node, 'phui-list-item-selected', true);
132
JX.DOM.alterClass(node, 'hide-unread-count', true);
133
134
_thread.node = node;
135
136
var data = JX.Stratcom.getData(node);
137
_thread.selected = data.threadID;
138
139
if (update_page_data) {
140
updatePageData(data);
141
}
142
143
redrawThread();
144
}
145
146
function updatePageData(data) {
147
var uri = '/Z' + _thread.selected;
148
var new_theme = data.theme;
149
150
if (new_theme != cur_theme) {
151
var root = JX.$('conpherence-main-layout');
152
JX.DOM.alterClass(root, cur_theme, false);
153
JX.DOM.alterClass(root, new_theme, true);
154
cur_theme = new_theme;
155
}
156
JX.History.replace(uri);
157
if (data.title) {
158
JX.Title.setTitle(data.title);
159
} else if (_thread.node) {
160
var threadData = JX.Stratcom.getData(_thread.node);
161
JX.Title.setTitle(threadData.title);
162
}
163
}
164
165
JX.Stratcom.listen(
166
'conpherence-update-page-data',
167
null,
168
function (e) {
169
updatePageData(e.getData());
170
}
171
);
172
173
function redrawThread() {
174
if (!_thread.node) {
175
return;
176
}
177
178
if (_thread.visible == _thread.selected) {
179
return;
180
}
181
182
var data = JX.Stratcom.getData(_thread.node);
183
184
if (_thread.visible !== null || !config.hasThread) {
185
threadManager.setLoadThreadURI('/conpherence/' + data.threadID + '/');
186
threadManager.loadThreadByID(data.threadID);
187
} else if (config.hasThread) {
188
// we loaded the thread via the server so let the thread manager know
189
threadManager.setLoadedThreadID(config.selectedThreadID);
190
threadManager.setLoadedThreadPHID(config.selectedThreadPHID);
191
threadManager.setLatestTransactionID(config.latestTransactionID);
192
threadManager.setCanEditLoadedThread(config.canEditSelectedThread);
193
threadManager.cacheCurrentTransactions();
194
_scrollMessageWindow();
195
_focusTextarea();
196
} else {
197
didRedrawThread();
198
}
199
200
if (_thread.visible !== null || !config.hasWidgets) {
201
reloadWidget(data);
202
}
203
204
_thread.visible = _thread.selected;
205
}
206
207
function markThreadsLoading(loading) {
208
var root = JX.$('conpherence-main-layout');
209
JX.DOM.alterClass(root, 'loading', loading);
210
}
211
212
function markThreadLoading(loading) {
213
try {
214
var textarea = JX.DOM.find(form, 'textarea');
215
textarea.disabled = loading;
216
var button = JX.DOM.find(form, 'button');
217
button.disabled = loading;
218
} catch (ex) {
219
// haven't loaded it yet!
220
}
221
}
222
223
function markWidgetLoading(loading) {
224
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
225
var widgets_root = JX.DOM.find(root, 'div', 'conpherence-participant-pane');
226
227
JX.DOM.alterClass(widgets_root, 'loading', loading);
228
}
229
230
function reloadWidget(data) {
231
markWidgetLoading(true);
232
if (!data.widget) {
233
data.widget = getDefaultWidget();
234
}
235
var widget_uri = config.baseURI + 'participant/' + data.threadID + '/';
236
new JX.Workflow(widget_uri, {})
237
.setHandler(JX.bind(null, onWidgetResponse, data.threadID, data.widget))
238
.start();
239
}
240
JX.Stratcom.listen(
241
'conpherence-reload-widget',
242
null,
243
function (e) {
244
var data = e.getData();
245
if (data.threadID != _thread.selected) {
246
return;
247
}
248
reloadWidget(data);
249
}
250
);
251
252
function onWidgetResponse(thread_id, widget, response) {
253
// we got impatient and this is no longer the right answer :/
254
if (_thread.selected != thread_id) {
255
return;
256
}
257
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
258
var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widgets-holder');
259
JX.DOM.setContent(widgets_root, JX.$H(response.widgets));
260
}
261
262
function getDefaultWidget() {
263
return 'widgets-people';
264
}
265
266
/**
267
* This function is a wee bit tricky. Internally, we want to scroll the
268
* message window and let other stuff - notably widgets - redraw / build if
269
* necessary. Externally, we want a hook to scroll the message window
270
* - notably when the widget selector is used to invoke the message pane.
271
* The following three functions get 'er done.
272
*/
273
function didRedrawThread(build_device_widget_selector) {
274
_scrollMessageWindow();
275
_focusTextarea();
276
JX.Stratcom.invoke(
277
'conpherence-did-redraw-thread',
278
null,
279
{
280
widget : getDefaultWidget(),
281
threadID : _thread.selected,
282
buildDeviceWidgetSelector : build_device_widget_selector
283
});
284
}
285
286
var _firstScroll = true;
287
function _scrollMessageWindow() {
288
if (_firstScroll) {
289
_firstScroll = false;
290
291
// We want to let the standard #anchor tech take over after we make sure
292
// we don't have to present the user with a "load older message?" dialog
293
if (window.location.hash) {
294
var hash = window.location.hash.replace(/^#/, '');
295
try {
296
JX.$('anchor-' + hash);
297
} catch (ex) {
298
var uri = '/conpherence/' +
299
_thread.selected + '/' + hash + '/';
300
threadManager.setLoadThreadURI(uri);
301
threadManager.loadThreadByID(_thread.selected, true);
302
_firstScroll = true;
303
return;
304
}
305
return;
306
}
307
}
308
scrollbar.scrollTo(scrollbar.getViewportNode().scrollHeight);
309
}
310
function _focusTextarea() {
311
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
312
var form_root = JX.DOM.find(root, 'div', 'conpherence-form');
313
try {
314
var textarea = JX.DOM.find(form_root, 'textarea');
315
// We may have a draft so do this JS trick so we end up focused at the
316
// end of the draft.
317
var textarea_value = textarea.value;
318
textarea.value = '';
319
JX.DOM.focus(textarea);
320
textarea.value = textarea_value;
321
} catch (ex) {
322
// no textarea? no problem
323
}
324
}
325
JX.Stratcom.listen(
326
'conpherence-redraw-thread',
327
null,
328
function () {
329
_scrollMessageWindow();
330
}
331
);
332
333
JX.Stratcom.listen(
334
'click',
335
'conpherence-menu-click',
336
function(e) {
337
if (!e.isNormalClick()) {
338
return;
339
}
340
341
// On devices, just follow the link normally.
342
if (JX.Device.getDevice() != 'desktop') {
343
return;
344
}
345
346
e.kill();
347
selectThread(e.getNode('conpherence-menu-click'), true);
348
});
349
350
/**
351
* On devices, we just show a thread list, so we don't want to automatically
352
* select or load any threads. On desktop, we automatically select the first
353
* thread, changing the _currentRole from list to thread.
354
*/
355
function onDeviceChange() {
356
var new_device = JX.Device.getDevice();
357
if (new_device === _oldDevice) {
358
return;
359
}
360
361
if (_oldDevice === null) {
362
_oldDevice = new_device;
363
if (_currentRole == 'list') {
364
if (new_device != 'desktop') {
365
return;
366
}
367
} else {
368
loadThreads();
369
return;
370
}
371
}
372
var update_toggled_widget =
373
new_device == 'desktop' || _oldDevice == 'desktop';
374
_oldDevice = new_device;
375
376
if (_thread.visible !== null && update_toggled_widget) {
377
JX.Stratcom.invoke(
378
'conpherence-did-redraw-thread',
379
null,
380
{
381
widget : getDefaultWidget(),
382
threadID : _thread.selected
383
});
384
}
385
386
if (_currentRole == 'list' && new_device == 'desktop') {
387
// this selects a thread and loads it
388
didLoadThreads();
389
_currentRole = 'thread';
390
var root = JX.DOM.find(document, 'div', 'conpherence-layout');
391
JX.DOM.alterClass(root, 'conpherence-role-list', false);
392
JX.DOM.alterClass(root, 'conpherence-role-thread', true);
393
}
394
}
395
JX.Stratcom.listen('phabricator-device-change', null, onDeviceChange);
396
397
function loadThreads() {
398
markThreadsLoading(true);
399
var uri = config.baseURI + 'thread/' + config.selectedThreadID + '/';
400
new JX.Workflow(uri)
401
.setHandler(onLoadThreadsResponse)
402
.start();
403
}
404
405
function onLoadThreadsResponse(r) {
406
var layout = JX.$(config.layoutID);
407
var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane');
408
JX.DOM.setContent(menu, JX.$H(r));
409
410
config.selectedID && selectThreadByID(config.selectedID);
411
412
markThreadsLoading(false);
413
}
414
415
function didLoadThreads() {
416
// If there's no thread selected yet, select the current thread or the
417
// first thread.
418
if (!_thread.selected) {
419
if (config.selectedID) {
420
selectThreadByID(config.selectedID, true);
421
} else {
422
var layout = JX.$(config.layoutID);
423
var threads = JX.DOM.scry(layout, 'a', 'conpherence-menu-click');
424
if (threads.length) {
425
selectThread(threads[0]);
426
} else {
427
var nothreads = JX.DOM.find(layout, 'div', 'conpherence-no-threads');
428
nothreads.style.display = 'block';
429
markThreadLoading(false);
430
markWidgetLoading(false);
431
}
432
}
433
}
434
}
435
436
JX.Stratcom.listen(
437
['keydown'],
438
'conpherence-pontificate',
439
function (e) {
440
threadManager.handleDraftKeydown(e);
441
});
442
443
});
444
445