Refactoring center panel away. Moving tutorial to a route. Fixing a

bunch of bugs.
Этот коммит содержится в:
Christopher Speller
2016-03-24 20:04:40 -04:00
родитель bf636404d2
Коммит 5ce1a4368b
22 изменённых файлов: 366 добавлений и 304 удалений

Просмотреть файл

@@ -13,23 +13,56 @@ import * as Utils from 'utils/utils.jsx';
import * as Websockets from './websocket_actions.jsx'; import * as Websockets from './websocket_actions.jsx';
import * as I18n from 'i18n/i18n.jsx'; import * as I18n from 'i18n/i18n.jsx';
import {browserHistory} from 'react-router';
import en from 'i18n/en.json'; import en from 'i18n/en.json';
export function emitChannelClickEvent(channel) { export function emitChannelClickEvent(channel) {
AsyncClient.getChannels(true); function userVisitedFakeChannel(chan, success, fail) {
AsyncClient.getChannelExtraInfo(channel.id); const otherUserId = Utils.getUserIdFromChannelName(chan);
AsyncClient.updateLastViewedAt(channel.id); Client.createDirectChannel(
AsyncClient.getPosts(channel.id); 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({ AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL, type: ActionTypes.CLICK_CHANNEL,
name: channel.name, name: chan.name,
id: channel.id, id: chan.id,
prev: ChannelStore.getCurrentId() prev: ChannelStore.getCurrentId()
}); });
}
if (channel.fake) {
userVisitedFakeChannel(
channel,
(data) => {
switchToChannel(data);
},
() => {
browserHistory.push('/' + this.state.currentTeam.name);
}
);
} else {
switchToChannel(channel);
}
} }
export function emitPostFocusEvent(postId) { export function emitPostFocusEvent(postId) {
AsyncClient.getChannels(true);
Client.getPostById( Client.getPostById(
postId, postId,
(data) => { (data) => {
@@ -39,6 +72,8 @@ export function emitPostFocusEvent(postId) {
post_list: data post_list: data
}); });
AsyncClient.getChannelExtraInfo(data.channel_id);
AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS);
AsyncClient.getPostsAfter(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 postParentId
}); });
} }

Просмотреть файл

@@ -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 = {
};

Просмотреть файл

