[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 * phabricator-shaped-request 12 */ 13 14 JX.behavior('conpherence-menu', function(config) { 15 16 /** 17 * State for displayed thread. 18 */ 19 var _thread = { 20 selected: null, 21 visible: null, 22 node: null 23 }; 24 25 /** 26 * Current role of this behavior. The two possible roles are to show a 'list' 27 * of threads or a specific 'thread'. On devices, this behavior stays in the 28 * 'list' role indefinitely, treating clicks normally and the next page 29 * loads the behavior with role = 'thread'. On desktop, this behavior 30 * auto-loads a thread as part of the 'list' role. As the thread loads the 31 * role is changed to 'thread'. 32 */ 33 var _currentRole = null; 34 35 /** 36 * When _oldDevice is null the code is executing for the first time. 37 */ 38 var _oldDevice = null; 39 40 /** 41 * Initializes this behavior based on all the configuraton jonx and the 42 * result of JX.Device.getDevice(); 43 */ 44 function init() { 45 _currentRole = config.role; 46 47 if (_currentRole == 'thread') { 48 markThreadsLoading(true); 49 } else { 50 markThreadLoading(true); 51 } 52 markWidgetLoading(true); 53 onDeviceChange(); 54 } 55 init(); 56 57 /** 58 * Selecting threads 59 */ 60 JX.Stratcom.listen( 61 'conpherence-selectthread', 62 null, 63 function (e) { 64 selectThreadByID(e.getData().id); 65 } 66 ); 67 68 function selectThreadByID(id, update_page_data) { 69 var thread = JX.$(id); 70 selectThread(thread, update_page_data); 71 } 72 73 function selectThread(node, update_page_data) { 74 if (_thread.node) { 75 JX.DOM.alterClass(_thread.node, 'conpherence-selected', false); 76 // keep the unread-count hidden still. big TODO once we ajax in updates 77 // to threads to make this work right and move threads between read / 78 // unread 79 } 80 81 JX.DOM.alterClass(node, 'conpherence-selected', true); 82 JX.DOM.alterClass(node, 'hide-unread-count', true); 83 84 _thread.node = node; 85 86 var data = JX.Stratcom.getData(node); 87 _thread.selected = data.threadID; 88 89 if (update_page_data) { 90 updatePageData(data); 91 } 92 93 redrawThread(); 94 } 95 96 function updatePageData(data) { 97 var uri_suffix = _thread.selected + '/'; 98 if (data.use_base_uri) { 99 uri_suffix = ''; 100 } 101 JX.History.replace(config.baseURI + uri_suffix); 102 if (data.title) { 103 document.title = data.title; 104 } else if (_thread.node) { 105 var threadData = JX.Stratcom.getData(_thread.node); 106 document.title = threadData.title; 107 } 108 } 109 110 JX.Stratcom.listen( 111 'conpherence-update-page-data', 112 null, 113 function (e) { 114 updatePageData(e.getData()); 115 } 116 ); 117 118 function redrawThread() { 119 if (!_thread.node) { 120 return; 121 } 122 123 if (_thread.visible == _thread.selected) { 124 return; 125 } 126 127 var data = JX.Stratcom.getData(_thread.node); 128 129 if (_thread.visible !== null || !config.hasThread) { 130 markThreadLoading(true); 131 var uri = config.baseURI + data.threadID + '/'; 132 new JX.Workflow(uri, {}) 133 .setHandler(JX.bind(null, onLoadThreadResponse, data.threadID)) 134 .start(); 135 } else if (config.hasThread) { 136 _scrollMessageWindow(); 137 } else { 138 didRedrawThread(); 139 } 140 141 if (_thread.visible !== null || !config.hasWidgets) { 142 reloadWidget(data); 143 } else { 144 JX.Stratcom.invoke( 145 'conpherence-update-widgets', 146 null, 147 { 148 widget : getDefaultWidget(), 149 buildSelectors : false, 150 toggleWidget : true, 151 threadID : _thread.selected 152 }); 153 } 154 155 _thread.visible = _thread.selected; 156 } 157 158 function markThreadsLoading(loading) { 159 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 160 var menu = JX.DOM.find(root, 'div', 'conpherence-menu-pane'); 161 JX.DOM.alterClass(menu, 'loading', loading); 162 } 163 164 function markThreadLoading(loading) { 165 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 166 var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane'); 167 var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane'); 168 var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); 169 170 JX.DOM.alterClass(header_root, 'loading', loading); 171 JX.DOM.alterClass(messages_root, 'loading', loading); 172 JX.DOM.alterClass(form_root, 'loading', loading); 173 174 try { 175 var textarea = JX.DOM.find(form, 'textarea'); 176 textarea.disabled = loading; 177 var button = JX.DOM.find(form, 'button'); 178 button.disabled = loading; 179 } catch (ex) { 180 // haven't loaded it yet! 181 } 182 } 183 184 function markWidgetLoading(loading) { 185 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 186 var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widget-pane'); 187 188 JX.DOM.alterClass(widgets_root, 'loading', loading); 189 } 190 191 function reloadWidget(data) { 192 markWidgetLoading(true); 193 if (!data.widget) { 194 data.widget = getDefaultWidget(); 195 } 196 var widget_uri = config.baseURI + 'widget/' + data.threadID + '/'; 197 new JX.Workflow(widget_uri, {}) 198 .setHandler(JX.bind(null, onWidgetResponse, data.threadID, data.widget)) 199 .start(); 200 } 201 JX.Stratcom.listen( 202 'conpherence-reload-widget', 203 null, 204 function (e) { 205 var data = e.getData(); 206 if (data.threadID != _thread.selected) { 207 return; 208 } 209 reloadWidget(data); 210 } 211 ); 212 213 function onWidgetResponse(thread_id, widget, response) { 214 // we got impatient and this is no longer the right answer :/ 215 if (_thread.selected != thread_id) { 216 return; 217 } 218 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 219 var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widgets-holder'); 220 JX.DOM.setContent(widgets_root, JX.$H(response.widgets)); 221 222 JX.Stratcom.invoke( 223 'conpherence-update-widgets', 224 null, 225 { 226 widget : widget, 227 buildSelectors : true, 228 toggleWidget : true, 229 threadID : _thread.selected 230 }); 231 232 markWidgetLoading(false); 233 } 234 235 function getDefaultWidget() { 236 var device = JX.Device.getDevice(); 237 var widget = 'conpherence-message-pane'; 238 if (device == 'desktop') { 239 widget = 'widgets-people'; 240 } 241 return widget; 242 } 243 244 function onLoadThreadResponse(thread_id, response) { 245 // we got impatient and this is no longer the right answer :/ 246 if (_thread.selected != thread_id) { 247 return; 248 } 249 var header = JX.$H(response.header); 250 var messages = JX.$H(response.messages); 251 var form = JX.$H(response.form); 252 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 253 var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane'); 254 var messages_root = JX.DOM.find(root, 'div', 'conpherence-messages'); 255 var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); 256 JX.DOM.setContent(header_root, header); 257 JX.DOM.setContent(messages_root, messages); 258 JX.DOM.setContent(form_root, form); 259 260 markThreadLoading(false); 261 262 didRedrawThread(true); 263 } 264 265 /** 266 * This function is a wee bit tricky. Internally, we want to scroll the 267 * message window and let other stuff - notably widgets - redraw / build if 268 * necessary. Externally, we want a hook to scroll the message window 269 * - notably when the widget selector is used to invoke the message pane. 270 * The following three functions get 'er done. 271 */ 272 function didRedrawThread(build_device_widget_selector) { 273 _scrollMessageWindow(); 274 JX.Stratcom.invoke( 275 'conpherence-did-redraw-thread', 276 null, 277 { 278 widget : getDefaultWidget(), 279 threadID : _thread.selected, 280 buildDeviceWidgetSelector : build_device_widget_selector 281 }); 282 } 283 function _scrollMessageWindow() { 284 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 285 var messages_root = JX.DOM.find(root, 'div', 'conpherence-messages'); 286 messages_root.scrollTop = messages_root.scrollHeight; 287 } 288 JX.Stratcom.listen( 289 'conpherence-redraw-thread', 290 null, 291 function () { 292 _scrollMessageWindow(); 293 } 294 ); 295 296 JX.Stratcom.listen( 297 'click', 298 'conpherence-menu-click', 299 function(e) { 300 if (!e.isNormalClick()) { 301 return; 302 } 303 304 // On devices, just follow the link normally. 305 if (JX.Device.getDevice() != 'desktop') { 306 return; 307 } 308 309 e.kill(); 310 selectThread(e.getNode('conpherence-menu-click'), true); 311 }); 312 313 JX.Stratcom.listen('click', 'conpherence-edit-metadata', function (e) { 314 e.kill(); 315 var root = e.getNode('conpherence-layout'); 316 var form = JX.DOM.find(root, 'form', 'conpherence-pontificate'); 317 var data = e.getNodeData('conpherence-edit-metadata'); 318 var header = JX.DOM.find(root, 'div', 'conpherence-header-pane'); 319 var messages = JX.DOM.find(root, 'div', 'conpherence-messages'); 320 321 new JX.Workflow.newFromForm(form, data) 322 .setHandler(JX.bind(this, function(r) { 323 JX.DOM.appendContent(messages, JX.$H(r.transactions)); 324 messages.scrollTop = messages.scrollHeight; 325 326 JX.DOM.setContent( 327 header, 328 JX.$H(r.header) 329 ); 330 331 try { 332 // update the menu entry 333 JX.DOM.replace( 334 JX.$(r.conpherence_phid + '-nav-item'), 335 JX.$H(r.nav_item) 336 ); 337 JX.Stratcom.invoke( 338 'conpherence-selectthread', 339 null, 340 { id : r.conpherence_phid + '-nav-item' } 341 ); 342 } catch (ex) { 343 // Ignore; this view may not have a menu. 344 } 345 })) 346 .start(); 347 }); 348 349 var _loadingTransactionID = null; 350 JX.Stratcom.listen('click', 'show-older-messages', function(e) { 351 e.kill(); 352 var data = e.getNodeData('show-older-messages'); 353 if (data.oldest_transaction_id == _loadingTransactionID) { 354 return; 355 } 356 _loadingTransactionID = data.oldest_transaction_id; 357 var node = e.getNode('show-older-messages'); 358 JX.DOM.setContent(node, 'Loading...'); 359 JX.DOM.alterClass(node, 'conpherence-show-older-messages-loading', true); 360 361 var conf_id = _thread.selected; 362 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 363 var messages_root = JX.DOM.find(root, 'div', 'conpherence-messages'); 364 new JX.Workflow(config.baseURI + conf_id + '/', data) 365 .setHandler(function(r) { 366 JX.DOM.remove(node); 367 var messages = JX.$H(r.messages); 368 JX.DOM.prependContent( 369 messages_root, 370 JX.$H(messages)); 371 }).start(); 372 }); 373 374 /** 375 * On devices, we just show a thread list, so we don't want to automatically 376 * select or load any threads. On desktop, we automatically select the first 377 * thread, changing the _currentRole from list to thread. 378 */ 379 function onDeviceChange() { 380 var new_device = JX.Device.getDevice(); 381 if (new_device === _oldDevice) { 382 return; 383 } 384 385 if (_oldDevice === null) { 386 _oldDevice = new_device; 387 if (_currentRole == 'list') { 388 if (new_device != 'desktop') { 389 return; 390 } 391 } else { 392 loadThreads(); 393 return; 394 } 395 } 396 var update_toggled_widget = 397 new_device == 'desktop' || _oldDevice == 'desktop'; 398 _oldDevice = new_device; 399 400 if (_thread.visible !== null && update_toggled_widget) { 401 JX.Stratcom.invoke( 402 'conpherence-did-redraw-thread', 403 null, 404 { 405 widget : getDefaultWidget(), 406 threadID : _thread.selected 407 }); 408 } 409 410 if (_currentRole == 'list' && new_device == 'desktop') { 411 // this selects a thread and loads it 412 didLoadThreads(); 413 _currentRole = 'thread'; 414 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 415 JX.DOM.alterClass(root, 'conpherence-role-list', false); 416 JX.DOM.alterClass(root, 'conpherence-role-thread', true); 417 } 418 } 419 JX.Stratcom.listen('phabricator-device-change', null, onDeviceChange); 420 421 function loadThreads() { 422 markThreadsLoading(true); 423 var uri = config.baseURI + 'thread/' + config.selectedThreadID + '/'; 424 new JX.Workflow(uri) 425 .setHandler(onLoadThreadsResponse) 426 .start(); 427 } 428 429 function onLoadThreadsResponse(r) { 430 var layout = JX.$(config.layoutID); 431 var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane'); 432 JX.DOM.setContent(menu, JX.$H(r)); 433 434 config.selectedID && selectThreadByID(config.selectedID); 435 436 _thread.node.scrollIntoView(); 437 438 markThreadsLoading(false); 439 } 440 441 function didLoadThreads() { 442 // If there's no thread selected yet, select the current thread or the 443 // first thread. 444 if (!_thread.selected) { 445 if (config.selectedID) { 446 selectThreadByID(config.selectedID, true); 447 } else { 448 var layout = JX.$(config.layoutID); 449 var threads = JX.DOM.scry(layout, 'a', 'conpherence-menu-click'); 450 if (threads.length) { 451 selectThread(threads[0]); 452 } else { 453 var nothreads = JX.DOM.find(layout, 'div', 'conpherence-no-threads'); 454 nothreads.style.display = 'block'; 455 markThreadLoading(false); 456 markWidgetLoading(false); 457 } 458 } 459 } 460 } 461 462 var handleThreadScrollers = function (e) { 463 e.kill(); 464 465 var data = e.getNodeData('conpherence-menu-scroller'); 466 var scroller = e.getNode('conpherence-menu-scroller'); 467 JX.DOM.alterClass(scroller, 'loading', true); 468 JX.DOM.setContent(scroller.firstChild, 'Loading...'); 469 new JX.Workflow(scroller.href, data) 470 .setHandler( 471 JX.bind(null, threadScrollerResponse, scroller, data.direction)) 472 .start(); 473 }; 474 475 var threadScrollerResponse = function (scroller, direction, r) { 476 var html = JX.$H(r.html); 477 478 var thread_phids = r.phids; 479 var reselect_id = null; 480 // remove any threads that are in the list that we just got back 481 // in the result set; things have changed and they'll be in the 482 // right place soon 483 for (var ii = 0; ii < thread_phids.length; ii++) { 484 try { 485 var node_id = thread_phids[ii] + '-nav-item'; 486 var node = JX.$(node_id); 487 var node_data = JX.Stratcom.getData(node); 488 if (node_data.id == _thread.selected) { 489 reselect_id = node_id; 490 } 491 JX.DOM.remove(node); 492 } catch (ex) { 493 // ignore , just haven't seen this thread yet 494 } 495 } 496 497 var root = JX.DOM.find(document, 'div', 'conpherence-layout'); 498 var menu_root = JX.DOM.find(root, 'div', 'conpherence-menu-pane'); 499 var scroll_y = 0; 500 // we have to do some hyjinx in the up case to make the menu scroll to 501 // where it should 502 if (direction == 'up') { 503 var style = { 504 position: 'absolute', 505 left: '-10000px' 506 }; 507 var test_size = JX.$N('div', {style: style}, html); 508 document.body.appendChild(test_size); 509 var html_size = JX.Vector.getDim(test_size); 510 JX.DOM.remove(test_size); 511 scroll_y = html_size.y; 512 } 513 JX.DOM.replace(scroller, html); 514 menu_root.scrollTop += scroll_y; 515 516 if (reselect_id) { 517 JX.Stratcom.invoke( 518 'conpherence-selectthread', 519 null, 520 { id : reselect_id } 521 ); 522 } 523 }; 524 525 JX.Stratcom.listen( 526 ['click'], 527 'conpherence-menu-scroller', 528 handleThreadScrollers 529 ); 530 531 var onkeydownDraft = function (e) { 532 var form = e.getNode('tag:form'); 533 var data = e.getNodeData('tag:form'); 534 535 if (!data.preview) { 536 var uri = config.baseURI + 'update/' + _thread.selected + '/'; 537 data.preview = new JX.PhabricatorShapedRequest( 538 uri, 539 JX.bag, 540 function () { 541 var data = JX.DOM.convertFormToDictionary(form); 542 data.action = 'draft'; 543 return data; 544 }); 545 } 546 547 data.preview.trigger(); 548 }; 549 550 JX.Stratcom.listen( 551 ['keydown'], 552 'conpherence-pontificate', 553 onkeydownDraft); 554 555 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |