summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webapp/action_creators/global_actions.jsx56
-rw-r--r--webapp/components/center_panel.jsx145
-rw-r--r--webapp/components/channel_header.jsx15
-rw-r--r--webapp/components/channel_view.jsx67
-rw-r--r--webapp/components/logged_in.jsx35
-rw-r--r--webapp/components/more_channels.jsx4
-rw-r--r--webapp/components/more_direct_channels.jsx3
-rw-r--r--webapp/components/navbar.jsx5
-rw-r--r--webapp/components/new_channel_flow.jsx7
-rw-r--r--webapp/components/permalink_view.jsx109
-rw-r--r--webapp/components/popover_list_members.jsx3
-rw-r--r--webapp/components/removed_from_channel_modal.jsx4
-rw-r--r--webapp/components/rename_channel_modal.jsx5
-rw-r--r--webapp/components/search_results_item.jsx23
-rw-r--r--webapp/components/sidebar.jsx74
-rw-r--r--webapp/components/sidebar_right.jsx4
-rw-r--r--webapp/components/tutorial/tutorial_intro_screens.jsx6
-rw-r--r--webapp/components/tutorial/tutorial_view.jsx19
-rw-r--r--webapp/root.jsx60
-rw-r--r--webapp/stores/post_store.jsx2
-rw-r--r--webapp/stores/search_store.jsx2
-rw-r--r--webapp/utils/utils.jsx22
22 files changed, 366 insertions, 304 deletions
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 = <TutorialIntroScreens/>;
- createPost = null;
- } else if (this.state.showPostFocus) {
- postsContainer = <PostFocusView profiles={this.state.profiles}/>;
-
- handleClick = function clickHandler(e) {
- e.preventDefault();
- Utils.switchChannel(channel);
- };
-
- createPost = (
- <div
- id='archive-link-home'
- onClick={handleClick}
- >
- <Link to=''>
- <FormattedMessage
- id='center_panel.recent'
- defaultMessage='Click here to jump to recent messages. '
- />
- <i className='fa fa-arrow-down'></i>
- </Link>
- </div>
- );
- } else {
- postsContainer = <PostsViewContainer profiles={this.state.profiles}/>;
- createPost = (
- <div
- className='post-create__container'
- id='post-create'
- >
- <CreatePost/>
- </div>
- );
- }
-
- return (
- <div className='inner-wrap channel__wrap'>
- <div className='row header'>
- <div id='navbar'>
- <Navbar/>
- </div>
- </div>
- <div className='row main'>
- <FileUploadOverlay
- id='file_upload_overlay'
- overlayType='center'
- />
- <div
- id='app-content'
- className='app__content'
- >
- <div
- id='channel-header'
- className='channel-header'
- >
- <ChannelHeader
- user={this.state.user}
- />
- </div>
- {postsContainer}
- {createPost}
- </div>
- </div>
- </div>
- );
- }
-}
-
-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 (
- <div>
+ <div
+ id='channel-header'
+ className='channel-header'
+ >
<table className='channel-header alt'>
<tbody>
<tr>
@@ -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 (
- <CenterPanel/>
+ <div
+ id='app-content'
+ className='app__content'
+ >
+ <ChannelHeader
+ channelId={this.state.channelId}
+ />
+ <PostsViewContainer profiles={this.state.profiles}/>
+ <div
+ className='post-create__container'
+ id='post-create'
+ >
+ <CreatePost/>
+ </div>
+ </div>
);
}
}
@@ -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(
+ <div
+ key='inner-wrap'
+ className='inner-wrap channel__wrap'
+ >
+ <div className='row header'>
+ <div id='navbar'>
+ <Navbar/>
+ </div>
+ </div>
+ <div className='row main'>
+ {this.props.center}
+ </div>
+ </div>
+ );
+ }
return (
<div className='channel-view'>
<ErrorBar/>
<div className='container-fluid'>
<SidebarRight/>
<SidebarRightMenu/>
- {this.props.sidebar}
- {this.props.center}
+ {content}
<GetPostLinkModal/>
<GetTeamInviteLinkModal/>
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 (
+ <div
+ id='app-content'
+ className='app__content'
+ >
+ <ChannelHeader
+ channelId={this.state.channelId}
+ />
+ <PostFocusView profiles={this.state.profiles}/>
+ <div
+ id='archive-link-home'
+ >
+ <Link
+ to={this.state.teamURL + '/channels/' + this.state.channelName}
+ >
+ <FormattedMessage
+ id='center_panel.recent'
+ defaultMessage='Click here to jump to recent messages. '
+ />
+ <i className='fa fa-arrow-down'></i>
+ </Link>
+ </div>
+ </div>
+ );
+ }
+}
+
+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 {
</time>
</li>
<li>
- <a
- href='#'
+ <Link
+ to={Utils.getTeamURLFromAddressBar() + '/pl/' + this.props.post.id}
className='search-item__jump'
- onClick={this.handleClick}
>
<FormattedMessage
id='search_item.jump'
defaultMessage='Jump'
/>
- </a>
+ </Link>
</li>
<li>
<a
diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx
index c0d4755ed..bf51fa102 100644
--- a/webapp/components/sidebar.jsx
+++ b/webapp/components/sidebar.jsx
@@ -15,7 +15,6 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
-import * as Client from 'utils/client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -29,7 +28,7 @@ import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import loadingGif from 'images/load.gif';
import React from 'react';
-import {browserHistory} from 'react-router';
+import {browserHistory, Link} from 'react-router';
import favicon from 'images/favicon/favicon-16x16.png';
import redFavicon from 'images/favicon/redfavicon-16x16.png';
@@ -259,7 +258,7 @@ export default class Sidebar extends React.Component {
}
if (channel.id === this.state.activeId) {
- Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL));
+ browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
}
}
@@ -406,48 +405,6 @@ export default class Sidebar extends React.Component {
icon = <div className='status'><i className='fa fa-lock'></i></div>;
}
- // 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 = (
<Tooltip id='remove-dm-tooltip'>
@@ -464,12 +421,12 @@ export default class Sidebar extends React.Component {
placement='top'
overlay={removeTooltip}
>
- <span
- className='btn-close'
- data-close='true'
- >
- {'×'}
- </span>
+ <span
+ onClick={() => handleClose(channel)}
+ className='btn-close'
+ >
+ {'×'}
+ </span>
</OverlayTrigger>
);
@@ -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 (
<li
key={channel.name}
ref={channel.name}
className={linkClass}
>
- <a
+ <Link
+ to={link}
className={rowClass}
- href={'#'}
- onClick={handleClick}
>
{icon}
{status}
{channel.display_name}
{badge}
{closeButton}
- </a>
+ </Link>
{tutorialTip}
</li>
);
@@ -600,6 +563,7 @@ export default class Sidebar extends React.Component {
<div
className='sidebar--left'
id='sidebar-left'
+ key='sidebar-left'
>
<NewChannelFlow
show={showChannelModal}
diff --git a/webapp/components/sidebar_right.jsx b/webapp/components/sidebar_right.jsx
index a2e3914f3..594674929 100644
--- a/webapp/components/sidebar_right.jsx
+++ b/webapp/components/sidebar_right.jsx
@@ -29,7 +29,7 @@ export default class SidebarRight extends React.Component {
this.doStrangeThings = this.doStrangeThings.bind(this);
this.state = {
- searchVisible: !!SearchStore.getSearchResults(),
+ searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch(),
postRightVisible: !!PostStore.getSelectedPost(),
fromSearch: false,
@@ -111,7 +111,7 @@ export default class SidebarRight extends React.Component {
}
onSearchChange() {
this.setState({
- searchVisible: !!SearchStore.getSearchResults(),
+ searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch()
});
}
diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx
index 913a30483..bad426cfc 100644
--- a/webapp/components/tutorial/tutorial_intro_screens.jsx
+++ b/webapp/components/tutorial/tutorial_intro_screens.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -11,6 +10,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {browserHistory} from 'react-router';
const Preferences = Constants.Preferences;
@@ -34,7 +34,7 @@ export default class TutorialIntroScreens extends React.Component {
return;
}
- Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL));
+ browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
@@ -52,6 +52,8 @@ export default class TutorialIntroScreens extends React.Component {
UserStore.getCurrentId(),
'999'
);
+
+ browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
}
createScreen() {
switch (this.state.currentScreen) {
diff --git a/webapp/components/tutorial/tutorial_view.jsx b/webapp/components/tutorial/tutorial_view.jsx
new file mode 100644
index 000000000..d9e0ef40d
--- /dev/null
+++ b/webapp/components/tutorial/tutorial_view.jsx
@@ -0,0 +1,19 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import TutorialIntroScreens from './tutorial_intro_screens.jsx';
+
+export default class TutorialView extends React.Component {
+ render() {
+ return (
+ <div
+ id='app-content'
+ className='app__content'
+ >
+ <TutorialIntroScreens/>
+ </div>
+ );
+ }
+}
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() {
>
<Route
path=':team/channels/:channel'
- onEnter={onChannelChange}
+ onEnter={onChannelEnter}
+ onChange={onChannelChange}
components={{
sidebar: Sidebar,
center: ChannelView
@@ -215,23 +227,23 @@ function renderRootComponent() {
onEnter={onPermalinkEnter}
components={{
sidebar: Sidebar,
- center: ChannelView
+ center: PermalinkView
}}
/>
<Route
- path=':team/logout'
- onEnter={onLoggedOut}
+ path=':team/tutorial'
components={{
- sidebar: null,
- center: null
+ sidebar: Sidebar,
+ center: TutorialView
}}
/>
<Route
+ path=':team/logout'
+ onEnter={onLoggedOut}
+ />
+ <Route
path='admin_console'
- components={{
- sidebar: null,
- center: AdminConsole
- }}
+ component={AdminConsole}
/>
</Route>
<Route component={NotLoggedIn}>
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() {