diff --git a/components/channel_layout/center_channel/center_channel.jsx b/components/channel_layout/center_channel/center_channel.jsx index a8de4e89f891..4a6c756d9baa 100644 --- a/components/channel_layout/center_channel/center_channel.jsx +++ b/components/channel_layout/center_channel/center_channel.jsx @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {Route, Switch, Redirect} from 'react-router-dom'; import classNames from 'classnames'; +import * as UserAgent from 'utils/user_agent.jsx'; import PermalinkView from 'components/permalink_view'; import Navbar from 'components/navbar'; import ChannelIdentifierRouter from 'components/channel_layout/channel_identifier_router'; @@ -28,6 +29,23 @@ export default class CenterChannel extends React.PureComponent { }; } + componentDidMount() { + document.body.classList.add('app__body'); + + // IE Detection + if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) { + document.body.classList.add('browser--ie'); + } + } + + componentWillUnmount() { + document.body.classList.remove('app__body'); + + if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) { + document.body.classList.remove('browser--ie'); + } + } + UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase if (this.props.location.pathname !== nextProps.location.pathname && nextProps.location.pathname.includes('/pl/')) { this.setState({returnTo: this.props.location.pathname}); @@ -65,7 +83,13 @@ export default class CenterChannel extends React.PureComponent { /> ( + + )} /> diff --git a/components/channel_layout/channel_group_route.jsx b/components/channel_layout/channel_group_route.jsx new file mode 100644 index 000000000000..14f78828bd08 --- /dev/null +++ b/components/channel_layout/channel_group_route.jsx @@ -0,0 +1,125 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; + +import {joinChannel} from 'mattermost-redux/actions/channels'; + +import * as GlobalActions from 'actions/global_actions.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import store from 'stores/redux_store.jsx'; +import {Constants} from 'utils/constants.jsx'; +import ChannelView from 'components/channel_view/index'; +import LoadingScreen from 'components/loading_screen.jsx'; +import * as Utils from 'utils/utils.jsx'; + +const dispatch = store.dispatch; +const getState = store.getState; + +export default class ChannelAndGroupRoute extends React.PureComponent { + static propTypes = { + byName: PropTypes.bool, + byId: PropTypes.bool, + asGroup: PropTypes.bool, + + /* + * Object from react-router + */ + match: PropTypes.shape({ + params: PropTypes.shape({ + identifier: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + }; + + constructor(props) { + super(props); + this.state = { + basicViewInfo: null, + }; + } + + static getDerivedStateFromProps(nextProps, prevState) { + if (nextProps.match.params.identifier !== prevState.identifier) { + return { + basicViewInfo: null, + identifier: nextProps.match.params.identifier, + }; + } + return null; + } + + componentDidMount() { + this.goToChannelOrGroup(); + } + + componentDidUpdate(prevProps) { + if (prevProps.match.params.identifier !== this.props.match.params.identifier) { + this.goToChannelOrGroup(); + } + } + + handleError = (match) => { + const {team} = match.params; + this.props.history.push(team ? `/${team}/channels/${Constants.DEFAULT_CHANNEL}` : '/'); + } + + simulateChannelClick(channel) { + GlobalActions.emitChannelClickEvent(channel); + } + + goToChannelOrGroup = async () => { + // console.log(match, history); + let basicViewInfo; + let basicViewInfoResponse; + const {team, identifier} = this.props.match.params; + if (this.props.byName) { + const channelName = identifier.toLowerCase(); + basicViewInfo = ChannelStore.getByName(channelName); + if (!basicViewInfo) { + basicViewInfoResponse = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), null, channelName)(dispatch, getState); + } + } else if (this.props.byId) { + const channelId = identifier.toLowerCase(); + basicViewInfo = ChannelStore.get(channelId); + if (!basicViewInfo) { + basicViewInfoResponse = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), channelId, null)(dispatch, getState); + } + } else if (this.props.asGroup) { + const groupName = identifier.toLowerCase(); + basicViewInfo = ChannelStore.getByName(groupName); + if (!basicViewInfo) { + basicViewInfoResponse = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), null, groupName)(dispatch, getState); + } + } + + basicViewInfo = basicViewInfo || basicViewInfoResponse.data.channel; + + if (basicViewInfoResponse && basicViewInfoResponse.error) { + this.handleError(this.props.match); + return; + } + + if (basicViewInfo.type === Constants.DM_CHANNEL) { + this.props.history.replace(`/${team}/messages/${Utils.getUserIdFromChannelId(basicViewInfo.name)}`); + } else if (this.props.byId) { + this.props.history.replace(`/${team}/channels/${basicViewInfo.name}`); + } + + this.simulateChannelClick(basicViewInfo); + + this.setState({ + basicViewInfo, + }); + } + + render() { + if (!this.state.basicViewInfo) { + return ; + } + return ; + } +} diff --git a/components/channel_layout/channel_identifier_router.jsx b/components/channel_layout/channel_identifier_router.jsx index 603de3bc899d..f28573cd7fa7 100644 --- a/components/channel_layout/channel_identifier_router.jsx +++ b/components/channel_layout/channel_identifier_router.jsx @@ -4,208 +4,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {joinChannel} from 'mattermost-redux/actions/channels'; -import {getUser, getUserByUsername, getUserByEmail} from 'mattermost-redux/actions/users'; - -import ChannelView from 'components/channel_view/index'; -import UserStore from 'stores/user_store.jsx'; import ChannelStore from 'stores/channel_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import {Constants} from 'utils/constants.jsx'; -import {openDirectChannelToUser} from 'actions/channel_actions.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; -import * as Utils from 'utils/utils.jsx'; -import store from 'stores/redux_store.jsx'; -const dispatch = store.dispatch; -const getState = store.getState; + +import ChannelAndGroupRoute from './channel_group_route'; +import UserRoute from './user_route'; const LENGTH_OF_ID = 26; const LENGTH_OF_GROUP_ID = 40; const LENGTH_OF_USER_ID_PAIR = 54; -function onChannelByIdentifierEnter({match, history}) { - const {path, identifier} = match.params; - - if (path === 'channels') { - if (identifier.length === LENGTH_OF_ID) { - // It's hard to tell an ID apart from a channel name of the same length, so check first if - // the identifier matches a channel that we have - const channelsByName = ChannelStore.getByName(identifier); - const moreChannelsByName = ChannelStore.getMoreChannelsList().find((chan) => chan.name === identifier); - if (channelsByName || moreChannelsByName) { - goToChannelByChannelName(match, history); - } else { - goToChannelByChannelId(match, history); - } - } else if (identifier.length === LENGTH_OF_GROUP_ID) { - goToGroupChannelByGroupId(match, history); - } else if (identifier.length === LENGTH_OF_USER_ID_PAIR) { - goToDirectChannelByUserIds(match, history); - } else { - goToChannelByChannelName(match, history); - } - } else if (path === 'messages') { - if (identifier.indexOf('@') === 0) { - goToDirectChannelByUsername(match, history); - } else if (identifier.indexOf('@') > 0) { - goToDirectChannelByEmail(match, history); - } else if (identifier.length === LENGTH_OF_ID) { - goToDirectChannelByUserId(match, history, identifier); - } else if (identifier.length === LENGTH_OF_GROUP_ID) { - goToGroupChannelByGroupId(match, history); - } else { - handleError(match, history); - } - } -} - -async function goToChannelByChannelId(match, history) { - const {team, identifier} = match.params; - const channelId = identifier.toLowerCase(); - - let channel = ChannelStore.get(channelId); - if (!channel) { - const {data, error} = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), channelId, null)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - channel = data.channel; - } - - if (channel.type === Constants.DM_CHANNEL) { - goToDirectChannelByUserId(match, history, Utils.getUserIdFromChannelId(channel.name)); - } else if (channel.type === Constants.GM_CHANNEL) { - history.replace(`/${team}/messages/${channel.name}`); - } else { - history.replace(`/${team}/channels/${channel.name}`); - } -} - -async function goToChannelByChannelName(match, history) { - const {team, identifier} = match.params; - const channelName = identifier.toLowerCase(); - - let channel = ChannelStore.getByName(channelName); - if (!channel) { - const {data, error} = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), null, channelName)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - channel = data.channel; - } - - if (channel.type === Constants.DM_CHANNEL) { - goToDirectChannelByUserIds(match, history); - } else if (channel.type === Constants.GM_CHANNEL) { - history.replace(`/${team}/messages/${channel.name}`); - } else { - doChannelChange(channel); - } -} - -async function goToDirectChannelByUsername(match, history) { - const {identifier} = match.params; - const username = identifier.slice(1, identifier.length).toLowerCase(); - - let user = UserStore.getProfileByUsername(username); - if (!user) { - const {data, error} = await getUserByUsername(username)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - user = data; - } - - openDirectChannelToUser( - user.id, - (channel) => { - doChannelChange(channel); - }, - () => handleError(match, history) - ); -} - -async function goToDirectChannelByUserId(match, history, userId) { - const {team} = match.params; - - let user = UserStore.getProfile(userId); - if (!user) { - const {data, error} = await getUser(userId)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - user = data; - } - - history.replace(`/${team}/messages/@${user.username}`); -} - -async function goToDirectChannelByUserIds(match, history) { - const {team, identifier} = match.params; - const userId = Utils.getUserIdFromChannelId(identifier.toLowerCase()); - - let user = UserStore.getProfile(userId); - if (!user) { - const {data, error} = await getUser(userId)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - user = data; - } - - history.replace(`/${team}/messages/@${user.username}`); -} - -async function goToDirectChannelByEmail(match, history) { - const {team, identifier} = match.params; - const email = identifier.toLowerCase(); - - let user = UserStore.getProfileByEmail(email); - if (!user) { - const {data, error} = await getUserByEmail(email)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - user = data; - } - - history.replace(`/${team}/messages/@${user.username}`); -} - -async function goToGroupChannelByGroupId(match, history) { - const {identifier} = match.params; - const groupId = identifier.toLowerCase(); - - history.replace(match.url.replace('/channels/', '/messages/')); - - let channel = ChannelStore.getByName(groupId); - if (!channel) { - const {data, error} = await joinChannel(UserStore.getCurrentId(), TeamStore.getCurrentId(), null, groupId)(dispatch, getState); - if (error) { - handleError(match, history); - return; - } - channel = data.channel; - } - - doChannelChange(channel); -} - -function doChannelChange(channel) { - GlobalActions.emitChannelClickEvent(channel); -} - -function handleError(match, history) { - const {team} = match.params; - history.push(team ? `/${team}/channels/${Constants.DEFAULT_CHANNEL}` : '/'); -} - export default class ChannelIdentifierRouter extends React.PureComponent { static propTypes = { @@ -215,25 +22,93 @@ export default class ChannelIdentifierRouter extends React.PureComponent { match: PropTypes.shape({ params: PropTypes.shape({ identifier: PropTypes.string.isRequired, - team: PropTypes.string.isRequired, }).isRequired, }).isRequired, } - constructor(props) { - super(props); - - onChannelByIdentifierEnter(props); - } - - UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase - if (this.props.match.params.team !== nextProps.match.params.team || - this.props.match.params.identifier !== nextProps.match.params.identifier) { - onChannelByIdentifierEnter(nextProps); + onChannelByIdentifierEnter(match) { + const {path, identifier} = match.params; + if (path === 'channels') { + if (identifier.length === LENGTH_OF_ID) { + // It's hard to tell an ID apart from a channel name of the same length, so check first if + // the identifier matches a channel that we have + if (ChannelStore.getByName(identifier)) { + return ( + + ); + } + return ( + + ); + } else if (identifier.length === LENGTH_OF_GROUP_ID) { + return ( + + ); + } else if (identifier.length === LENGTH_OF_USER_ID_PAIR) { + return ( + + ); + } + return ( + + ); + } else if (path === 'messages') { + if (identifier.indexOf('@') === 0) { + return ( + + ); + } else if (identifier.indexOf('@') > 0) { + return ( + + ); + } else if (identifier.length === LENGTH_OF_ID) { + return ( + + ); + } else if (identifier.length === LENGTH_OF_GROUP_ID) { + return ( + + ); + } } + + //TODO: fallback?? + return ( + + ); } render() { - return ; + return this.onChannelByIdentifierEnter(this.props.match); } } diff --git a/components/channel_layout/user_route.jsx b/components/channel_layout/user_route.jsx new file mode 100644 index 000000000000..68d07d36ee4f --- /dev/null +++ b/components/channel_layout/user_route.jsx @@ -0,0 +1,133 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; +import {getUser, getUserByUsername, getUserByEmail} from 'mattermost-redux/actions/users'; + +import * as GlobalActions from 'actions/global_actions.jsx'; +import {openDirectChannelToUser} from 'actions/channel_actions.jsx'; +import UserStore from 'stores/user_store.jsx'; +import store from 'stores/redux_store.jsx'; +import {Constants} from 'utils/constants.jsx'; +import LoadingScreen from 'components/loading_screen.jsx'; +import ChannelView from 'components/channel_view/index'; +import * as Utils from 'utils/utils.jsx'; + +const dispatch = store.dispatch; +const getState = store.getState; + +export default class ChannelRoute extends React.PureComponent { + static propTypes = { + byId: PropTypes.bool, + byIds: PropTypes.bool, + byEmail: PropTypes.bool, + byName: PropTypes.bool, + + /* + * Object from react-router + */ + match: PropTypes.shape({ + params: PropTypes.shape({ + identifier: PropTypes.string.isRequired, + team: PropTypes.string.isRequired, + }).isRequired, + }).isRequired, + }; + + constructor(props) { + super(props); + this.state = { + userInfo: null, + }; + } + + static getDerivedStateFromProps(nextProps, prevState) { + if (nextProps.match.params.identifier !== prevState.identifier) { + return { + userInfo: null, + identifier: nextProps.match.params.identifier, + }; + } + return null; + } + + componentDidMount() { + this.goToUser({match: this.props.match, history: this.props.history}); + } + + componentDidUpdate(prevProps) { + if (prevProps.match.params.identifier !== this.props.match.params.identifier) { + this.goToUser({match: this.props.match, history: this.props.history}); + } + } + + handleError(match) { + const {team} = match.params; + this.props.history.push(team ? `/${team}/channels/${Constants.DEFAULT_CHANNEL}` : '/'); + } + + doUserChange = (user) => { + if (this.props.byEmail || this.props.byId || this.props.byIds) { + this.props.history.replace(`/${this.props.match.params.team}/messages/@${user.display_name}`); + } + GlobalActions.emitChannelClickEvent(user); + } + + goToUser = async ({match}) => { + let user; + let userInfoResponse; + const {identifier} = match.params; + const username = identifier.slice(1, identifier.length).toLowerCase(); + + if (this.props.byName) { + user = UserStore.getProfileByUsername(username); + if (!user) { + userInfoResponse = await getUserByUsername(username)(dispatch, getState); + } + } else if (this.props.byEmail) { + const email = identifier.toLowerCase(); + user = UserStore.getProfileByEmail(email); + if (!user) { + userInfoResponse = await getUserByEmail(email)(dispatch, getState); + } + } else if (this.props.byId) { + user = UserStore.getProfile(identifier); + if (!user) { + userInfoResponse = await getUser(identifier)(dispatch, getState); + } + } else if (this.props.byIds) { + const userId = Utils.getUserIdFromChannelId(identifier.toLowerCase()); + user = UserStore.getProfile(userId); + if (!user) { + userInfoResponse = await getUser(userId)(dispatch, getState); + } + } + + user = user || userInfoResponse.data; + + if (userInfoResponse && userInfoResponse.error) { + this.handleError(match); + return; + } + + this.setState({ + userInfo: user, + }); + + openDirectChannelToUser( + user.id, + (userInfo) => { + this.doUserChange(userInfo); + }, + () => this.handleError(match) + ); + } + + render() { + if (!this.state.userInfo) { + return ; + } + return ; + } +} diff --git a/components/channel_view/channel_view.jsx b/components/channel_view/channel_view.jsx index 8c11a1424ecf..86f6411808ea 100644 --- a/components/channel_view/channel_view.jsx +++ b/components/channel_view/channel_view.jsx @@ -1,12 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import $ from 'jquery'; import PropTypes from 'prop-types'; import React from 'react'; import {FormattedMessage} from 'react-intl'; -import * as UserAgent from 'utils/user_agent.jsx'; import deferComponentRender from 'components/deferComponentRender'; import ChannelHeader from 'components/channel_header'; import CreatePost from 'components/create_post'; @@ -54,19 +52,6 @@ export default class ChannelView extends React.PureComponent { ); } - componentDidMount() { - $('body').addClass('app__body'); - - // IE Detection - if (UserAgent.isInternetExplorer() || UserAgent.isEdge()) { - $('body').addClass('browser--ie'); - } - } - - componentWillUnmount() { - $('body').removeClass('app__body'); - } - UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase if (this.props.match.url !== nextProps.match.url) { this.createDeferredPostView();