Path: blob/master/webroot/rsrc/js/application/conpherence/behavior-menu.js
12242 views
/**1* @provides javelin-behavior-conpherence-menu2* @requires javelin-behavior3* javelin-dom4* javelin-util5* javelin-stratcom6* javelin-workflow7* javelin-behavior-device8* javelin-history9* javelin-vector10* javelin-scrollbar11* phabricator-title12* phabricator-shaped-request13* conpherence-thread-manager14*/1516JX.behavior('conpherence-menu', function(config) {17/**18* State for displayed thread.19*/20var _thread = {21selected: null,22visible: null,23node: null24};2526var scrollbar = null;27var cur_theme = config.theme;2829// TODO - move more logic into the ThreadManager30var threadManager = new JX.ConpherenceThreadManager();31threadManager.setMessagesRootCallback(function() {32return scrollbar.getContentNode();33});34threadManager.setWillLoadThreadCallback(function() {35markThreadsLoading(true);36});37threadManager.setDidLoadThreadCallback(function(r) {38var header = JX.$H(r.header);39var search = JX.$H(r.search);40var messages = JX.$H(r.transactions);41var form = JX.$H(r.form);42var root = JX.DOM.find(document, 'div', 'conpherence-layout');43var header_root = JX.DOM.find(root, 'div', 'conpherence-header-pane');44var search_root = JX.DOM.find(root, 'div', 'conpherence-search-main');45var form_root = JX.DOM.find(root, 'div', 'conpherence-form');46JX.DOM.setContent(header_root, header);47JX.DOM.setContent(search_root, search);48JX.DOM.setContent(scrollbar.getContentNode(), messages);49JX.DOM.setContent(form_root, form);5051markThreadsLoading(false);5253didRedrawThread(true);54});5556threadManager.setDidUpdateThreadCallback(function(r) {57_scrollMessageWindow();58});5960threadManager.setWillSendMessageCallback(function () {61var root = JX.DOM.find(document, 'div', 'conpherence-layout');62var form_root = JX.DOM.find(root, 'div', 'conpherence-form');63markThreadLoading(true);64JX.DOM.alterClass(form_root, 'loading', true);65});6667threadManager.setDidSendMessageCallback(function (r, non_update) {68var root = JX.DOM.find(document, 'div', 'conpherence-layout');69var form_root = JX.DOM.find(root, 'div', 'conpherence-form');70var textarea = JX.DOM.find(form_root, 'textarea');71if (!non_update) {72_scrollMessageWindow();73}74markThreadLoading(false);7576setTimeout(function() { JX.DOM.focus(textarea); }, 100);77});78threadManager.start();7980/**81* Current role of this behavior. The two possible roles are to show a 'list'82* of threads or a specific 'thread'. On devices, this behavior stays in the83* 'list' role indefinitely, treating clicks normally and the next page84* loads the behavior with role = 'thread'. On desktop, this behavior85* auto-loads a thread as part of the 'list' role. As the thread loads the86* role is changed to 'thread'.87*/88var _currentRole = null;8990/**91* When _oldDevice is null the code is executing for the first time.92*/93var _oldDevice = null;9495/**96* Initializes this behavior based on all the configuration jonx and the97* result of JX.Device.getDevice();98*/99function init() {100_currentRole = config.role;101102if (_currentRole == 'thread') {103markThreadsLoading(true);104} else {105markThreadLoading(true);106}107markWidgetLoading(true);108onDeviceChange();109var root = JX.DOM.find(document, 'div', 'conpherence-layout');110var messages_root = JX.DOM.find(root, 'div', 'conpherence-message-pane');111var messages = JX.DOM.find(messages_root, 'div', 'conpherence-messages');112scrollbar = new JX.Scrollbar(messages);113scrollbar.setAsScrollFrame();114}115init();116117/**118* Selecting threads119*/120function selectThreadByID(id, update_page_data) {121var thread = JX.$(id);122selectThread(thread, update_page_data);123}124125function selectThread(node, update_page_data) {126if (_thread.node) {127JX.DOM.alterClass(_thread.node, 'phui-list-item-selected', false);128}129130JX.DOM.alterClass(node, 'phui-list-item-selected', true);131JX.DOM.alterClass(node, 'hide-unread-count', true);132133_thread.node = node;134135var data = JX.Stratcom.getData(node);136_thread.selected = data.threadID;137138if (update_page_data) {139updatePageData(data);140}141142redrawThread();143}144145function updatePageData(data) {146var uri = '/Z' + _thread.selected;147var new_theme = data.theme;148149if (new_theme != cur_theme) {150var root = JX.$('conpherence-main-layout');151JX.DOM.alterClass(root, cur_theme, false);152JX.DOM.alterClass(root, new_theme, true);153cur_theme = new_theme;154}155JX.History.replace(uri);156if (data.title) {157JX.Title.setTitle(data.title);158} else if (_thread.node) {159var threadData = JX.Stratcom.getData(_thread.node);160JX.Title.setTitle(threadData.title);161}162}163164JX.Stratcom.listen(165'conpherence-update-page-data',166null,167function (e) {168updatePageData(e.getData());169}170);171172function redrawThread() {173if (!_thread.node) {174return;175}176177if (_thread.visible == _thread.selected) {178return;179}180181var data = JX.Stratcom.getData(_thread.node);182183if (_thread.visible !== null || !config.hasThread) {184threadManager.setLoadThreadURI('/conpherence/' + data.threadID + '/');185threadManager.loadThreadByID(data.threadID);186} else if (config.hasThread) {187// we loaded the thread via the server so let the thread manager know188threadManager.setLoadedThreadID(config.selectedThreadID);189threadManager.setLoadedThreadPHID(config.selectedThreadPHID);190threadManager.setLatestTransactionID(config.latestTransactionID);191threadManager.setCanEditLoadedThread(config.canEditSelectedThread);192threadManager.cacheCurrentTransactions();193_scrollMessageWindow();194_focusTextarea();195} else {196didRedrawThread();197}198199if (_thread.visible !== null || !config.hasWidgets) {200reloadWidget(data);201}202203_thread.visible = _thread.selected;204}205206function markThreadsLoading(loading) {207var root = JX.$('conpherence-main-layout');208JX.DOM.alterClass(root, 'loading', loading);209}210211function markThreadLoading(loading) {212try {213var textarea = JX.DOM.find(form, 'textarea');214textarea.disabled = loading;215var button = JX.DOM.find(form, 'button');216button.disabled = loading;217} catch (ex) {218// haven't loaded it yet!219}220}221222function markWidgetLoading(loading) {223var root = JX.DOM.find(document, 'div', 'conpherence-layout');224var widgets_root = JX.DOM.find(root, 'div', 'conpherence-participant-pane');225226JX.DOM.alterClass(widgets_root, 'loading', loading);227}228229function reloadWidget(data) {230markWidgetLoading(true);231if (!data.widget) {232data.widget = getDefaultWidget();233}234var widget_uri = config.baseURI + 'participant/' + data.threadID + '/';235new JX.Workflow(widget_uri, {})236.setHandler(JX.bind(null, onWidgetResponse, data.threadID, data.widget))237.start();238}239JX.Stratcom.listen(240'conpherence-reload-widget',241null,242function (e) {243var data = e.getData();244if (data.threadID != _thread.selected) {245return;246}247reloadWidget(data);248}249);250251function onWidgetResponse(thread_id, widget, response) {252// we got impatient and this is no longer the right answer :/253if (_thread.selected != thread_id) {254return;255}256var root = JX.DOM.find(document, 'div', 'conpherence-layout');257var widgets_root = JX.DOM.find(root, 'div', 'conpherence-widgets-holder');258JX.DOM.setContent(widgets_root, JX.$H(response.widgets));259}260261function getDefaultWidget() {262return 'widgets-people';263}264265/**266* This function is a wee bit tricky. Internally, we want to scroll the267* message window and let other stuff - notably widgets - redraw / build if268* necessary. Externally, we want a hook to scroll the message window269* - notably when the widget selector is used to invoke the message pane.270* The following three functions get 'er done.271*/272function didRedrawThread(build_device_widget_selector) {273_scrollMessageWindow();274_focusTextarea();275JX.Stratcom.invoke(276'conpherence-did-redraw-thread',277null,278{279widget : getDefaultWidget(),280threadID : _thread.selected,281buildDeviceWidgetSelector : build_device_widget_selector282});283}284285var _firstScroll = true;286function _scrollMessageWindow() {287if (_firstScroll) {288_firstScroll = false;289290// We want to let the standard #anchor tech take over after we make sure291// we don't have to present the user with a "load older message?" dialog292if (window.location.hash) {293var hash = window.location.hash.replace(/^#/, '');294try {295JX.$('anchor-' + hash);296} catch (ex) {297var uri = '/conpherence/' +298_thread.selected + '/' + hash + '/';299threadManager.setLoadThreadURI(uri);300threadManager.loadThreadByID(_thread.selected, true);301_firstScroll = true;302return;303}304return;305}306}307scrollbar.scrollTo(scrollbar.getViewportNode().scrollHeight);308}309function _focusTextarea() {310var root = JX.DOM.find(document, 'div', 'conpherence-layout');311var form_root = JX.DOM.find(root, 'div', 'conpherence-form');312try {313var textarea = JX.DOM.find(form_root, 'textarea');314// We may have a draft so do this JS trick so we end up focused at the315// end of the draft.316var textarea_value = textarea.value;317textarea.value = '';318JX.DOM.focus(textarea);319textarea.value = textarea_value;320} catch (ex) {321// no textarea? no problem322}323}324JX.Stratcom.listen(325'conpherence-redraw-thread',326null,327function () {328_scrollMessageWindow();329}330);331332JX.Stratcom.listen(333'click',334'conpherence-menu-click',335function(e) {336if (!e.isNormalClick()) {337return;338}339340// On devices, just follow the link normally.341if (JX.Device.getDevice() != 'desktop') {342return;343}344345e.kill();346selectThread(e.getNode('conpherence-menu-click'), true);347});348349/**350* On devices, we just show a thread list, so we don't want to automatically351* select or load any threads. On desktop, we automatically select the first352* thread, changing the _currentRole from list to thread.353*/354function onDeviceChange() {355var new_device = JX.Device.getDevice();356if (new_device === _oldDevice) {357return;358}359360if (_oldDevice === null) {361_oldDevice = new_device;362if (_currentRole == 'list') {363if (new_device != 'desktop') {364return;365}366} else {367loadThreads();368return;369}370}371var update_toggled_widget =372new_device == 'desktop' || _oldDevice == 'desktop';373_oldDevice = new_device;374375if (_thread.visible !== null && update_toggled_widget) {376JX.Stratcom.invoke(377'conpherence-did-redraw-thread',378null,379{380widget : getDefaultWidget(),381threadID : _thread.selected382});383}384385if (_currentRole == 'list' && new_device == 'desktop') {386// this selects a thread and loads it387didLoadThreads();388_currentRole = 'thread';389var root = JX.DOM.find(document, 'div', 'conpherence-layout');390JX.DOM.alterClass(root, 'conpherence-role-list', false);391JX.DOM.alterClass(root, 'conpherence-role-thread', true);392}393}394JX.Stratcom.listen('phabricator-device-change', null, onDeviceChange);395396function loadThreads() {397markThreadsLoading(true);398var uri = config.baseURI + 'thread/' + config.selectedThreadID + '/';399new JX.Workflow(uri)400.setHandler(onLoadThreadsResponse)401.start();402}403404function onLoadThreadsResponse(r) {405var layout = JX.$(config.layoutID);406var menu = JX.DOM.find(layout, 'div', 'conpherence-menu-pane');407JX.DOM.setContent(menu, JX.$H(r));408409config.selectedID && selectThreadByID(config.selectedID);410411markThreadsLoading(false);412}413414function didLoadThreads() {415// If there's no thread selected yet, select the current thread or the416// first thread.417if (!_thread.selected) {418if (config.selectedID) {419selectThreadByID(config.selectedID, true);420} else {421var layout = JX.$(config.layoutID);422var threads = JX.DOM.scry(layout, 'a', 'conpherence-menu-click');423if (threads.length) {424selectThread(threads[0]);425} else {426var nothreads = JX.DOM.find(layout, 'div', 'conpherence-no-threads');427nothreads.style.display = 'block';428markThreadLoading(false);429markWidgetLoading(false);430}431}432}433}434435JX.Stratcom.listen(436['keydown'],437'conpherence-pontificate',438function (e) {439threadManager.handleDraftKeydown(e);440});441442});443444445