Refactoring center panel away. Moving tutorial to a route. Fixing a
bunch of bugs.
Этот коммит содержится в:
@@ -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
Обычный файл
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) {
|
||||
|
||||
19
webapp/components/tutorial/tutorial_view.jsx
Обычный файл
19
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Ссылка в новой задаче
Block a user