From 5ce1a4368bafbd2ed50b1953658fca285cfd349b Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 24 Mar 2016 20:04:40 -0400 Subject: Refactoring center panel away. Moving tutorial to a route. Fixing a bunch of bugs. --- webapp/action_creators/global_actions.jsx | 56 ++++++-- webapp/components/center_panel.jsx | 145 --------------------- webapp/components/channel_header.jsx | 15 ++- webapp/components/channel_view.jsx | 67 +++++++++- webapp/components/logged_in.jsx | 35 ++++- webapp/components/more_channels.jsx | 4 +- webapp/components/more_direct_channels.jsx | 3 +- webapp/components/navbar.jsx | 5 +- webapp/components/new_channel_flow.jsx | 7 +- webapp/components/permalink_view.jsx | 109 ++++++++++++++++ webapp/components/popover_list_members.jsx | 3 +- webapp/components/removed_from_channel_modal.jsx | 4 +- webapp/components/rename_channel_modal.jsx | 5 +- webapp/components/search_results_item.jsx | 23 +--- webapp/components/sidebar.jsx | 74 +++-------- webapp/components/sidebar_right.jsx | 4 +- .../components/tutorial/tutorial_intro_screens.jsx | 6 +- webapp/components/tutorial/tutorial_view.jsx | 19 +++ webapp/root.jsx | 60 +++++---- webapp/stores/post_store.jsx | 2 +- webapp/stores/search_store.jsx | 2 +- webapp/utils/utils.jsx | 22 +--- 22 files changed, 366 insertions(+), 304 deletions(-) delete mode 100644 webapp/components/center_panel.jsx create mode 100644 webapp/components/permalink_view.jsx create mode 100644 webapp/components/tutorial/tutorial_view.jsx (limited to 'webapp') diff --git a/webapp/action_creators/global_actions.jsx b/webapp/action_creators/global_actions.jsx index 7322f1150..9c38d8955 100644 --- a/webapp/action_creators/global_actions.jsx +++ b/webapp/action_creators/global_actions.jsx @@ -13,23 +13,56 @@ import * as Utils from 'utils/utils.jsx'; import * as Websockets from './websocket_actions.jsx'; import * as I18n from 'i18n/i18n.jsx'; +import {browserHistory} from 'react-router'; + import en from 'i18n/en.json'; export function emitChannelClickEvent(channel) { - AsyncClient.getChannels(true); - AsyncClient.getChannelExtraInfo(channel.id); - AsyncClient.updateLastViewedAt(channel.id); - AsyncClient.getPosts(channel.id); + function userVisitedFakeChannel(chan, success, fail) { + const otherUserId = Utils.getUserIdFromChannelName(chan); + Client.createDirectChannel( + chan, + otherUserId, + (data) => { + success(data); + }, + () => { + fail(); + } + ); + } + function switchToChannel(chan) { + AsyncClient.getChannels(true); + AsyncClient.getChannelExtraInfo(chan.id); + AsyncClient.updateLastViewedAt(chan.id); + AsyncClient.getPosts(chan.id); + Client.trackPage(); + + AppDispatcher.handleViewAction({ + type: ActionTypes.CLICK_CHANNEL, + name: chan.name, + id: chan.id, + prev: ChannelStore.getCurrentId() + }); + } - AppDispatcher.handleViewAction({ - type: ActionTypes.CLICK_CHANNEL, - name: channel.name, - id: channel.id, - prev: ChannelStore.getCurrentId() - }); + if (channel.fake) { + userVisitedFakeChannel( + channel, + (data) => { + switchToChannel(data); + }, + () => { + browserHistory.push('/' + this.state.currentTeam.name); + } + ); + } else { + switchToChannel(channel); + } } export function emitPostFocusEvent(postId) { + AsyncClient.getChannels(true); Client.getPostById( postId, (data) => { @@ -39,6 +72,8 @@ export function emitPostFocusEvent(postId) { post_list: data }); + AsyncClient.getChannelExtraInfo(data.channel_id); + AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); } @@ -300,3 +335,4 @@ export function emitRemoteUserTypingEvent(channelId, userId, postParentId) { postParentId }); } + diff --git a/webapp/components/center_panel.jsx b/webapp/components/center_panel.jsx deleted file mode 100644 index 62b12c1d2..000000000 --- a/webapp/components/center_panel.jsx +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import TutorialIntroScreens from './tutorial/tutorial_intro_screens.jsx'; -import CreatePost from './create_post.jsx'; -import PostsViewContainer from './posts_view_container.jsx'; -import PostFocusView from './post_focus_view.jsx'; -import ChannelHeader from './channel_header.jsx'; -import Navbar from './navbar.jsx'; -import FileUploadOverlay from './file_upload_overlay.jsx'; - -import PreferenceStore from 'stores/preference_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; -import UserStore from 'stores/user_store.jsx'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import Constants from 'utils/constants.jsx'; -const TutorialSteps = Constants.TutorialSteps; -const Preferences = Constants.Preferences; - -import React from 'react'; -import {Link} from 'react-router'; - -export default class CenterPanel extends React.Component { - constructor(props) { - super(props); - - this.getStateFromStores = this.getStateFromStores.bind(this); - this.validState = this.validState.bind(this); - this.onStoresChange = this.onStoresChange.bind(this); - - this.state = this.getStateFromStores(); - } - getStateFromStores() { - const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); - return { - showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS, - showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS, - user: UserStore.getCurrentUser(), - channel: ChannelStore.getCurrent(), - profiles: JSON.parse(JSON.stringify(UserStore.getProfiles())) - }; - } - validState() { - return this.state.user && this.state.channel && this.state.profiles; - } - onStoresChange() { - this.setState(this.getStateFromStores()); - } - componentDidMount() { - PreferenceStore.addChangeListener(this.onStoresChange); - ChannelStore.addChangeListener(this.onStoresChange); - UserStore.addChangeListener(this.onStoresChange); - } - componentWillUnmount() { - PreferenceStore.removeChangeListener(this.onStoresChange); - ChannelStore.removeChangeListener(this.onStoresChange); - UserStore.removeChangeListener(this.onStoresChange); - } - render() { - if (!this.validState()) { - return null; - } - const channel = this.state.channel; - var handleClick = null; - let postsContainer; - let createPost; - if (this.state.showTutorialScreens) { - postsContainer = ; - createPost = null; - } else if (this.state.showPostFocus) { - postsContainer = ; - - handleClick = function clickHandler(e) { - e.preventDefault(); - Utils.switchChannel(channel); - }; - - createPost = ( - - ); - } else { - postsContainer = ; - createPost = ( -
- -
- ); - } - - return ( -
-
- -
-
- -
-
- -
- {postsContainer} - {createPost} -
-
-
- ); - } -} - -CenterPanel.defaultProps = { -}; - -CenterPanel.propTypes = { -}; diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 369fa2dbb..6bb466c3e 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -26,6 +26,7 @@ import * as Utils from 'utils/utils.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Client from 'utils/client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; @@ -53,11 +54,11 @@ export default class ChannelHeader extends React.Component { this.state = state; } getStateFromStores() { - const extraInfo = ChannelStore.getCurrentExtraInfo(); + const extraInfo = ChannelStore.getExtraInfo(this.props.channelId); return { - channel: ChannelStore.getCurrent(), - memberChannel: ChannelStore.getCurrentMember(), + channel: ChannelStore.get(this.props.channelId), + memberChannel: ChannelStore.getMember(this.props.channelId), users: extraInfo.members, userCount: extraInfo.member_count, searchVisible: SearchStore.getSearchResults() !== null, @@ -105,7 +106,7 @@ export default class ChannelHeader extends React.Component { }); const townsquare = ChannelStore.getByName('town-square'); - Utils.switchChannel(townsquare); + GlobalActions.emitChannelClickEvent(townsquare); }, (err) => { AsyncClient.dispatchError(err, 'handleLeave'); @@ -433,7 +434,10 @@ export default class ChannelHeader extends React.Component { } return ( -
+
@@ -518,4 +522,5 @@ export default class ChannelHeader extends React.Component { } ChannelHeader.propTypes = { + channelId: React.PropTypes.string.isRequired }; diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx index 34e1666d0..54d796ac1 100644 --- a/webapp/components/channel_view.jsx +++ b/webapp/components/channel_view.jsx @@ -1,14 +1,73 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import CenterPanel from 'components/center_panel.jsx'; - import React from 'react'; +import ChannelHeader from 'components/channel_header.jsx'; +import PostsViewContainer from 'components/posts_view_container.jsx'; +import CreatePost from 'components/create_post.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + export default class ChannelView extends React.Component { + constructor(props) { + super(props); + + this.getStateFromStores = this.getStateFromStores.bind(this); + this.isStateValid = this.isStateValid.bind(this); + this.updateState = this.updateState.bind(this); + + this.state = this.getStateFromStores(props); + } + getStateFromStores(props) { + const channel = ChannelStore.getByName(props.params.channel); + const channelId = channel ? channel.id : ''; + const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); + return { + channelId, + profiles + }; + } + isStateValid() { + return this.state.channelId !== '' && this.state.profiles; + } + updateState() { + this.setState(this.getStateFromStores(this.props)); + } + componentDidMount() { + ChannelStore.addChangeListener(this.updateState); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.updateState); + } + componentWillReceiveProps(nextProps) { + this.setState(this.getStateFromStores(nextProps)); + } + shouldComponentUpdate(nextProps, nextState) { + if (nextState.channelId !== this.state.channelId) { + return true; + } + + return false; + } render() { return ( - +
+ + +
+ +
+
); } } @@ -16,5 +75,5 @@ ChannelView.defaultProps = { }; ChannelView.propTypes = { - params: React.PropTypes.object + params: React.PropTypes.object.isRequired }; diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index c6f7b50b1..53db501bf 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -10,6 +10,8 @@ import BrowserStore from 'stores/browser_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; import ErrorBar from 'components/error_bar.jsx'; import * as Websockets from 'action_creators/websocket_actions.jsx'; @@ -17,6 +19,7 @@ import {browserHistory} from 'react-router'; import SidebarRight from 'components/sidebar_right.jsx'; import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; +import Navbar from 'components/navbar.jsx'; // Modals import GetPostLinkModal from 'components/get_post_link_modal.jsx'; @@ -66,6 +69,12 @@ export default class LoggedIn extends React.Component { Utils.applyTheme(Constants.THEMES.default); } } + + // Go to tutorial if we are first arrivign + const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); + if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { + browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); + } } componentWillMount() { // Emit view action @@ -186,14 +195,36 @@ export default class LoggedIn extends React.Component { $(window).off('keydown.preventBackspace'); } render() { + let content = []; + if (this.props.children) { + content = this.props.children; + } else { + content.push( + this.props.sidebar + ); + content.push( +
+
+ +
+
+ {this.props.center} +
+
+ ); + } return (
- {this.props.sidebar} - {this.props.center} + {content} diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx index d0eeec1ef..811bb8101 100644 --- a/webapp/components/more_channels.jsx +++ b/webapp/components/more_channels.jsx @@ -9,6 +9,7 @@ import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import LoadingScreen from './loading_screen.jsx'; import NewChannelFlow from './new_channel_flow.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -64,8 +65,7 @@ export default class MoreChannels extends React.Component { client.joinChannel(channel.id, () => { $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); - AsyncClient.getChannel(channel.id); - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); this.setState({joiningChannel: -1}); }, (err) => { diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx index d1446059d..feab8c9db 100644 --- a/webapp/components/more_direct_channels.jsx +++ b/webapp/components/more_direct_channels.jsx @@ -5,6 +5,7 @@ import {Modal} from 'react-bootstrap'; import FilteredUserList from './filtered_user_list.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; import SpinnerButton from 'components/spinner_button.jsx'; @@ -68,7 +69,7 @@ export default class MoreDirectChannels extends React.Component { Utils.openDirectChannelToUser( teammate, (channel) => { - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); this.setState({loadingDMChannel: -1}); this.handleHide(); }, diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index e58e142d0..5afd7e683 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -45,6 +45,7 @@ export default class Navbar extends React.Component { this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); this.showRenameChannelModal = this.showRenameChannelModal.bind(this); this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this); + this.isStateValid = this.isStateValid.bind(this); this.createCollapseButtons = this.createCollapseButtons.bind(this); this.createDropdown = this.createDropdown.bind(this); @@ -64,7 +65,7 @@ export default class Navbar extends React.Component { currentUser: UserStore.getCurrentUser() }; } - stateValid() { + isStateValid() { return this.state.channel && this.state.member && this.state.users && this.state.currentUser; } componentDidMount() { @@ -422,7 +423,7 @@ export default class Navbar extends React.Component { return buttons; } render() { - if (!this.stateValid()) { + if (!this.isStateValid()) { return null; } diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx index 30035ee5d..8c66ef3ce 100644 --- a/webapp/components/new_channel_flow.jsx +++ b/webapp/components/new_channel_flow.jsx @@ -2,9 +2,9 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import * as Client from 'utils/client.jsx'; import UserStore from 'stores/user_store.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import NewChannelModal from './new_channel_modal.jsx'; import ChangeURLModal from './change_url_modal.jsx'; @@ -110,8 +110,7 @@ class NewChannelFlow extends React.Component { Client.createChannel(channel, (data) => { this.props.onModalDismissed(); - AsyncClient.getChannel(data.id); - Utils.switchChannel(data); + GlobalActions.emitChannelClickEvent(data); }, (err) => { if (err.id === 'model.channel.is_valid.2_or_more.app_error') { @@ -247,4 +246,4 @@ NewChannelFlow.propTypes = { onModalDismissed: React.PropTypes.func.isRequired }; -export default injectIntl(NewChannelFlow); \ No newline at end of file +export default injectIntl(NewChannelFlow); diff --git a/webapp/components/permalink_view.jsx b/webapp/components/permalink_view.jsx new file mode 100644 index 000000000..8e49019ee --- /dev/null +++ b/webapp/components/permalink_view.jsx @@ -0,0 +1,109 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import ChannelHeader from 'components/channel_header.jsx'; +import PostFocusView from 'components/post_focus_view.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; + +import {Link} from 'react-router'; +import {FormattedMessage} from 'react-intl'; + +export default class PermalinkView extends React.Component { + constructor(props) { + super(props); + + this.getStateFromStores = this.getStateFromStores.bind(this); + this.isStateValid = this.isStateValid.bind(this); + this.updateState = this.updateState.bind(this); + + this.state = this.getStateFromStores(props); + } + getStateFromStores(props) { + const postId = props.params.postid; + const channel = ChannelStore.getCurrent(); + const channelId = channel ? channel.id : ''; + const channelName = channel ? channel.name : ''; + const teamURL = TeamStore.getCurrentTeamUrl(); + const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); + return { + channelId, + channelName, + profiles, + teamURL, + postId + }; + } + isStateValid() { + return this.state.channelId !== '' && this.state.profiles && this.state.teamURL; + } + updateState() { + this.setState(this.getStateFromStores(this.props)); + } + componentDidMount() { + ChannelStore.addChangeListener(this.updateState); + TeamStore.addChangeListener(this.updateState); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.updateState); + TeamStore.removeChangeListener(this.updateState); + } + componentWillReceiveProps(nextProps) { + this.setState(this.getStateFromStores(nextProps)); + } + shouldComponentUpdate(nextProps, nextState) { + if (nextState.postId !== this.state.postId) { + return true; + } + + if (nextState.channelId !== this.state.channelId) { + return true; + } + + if (nextState.teamURL !== this.state.teamURL) { + return true; + } + + return false; + } + render() { + if (!this.isStateValid()) { + return null; + } + return ( +
+ + + +
+ ); + } +} + +PermalinkView.defaultProps = { +}; + +PermalinkView.propTypes = { + params: React.PropTypes.object.isRequired +}; diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx index 819c7f590..cd583e4c3 100644 --- a/webapp/components/popover_list_members.jsx +++ b/webapp/components/popover_list_members.jsx @@ -6,6 +6,7 @@ import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; import {Popover, Overlay} from 'react-bootstrap'; import * as Utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import ChannelStore from 'stores/channel_store.jsx'; @@ -36,7 +37,7 @@ export default class PopoverListMembers extends React.Component { Utils.openDirectChannelToUser( teammate, (channel, channelAlreadyExisted) => { - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); if (channelAlreadyExisted) { this.closePopover(); } diff --git a/webapp/components/removed_from_channel_modal.jsx b/webapp/components/removed_from_channel_modal.jsx index cdd51bd6e..45018ac99 100644 --- a/webapp/components/removed_from_channel_modal.jsx +++ b/webapp/components/removed_from_channel_modal.jsx @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; -import * as utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -33,7 +33,7 @@ export default class RemovedFromChannelModal extends React.Component { } var townSquare = ChannelStore.getByName('town-square'); - setTimeout(() => utils.switchChannel(townSquare), 1); + setTimeout(() => GlobalActions.emitChannelClickEvent(townSquare), 1); this.setState(newState); } diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx index 72828984c..ced3c2d2b 100644 --- a/webapp/components/rename_channel_modal.jsx +++ b/webapp/components/rename_channel_modal.jsx @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as Client from 'utils/client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; @@ -165,8 +165,7 @@ export default class RenameChannelModal extends React.Component { Client.updateChannel( channel, () => { - AsyncClient.getChannel(channel.id); - Utils.updateAddressBar(channel.name); + GlobalActions.emitChannelClickEvent(channel); this.handleHide(); }, diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx index 35769d06b..219aa7093 100644 --- a/webapp/components/search_results_item.jsx +++ b/webapp/components/search_results_item.jsx @@ -1,37 +1,25 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; import UserProfile from './user_profile.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; +import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage, FormattedDate} from 'react-intl'; - import React from 'react'; +import {Link} from 'react-router'; export default class SearchResultsItem extends React.Component { constructor(props) { super(props); - this.handleClick = this.handleClick.bind(this); this.handleFocusRHSClick = this.handleFocusRHSClick.bind(this); } - handleClick(e) { - e.preventDefault(); - - GlobalActions.emitPostFocusEvent(this.props.post.id); - - if ($(window).width() < 768) { - $('.sidebar--right').removeClass('move--left'); - $('.inner-wrap').removeClass('move--left'); - } - } - handleFocusRHSClick(e) { e.preventDefault(); GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch); @@ -99,16 +87,15 @@ export default class SearchResultsItem extends React.Component {
  • - - +
  • ; } - // set up click handler to switch channels (or create a new channel for non-existant ones) - var handleClick = null; - - if (!channel.fake) { - handleClick = function clickHandler(e) { - if (e.target.attributes.getNamedItem('data-close')) { - handleClose(channel); - } else { - Utils.switchChannel(channel); - } - - e.preventDefault(); - }; - } else if (channel.fake) { - // It's a direct message channel that doesn't exist yet so let's create it now - var otherUserId = Utils.getUserIdFromChannelName(channel); - - if (this.state.loadingDMChannel === -1) { - handleClick = function clickHandler(e) { - e.preventDefault(); - - if (e.target.attributes.getNamedItem('data-close')) { - handleClose(channel); - } else { - this.setState({loadingDMChannel: index}); - - Client.createDirectChannel(channel, otherUserId, - (data) => { - this.setState({loadingDMChannel: -1}); - AsyncClient.getChannel(data.id); - Utils.switchChannel(data); - }, - () => { - this.setState({loadingDMChannel: -1}); - browserHistory('/' + this.state.currentTeam.name); - } - ); - } - }.bind(this); - } - } - let closeButton = null; const removeTooltip = ( @@ -464,12 +421,12 @@ export default class Sidebar extends React.Component { placement='top' overlay={removeTooltip} > - - {'×'} - + handleClose(channel)} + className='btn-close' + > + {'×'} + ); @@ -481,23 +438,29 @@ export default class Sidebar extends React.Component { tutorialTip = this.createTutorialTip(); } + let link = ''; + if (channel.fake) { + link = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name + '?fakechannel=' + encodeURIComponent(JSON.stringify(channel)); + } else { + link = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; + } + return (
  • - {icon} {status} {channel.display_name} {badge} {closeButton} - + {tutorialTip}
  • ); @@ -600,6 +563,7 @@ export default class Sidebar extends React.Component { + ); + } +} diff --git a/webapp/root.jsx b/webapp/root.jsx index a5771c3b1..ce59a95c9 100644 --- a/webapp/root.jsx +++ b/webapp/root.jsx @@ -19,6 +19,7 @@ import NeedsTeam from 'components/needs_team.jsx'; import PasswordResetSendLink from 'components/password_reset_send_link.jsx'; import PasswordResetForm from 'components/password_reset_form.jsx'; import ChannelView from 'components/channel_view.jsx'; +import PermalinkView from 'components/permalink_view.jsx'; import Sidebar from 'components/sidebar.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; @@ -34,6 +35,7 @@ import SignupUserComplete from 'components/signup_user_complete.jsx'; import ShouldVerifyEmail from 'components/should_verify_email.jsx'; import DoVerifyEmail from 'components/do_verify_email.jsx'; import AdminConsole from 'components/admin_console/admin_controller.jsx'; +import TutorialView from 'components/tutorial/tutorial_view.jsx'; import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx'; import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx'; @@ -133,20 +135,9 @@ function preLoggedIn(nextState, replace, callback) { const d2 = AsyncClient.getChannels(); - $.when(d1, d2).done(() => callback()); -} - -function onChannelChange(nextState) { - const channelName = nextState.params.channel; - - // Make sure we have all the channels - AsyncClient.getChannels(true); - - // Get our channel's ID - const channel = ChannelStore.getByName(channelName); - - // User clicked channel - GlobalActions.emitChannelClickEvent(channel); + $.when(d1, d2).done(() => { + callback(); + }); } function onRootEnter(nextState, replace, callback) { @@ -172,6 +163,26 @@ function onPermalinkEnter(nextState) { GlobalActions.emitPostFocusEvent(postId); } +function onChannelEnter(nextState) { + doChannelChange(nextState); +} + +function onChannelChange(prevState, nextState) { + if (prevState.params.channel !== nextState.params.channel) { + doChannelChange(nextState); + } +} + +function doChannelChange(state) { + let channel; + if (state.location.query.fakechannel) { + channel = JSON.parse(state.location.query.fakechannel); + } else { + channel = ChannelStore.getByName(state.params.channel); + } + GlobalActions.emitChannelClickEvent(channel); +} + function onLoggedOut(nextState) { const teamName = nextState.params.team; Client.logout( @@ -204,7 +215,8 @@ function renderRootComponent() { > + diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx index 903085760..757599b0f 100644 --- a/webapp/stores/post_store.jsx +++ b/webapp/stores/post_store.jsx @@ -406,7 +406,7 @@ class PostStoreClass extends EventEmitter { let posts; let pendingPosts; for (const k in this.postsInfo) { - if (this.postsInfo[k].postList.posts.hasOwnProperty(this.selectedPostId)) { + if (this.postsInfo[k].postList && this.postsInfo[k].postList.posts.hasOwnProperty(this.selectedPostId)) { posts = this.postsInfo[k].postList.posts; if (this.postsInfo[k].pendingPosts != null) { pendingPosts = this.postsInfo[k].pendingPosts.posts; diff --git a/webapp/stores/search_store.jsx b/webapp/stores/search_store.jsx index acaa9e52f..dc08ca3a6 100644 --- a/webapp/stores/search_store.jsx +++ b/webapp/stores/search_store.jsx @@ -16,7 +16,7 @@ class SearchStoreClass extends EventEmitter { constructor() { super(); - this.searchResults = {}; + this.searchResults = null; this.isMentionSearch = false; this.searchTerm = ''; } diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx index ac12edb82..dcf0cf740 100644 --- a/webapp/utils/utils.jsx +++ b/webapp/utils/utils.jsx @@ -169,7 +169,7 @@ export function notifyMe(title, body, channel) { notification.onclick = () => { window.focus(); if (channel) { - switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); } else { browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square'); } @@ -957,24 +957,6 @@ export function isValidUsername(name) { return error; } -export function updateAddressBar(channelName) { - const teamURL = TeamStore.getCurrentTeamUrl(); - history.replaceState('data', '', teamURL + '/channels/' + channelName); -} - -export function switchChannel(channel) { - GlobalActions.emitChannelClickEvent(channel); - - updateAddressBar(channel.name); - - $('.inner-wrap').removeClass('move--right'); - $('.sidebar--left').removeClass('move--right'); - - client.trackPage(); - - return false; -} - export function isMobile() { return screen.width <= 768; } @@ -1253,7 +1235,7 @@ export function importSlack(file, success, error) { } export function getTeamURLFromAddressBar() { - return window.location.href.split('/channels')[0]; + return window.location.origin + '/' + window.location.pathname.split('/')[1]; } export function getShortenedTeamURL() { -- cgit v1.2.3-1-g7c22