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 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: channel.name,
id: channel.id,
prev: ChannelStore.getCurrentId()
});
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: chan.name,
id: chan.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
});
}

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

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

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

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

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

@@ -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/>

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

@@ -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) => {

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

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

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

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

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

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

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

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

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

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

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

@@ -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

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

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

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

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

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

@@ -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) {

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

@@ -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 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/tutorial'
components={{
sidebar: Sidebar,
center: TutorialView
}}
/>
<Route
path=':team/logout'
onEnter={onLoggedOut}
components={{
sidebar: null,
center: null
}}
/>
<Route
path='admin_console'
components={{
sidebar: null,
center: AdminConsole
}}
component={AdminConsole}
/>
</Route>
<Route component={NotLoggedIn}>

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

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

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

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

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

@@ -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() {