@@ -26,6 +26,7 @@ import * as Utils from 'utils/utils.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx';
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import * as Client from 'utils/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 Constants from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
@@ -53,11 +54,11 @@ export default class ChannelHeader extends React.Component {
this.state = state; this.state = state;
} }
getStateFromStores() { getStateFromStores() {
const extraInfo = ChannelStore.getCurrentExtraInfo(); const extraInfo = ChannelStore.getExtraInfo(this.props.channelId);
return { return {
channel: ChannelStore.getCurrent(), channel: ChannelStore.get(this.props.channelId),
memberChannel: ChannelStore.getCurrentMember(), memberChannel: ChannelStore.getMember(this.props.channelId),
users: extraInfo.members, users: extraInfo.members,
userCount: extraInfo.member_count, userCount: extraInfo.member_count,
searchVisible: SearchStore.getSearchResults() !== null, searchVisible: SearchStore.getSearchResults() !== null,
@@ -105,7 +106,7 @@ export default class ChannelHeader extends React.Component {
}); });
const townsquare = ChannelStore.getByName('town-square'); const townsquare = ChannelStore.getByName('town-square');
Utils.switchChannel(townsquare); GlobalActions.emitChannelClickEvent(townsquare);
}, },
(err) => { (err) => {
AsyncClient.dispatchError(err, 'handleLeave'); AsyncClient.dispatchError(err, 'handleLeave');
@@ -433,7 +434,10 @@ export default class ChannelHeader extends React.Component {
} }
return ( return (
<div> <div
id='channel-header'
className='channel-header'
>
<table className='channel-header alt'> <table className='channel-header alt'>
<tbody> <tbody>
<tr> <tr>
@@ -518,4 +522,5 @@ export default class ChannelHeader extends React.Component {
} }
ChannelHeader.propTypes = { ChannelHeader.propTypes = {
channelId: React.PropTypes.string.isRequired
}; };

Просмотреть файл

@@ -1,14 +1,73 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import CenterPanel from 'components/center_panel.jsx';
import React from 'react'; 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 { 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() { render() {
return ( 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 = { ChannelView.propTypes = {
params: React.PropTypes.object params: React.PropTypes.object.isRequired
}; };

Просмотреть файл

@@ -10,6 +10,8 @@ import BrowserStore from 'stores/browser_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
const TutorialSteps = Constants.TutorialSteps;
const Preferences = Constants.Preferences;
import ErrorBar from 'components/error_bar.jsx'; import ErrorBar from 'components/error_bar.jsx';
import * as Websockets from 'action_creators/websocket_actions.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 SidebarRight from 'components/sidebar_right.jsx';
import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; import SidebarRightMenu from 'components/sidebar_right_menu.jsx';
import Navbar from 'components/navbar.jsx';
// Modals // Modals
import GetPostLinkModal from 'components/get_post_link_modal.jsx'; 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); 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() { componentWillMount() {
// Emit view action // Emit view action
@@ -186,14 +195,36 @@ export default class LoggedIn extends React.Component {
$(window).off('keydown.preventBackspace'); $(window).off('keydown.preventBackspace');
} }
render() { 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 ( return (
<div className='channel-view'> <div className='channel-view'>
<ErrorBar/> <ErrorBar/>
<div className='container-fluid'> <div className='container-fluid'>
<SidebarRight/> <SidebarRight/>
<SidebarRightMenu/> <SidebarRightMenu/>
{this.props.sidebar} {content}
{this.props.center}
<GetPostLinkModal/> <GetPostLinkModal/>
<GetTeamInviteLinkModal/> <GetTeamInviteLinkModal/>

Просмотреть файл

@@ -9,6 +9,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
import LoadingScreen from './loading_screen.jsx'; import LoadingScreen from './loading_screen.jsx';
import NewChannelFlow from './new_channel_flow.jsx'; import NewChannelFlow from './new_channel_flow.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
@@ -64,8 +65,7 @@ export default class MoreChannels extends React.Component {
client.joinChannel(channel.id, client.joinChannel(channel.id,
() => { () => {
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id); GlobalActions.emitChannelClickEvent(channel);
Utils.switchChannel(channel);
this.setState({joiningChannel: -1}); this.setState({joiningChannel: -1});
}, },
(err) => { (err) => {

Просмотреть файл

@@ -5,6 +5,7 @@ import {Modal} from 'react-bootstrap';
import FilteredUserList from './filtered_user_list.jsx'; import FilteredUserList from './filtered_user_list.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import SpinnerButton from 'components/spinner_button.jsx'; import SpinnerButton from 'components/spinner_button.jsx';
@@ -68,7 +69,7 @@ export default class MoreDirectChannels extends React.Component {
Utils.openDirectChannelToUser( Utils.openDirectChannelToUser(
teammate, teammate,
(channel) => { (channel) => {
Utils.switchChannel(channel); GlobalActions.emitChannelClickEvent(channel);
this.setState({loadingDMChannel: -1}); this.setState({loadingDMChannel: -1});
this.handleHide(); this.handleHide();
}, },

Просмотреть файл

@@ -45,6 +45,7 @@ export default class Navbar extends React.Component {
this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this);
this.showRenameChannelModal = this.showRenameChannelModal.bind(this); this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this); this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
this.isStateValid = this.isStateValid.bind(this);
this.createCollapseButtons = this.createCollapseButtons.bind(this); this.createCollapseButtons = this.createCollapseButtons.bind(this);
this.createDropdown = this.createDropdown.bind(this); this.createDropdown = this.createDropdown.bind(this);
@@ -64,7 +65,7 @@ export default class Navbar extends React.Component {
currentUser: UserStore.getCurrentUser() currentUser: UserStore.getCurrentUser()
}; };
} }
stateValid() { isStateValid() {
return this.state.channel && this.state.member && this.state.users && this.state.currentUser; return this.state.channel && this.state.member && this.state.users && this.state.currentUser;
} }
componentDidMount() { componentDidMount() {
@@ -422,7 +423,7 @@ export default class Navbar extends React.Component {
return buttons; return buttons;
} }
render() { render() {
if (!this.stateValid()) { if (!this.isStateValid()) {
return null; return null;
} }

Просмотреть файл

@@ -2,9 +2,9 @@
// See License.txt for license information. // See License.txt for license information.
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Client from 'utils/client.jsx'; import * as Client from 'utils/client.jsx';
import UserStore from 'stores/user_store.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 NewChannelModal from './new_channel_modal.jsx';
import ChangeURLModal from './change_url_modal.jsx'; import ChangeURLModal from './change_url_modal.jsx';
@@ -110,8 +110,7 @@ class NewChannelFlow extends React.Component {
Client.createChannel(channel, Client.createChannel(channel,
(data) => { (data) => {
this.props.onModalDismissed(); this.props.onModalDismissed();
AsyncClient.getChannel(data.id); GlobalActions.emitChannelClickEvent(data);
Utils.switchChannel(data);
}, },
(err) => { (err) => {
if (err.id === 'model.channel.is_valid.2_or_more.app_error') { if (err.id === 'model.channel.is_valid.2_or_more.app_error') {
@@ -247,4 +246,4 @@ NewChannelFlow.propTypes = {
onModalDismissed: React.PropTypes.func.isRequired onModalDismissed: React.PropTypes.func.isRequired
}; };
export default injectIntl(NewChannelFlow); export default injectIntl(NewChannelFlow);

109
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
};

Просмотреть файл

@@ -6,6 +6,7 @@ import $ from 'jquery';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import {Popover, Overlay} from 'react-bootstrap'; import {Popover, Overlay} from 'react-bootstrap';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
@@ -36,7 +37,7 @@ export default class PopoverListMembers extends React.Component {
Utils.openDirectChannelToUser( Utils.openDirectChannelToUser(
teammate, teammate,
(channel, channelAlreadyExisted) => { (channel, channelAlreadyExisted) => {
Utils.switchChannel(channel); GlobalActions.emitChannelClickEvent(channel);
if (channelAlreadyExisted) { if (channelAlreadyExisted) {
this.closePopover(); this.closePopover();
} }

Просмотреть файл

@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
import ChannelStore from 'stores/channel_store.jsx'; import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import BrowserStore from 'stores/browser_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'; import {FormattedMessage} from 'react-intl';
@@ -33,7 +33,7 @@ export default class RemovedFromChannelModal extends React.Component {
} }
var townSquare = ChannelStore.getByName('town-square'); var townSquare = ChannelStore.getByName('town-square');
setTimeout(() => utils.switchChannel(townSquare), 1); setTimeout(() => GlobalActions.emitChannelClickEvent(townSquare), 1);
this.setState(newState); this.setState(newState);
} }

Просмотреть файл

@@ -4,7 +4,7 @@
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import * as Client from 'utils/client.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 Constants from 'utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
@@ -165,8 +165,7 @@ export default class RenameChannelModal extends React.Component {
Client.updateChannel( Client.updateChannel(
channel, channel,
() => { () => {
AsyncClient.getChannel(channel.id); GlobalActions.emitChannelClickEvent(channel);
Utils.updateAddressBar(channel.name);
this.handleHide(); this.handleHide();
}, },

Просмотреть файл

@@ -1,37 +1,25 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import $ from 'jquery';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import UserProfile from './user_profile.jsx'; import UserProfile from './user_profile.jsx';
import * as GlobalActions from 'action_creators/global_actions.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedDate} from 'react-intl'; import {FormattedMessage, FormattedDate} from 'react-intl';
import React from 'react'; import React from 'react';
import {Link} from 'react-router';
export default class SearchResultsItem extends React.Component { export default class SearchResultsItem extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleClick = this.handleClick.bind(this);
this.handleFocusRHSClick = this.handleFocusRHSClick.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) { handleFocusRHSClick(e) {
e.preventDefault(); e.preventDefault();
GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch); GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch);
@@ -99,16 +87,15 @@ export default class SearchResultsItem extends React.Component {
</time> </time>
</li> </li>
<li> <li>
<a <Link
href='#' to={Utils.getTeamURLFromAddressBar() + '/pl/' + this.props.post.id}
className='search-item__jump' className='search-item__jump'
onClick={this.handleClick}
> >
<FormattedMessage <FormattedMessage
id='search_item.jump' id='search_item.jump'
defaultMessage='Jump' defaultMessage='Jump'
/> />
</a> </Link>
</li> </li>
<li> <li>
<a <a

Просмотреть файл

@@ -15,7 +15,6 @@ import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import * as Client from 'utils/client.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx'; import Constants from 'utils/constants.jsx';
@@ -29,7 +28,7 @@ import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import loadingGif from 'images/load.gif'; import loadingGif from 'images/load.gif';
import React from 'react'; import React from 'react';
import {browserHistory} from 'react-router'; import {browserHistory, Link} from 'react-router';
import favicon from 'images/favicon/favicon-16x16.png'; import favicon from 'images/favicon/favicon-16x16.png';
import redFavicon from 'images/favicon/redfavicon-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) { 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>; 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; let closeButton = null;
const removeTooltip = ( const removeTooltip = (
<Tooltip id='remove-dm-tooltip'> <Tooltip id='remove-dm-tooltip'>
@@ -464,12 +421,12 @@ export default class Sidebar extends React.Component {
placement='top' placement='top'
overlay={removeTooltip} overlay={removeTooltip}
> >
<span <span
className='btn-close' onClick={() => handleClose(channel)}
data-close='true' className='btn-close'
> >
{'×'} {'×'}
</span> </span>
</OverlayTrigger> </OverlayTrigger>
); );
@@ -481,23 +438,29 @@ export default class Sidebar extends React.Component {
tutorialTip = this.createTutorialTip(); 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 ( return (
<li <li
key={channel.name} key={channel.name}
ref={channel.name} ref={channel.name}
className={linkClass} className={linkClass}
> >
<a <Link
to={link}
className={rowClass} className={rowClass}
href={'#'}
onClick={handleClick}
> >
{icon} {icon}
{status} {status}
{channel.display_name} {channel.display_name}
{badge} {badge}
{closeButton} {closeButton}
</a> </Link>
{tutorialTip} {tutorialTip}
</li> </li>
); );
@@ -600,6 +563,7 @@ export default class Sidebar extends React.Component {
<div <div
className='sidebar--left' className='sidebar--left'
id='sidebar-left' id='sidebar-left'
key='sidebar-left'
> >
<NewChannelFlow <NewChannelFlow
show={showChannelModal} show={showChannelModal}

Просмотреть файл

@@ -29,7 +29,7 @@ export default class SidebarRight extends React.Component {
this.doStrangeThings = this.doStrangeThings.bind(this); this.doStrangeThings = this.doStrangeThings.bind(this);
this.state = { this.state = {
searchVisible: !!SearchStore.getSearchResults(), searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch(), isMentionSearch: SearchStore.getIsMentionSearch(),
postRightVisible: !!PostStore.getSelectedPost(), postRightVisible: !!PostStore.getSelectedPost(),
fromSearch: false, fromSearch: false,
@@ -111,7 +111,7 @@ export default class SidebarRight extends React.Component {
} }
onSearchChange() { onSearchChange() {
this.setState({ this.setState({
searchVisible: !!SearchStore.getSearchResults(), searchVisible: SearchStore.getSearchResults() !== null,
isMentionSearch: SearchStore.getIsMentionSearch() isMentionSearch: SearchStore.getIsMentionSearch()
}); });
} }

Просмотреть файл

@@ -2,7 +2,6 @@
// See License.txt for license information. // See License.txt for license information.
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx'; import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import * as Utils from 'utils/utils.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 Constants from 'utils/constants.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import {browserHistory} from 'react-router';
const Preferences = Constants.Preferences; const Preferences = Constants.Preferences;
@@ -34,7 +34,7 @@ export default class TutorialIntroScreens extends React.Component {
return; 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); const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0);
@@ -52,6 +52,8 @@ export default class TutorialIntroScreens extends React.Component {
UserStore.getCurrentId(), UserStore.getCurrentId(),
'999' '999'
); );
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
} }
createScreen() { createScreen() {
switch (this.state.currentScreen) { switch (this.state.currentScreen) {

Просмотреть файл

@@ -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>
);
}
}

Просмотреть файл

@@ -19,6 +19,7 @@ import NeedsTeam from 'components/needs_team.jsx';
import PasswordResetSendLink from 'components/password_reset_send_link.jsx'; import PasswordResetSendLink from 'components/password_reset_send_link.jsx';
import PasswordResetForm from 'components/password_reset_form.jsx'; import PasswordResetForm from 'components/password_reset_form.jsx';
import ChannelView from 'components/channel_view.jsx'; import ChannelView from 'components/channel_view.jsx';
import PermalinkView from 'components/permalink_view.jsx';
import Sidebar from 'components/sidebar.jsx'; import Sidebar from 'components/sidebar.jsx';
import * as AsyncClient from 'utils/async_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx';
import PreferenceStore from 'stores/preference_store.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 ShouldVerifyEmail from 'components/should_verify_email.jsx';
import DoVerifyEmail from 'components/do_verify_email.jsx'; import DoVerifyEmail from 'components/do_verify_email.jsx';
import AdminConsole from 'components/admin_console/admin_controller.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 SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.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(); const d2 = AsyncClient.getChannels();
$.when(d1, d2).done(() => callback()); $.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);
} }
function onRootEnter(nextState, replace, callback) { function onRootEnter(nextState, replace, callback) {
@@ -172,6 +163,26 @@ function onPermalinkEnter(nextState) {
GlobalActions.emitPostFocusEvent(postId); 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) { function onLoggedOut(nextState) {
const teamName = nextState.params.team; const teamName = nextState.params.team;
Client.logout( Client.logout(
@@ -204,7 +215,8 @@ function renderRootComponent() {
> >
<Route <Route
path=':team/channels/:channel' path=':team/channels/:channel'
onEnter={onChannelChange} onEnter={onChannelEnter}
onChange={onChannelChange}
components={{ components={{
sidebar: Sidebar, sidebar: Sidebar,
center: ChannelView center: ChannelView
@@ -215,23 +227,23 @@ function renderRootComponent() {
onEnter={onPermalinkEnter} onEnter={onPermalinkEnter}
components={{ components={{
sidebar: Sidebar, sidebar: Sidebar,
center: ChannelView center: PermalinkView
}}
/>
<Route
path=':team/tutorial'
components={{
sidebar: Sidebar,
center: TutorialView
}} }}
/> />
<Route <Route
path=':team/logout' path=':team/logout'
onEnter={onLoggedOut} onEnter={onLoggedOut}
components={{
sidebar: null,
center: null
}}
/> />
<Route <Route
path='admin_console' path='admin_console'
components={{ component={AdminConsole}
sidebar: null,
center: AdminConsole
}}
/> />
</Route> </Route>
<Route component={NotLoggedIn}> <Route component={NotLoggedIn}>

Просмотреть файл

@@ -406,7 +406,7 @@ class PostStoreClass extends EventEmitter {
let posts; let posts;
let pendingPosts; let pendingPosts;
for (const k in this.postsInfo) { 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; posts = this.postsInfo[k].postList.posts;
if (this.postsInfo[k].pendingPosts != null) { if (this.postsInfo[k].pendingPosts != null) {
pendingPosts = this.postsInfo[k].pendingPosts.posts; pendingPosts = this.postsInfo[k].pendingPosts.posts;

Просмотреть файл

@@ -16,7 +16,7 @@ class SearchStoreClass extends EventEmitter {
constructor() { constructor() {
super(); super();
this.searchResults = {}; this.searchResults = null;
this.isMentionSearch = false; this.isMentionSearch = false;
this.searchTerm = ''; this.searchTerm = '';
} }

Просмотреть файл

@@ -169,7 +169,7 @@ export function notifyMe(title, body, channel) {
notification.onclick = () => { notification.onclick = () => {
window.focus(); window.focus();
if (channel) { if (channel) {
switchChannel(channel); GlobalActions.emitChannelClickEvent(channel);
} else { } else {
browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square'); browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square');
} }
@@ -957,24 +957,6 @@ export function isValidUsername(name) {
return error; 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() { export function isMobile() {
return screen.width <= 768; return screen.width <= 768;
} }
@@ -1253,7 +1235,7 @@ export function importSlack(file, success, error) {
} }
export function getTeamURLFromAddressBar() { export function getTeamURLFromAddressBar() {
return window.location.href.split('/channels')[0]; return window.location.origin + '/' + window.location.pathname.split('/')[1];
} }
export function getShortenedTeamURL() { export function getShortenedTeamURL() {