diff options
Diffstat (limited to 'webapp/components')
31 files changed, 317 insertions, 253 deletions
diff --git a/webapp/components/admin_console/admin_navbar_dropdown.jsx b/webapp/components/admin_console/admin_navbar_dropdown.jsx index 527f97959..729d4b14d 100644 --- a/webapp/components/admin_console/admin_navbar_dropdown.jsx +++ b/webapp/components/admin_console/admin_navbar_dropdown.jsx @@ -64,7 +64,7 @@ export default class AdminNavbarDropdown extends React.Component { > <li> <Link - to={Utils.getWindowLocationOrigin() + '/' + this.state.currentTeam.name + '/channels/town-square'} + to={'/' + this.state.currentTeam.name + '/channels/town-square'} > <FormattedMessage id='admin.nav.switch' diff --git a/webapp/components/center_panel.jsx b/webapp/components/center_panel.jsx deleted file mode 100644 index 62b12c1d2..000000000 --- a/webapp/components/center_panel.jsx +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import TutorialIntroScreens from './tutorial/tutorial_intro_screens.jsx'; -import CreatePost from './create_post.jsx'; -import PostsViewContainer from './posts_view_container.jsx'; -import PostFocusView from './post_focus_view.jsx'; -import ChannelHeader from './channel_header.jsx'; -import Navbar from './navbar.jsx'; -import FileUploadOverlay from './file_upload_overlay.jsx'; - -import PreferenceStore from 'stores/preference_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; -import UserStore from 'stores/user_store.jsx'; - -import * as Utils from 'utils/utils.jsx'; - -import {FormattedMessage} from 'react-intl'; - -import Constants from 'utils/constants.jsx'; -const TutorialSteps = Constants.TutorialSteps; -const Preferences = Constants.Preferences; - -import React from 'react'; -import {Link} from 'react-router'; - -export default class CenterPanel extends React.Component { - constructor(props) { - super(props); - - this.getStateFromStores = this.getStateFromStores.bind(this); - this.validState = this.validState.bind(this); - this.onStoresChange = this.onStoresChange.bind(this); - - this.state = this.getStateFromStores(); - } - getStateFromStores() { - const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); - return { - showTutorialScreens: tutorialStep <= TutorialSteps.INTRO_SCREENS, - showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS, - user: UserStore.getCurrentUser(), - channel: ChannelStore.getCurrent(), - profiles: JSON.parse(JSON.stringify(UserStore.getProfiles())) - }; - } - validState() { - return this.state.user && this.state.channel && this.state.profiles; - } - onStoresChange() { - this.setState(this.getStateFromStores()); - } - componentDidMount() { - PreferenceStore.addChangeListener(this.onStoresChange); - ChannelStore.addChangeListener(this.onStoresChange); - UserStore.addChangeListener(this.onStoresChange); - } - componentWillUnmount() { - PreferenceStore.removeChangeListener(this.onStoresChange); - ChannelStore.removeChangeListener(this.onStoresChange); - UserStore.removeChangeListener(this.onStoresChange); - } - render() { - if (!this.validState()) { - return null; - } - const channel = this.state.channel; - var handleClick = null; - let postsContainer; - let createPost; - if (this.state.showTutorialScreens) { - postsContainer = <TutorialIntroScreens/>; - createPost = null; - } else if (this.state.showPostFocus) { - postsContainer = <PostFocusView profiles={this.state.profiles}/>; - - handleClick = function clickHandler(e) { - e.preventDefault(); - Utils.switchChannel(channel); - }; - - createPost = ( - <div - id='archive-link-home' - onClick={handleClick} - > - <Link to=''> - <FormattedMessage - id='center_panel.recent' - defaultMessage='Click here to jump to recent messages. ' - /> - <i className='fa fa-arrow-down'></i> - </Link> - </div> - ); - } else { - postsContainer = <PostsViewContainer profiles={this.state.profiles}/>; - createPost = ( - <div - className='post-create__container' - id='post-create' - > - <CreatePost/> - </div> - ); - } - - return ( - <div className='inner-wrap channel__wrap'> - <div className='row header'> - <div id='navbar'> - <Navbar/> - </div> - </div> - <div className='row main'> - <FileUploadOverlay - id='file_upload_overlay' - overlayType='center' - /> - <div - id='app-content' - className='app__content' - > - <div - id='channel-header' - className='channel-header' - > - <ChannelHeader - user={this.state.user} - /> - </div> - {postsContainer} - {createPost} - </div> - </div> - </div> - ); - } -} - -CenterPanel.defaultProps = { -}; - -CenterPanel.propTypes = { -}; diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx index 369fa2dbb..6bb466c3e 100644 --- a/webapp/components/channel_header.jsx +++ b/webapp/components/channel_header.jsx @@ -26,6 +26,7 @@ import * as Utils from 'utils/utils.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as Client from 'utils/client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage} from 'react-intl'; @@ -53,11 +54,11 @@ export default class ChannelHeader extends React.Component { this.state = state; } getStateFromStores() { - const extraInfo = ChannelStore.getCurrentExtraInfo(); + const extraInfo = ChannelStore.getExtraInfo(this.props.channelId); return { - channel: ChannelStore.getCurrent(), - memberChannel: ChannelStore.getCurrentMember(), + channel: ChannelStore.get(this.props.channelId), + memberChannel: ChannelStore.getMember(this.props.channelId), users: extraInfo.members, userCount: extraInfo.member_count, searchVisible: SearchStore.getSearchResults() !== null, @@ -105,7 +106,7 @@ export default class ChannelHeader extends React.Component { }); const townsquare = ChannelStore.getByName('town-square'); - Utils.switchChannel(townsquare); + GlobalActions.emitChannelClickEvent(townsquare); }, (err) => { AsyncClient.dispatchError(err, 'handleLeave'); @@ -433,7 +434,10 @@ export default class ChannelHeader extends React.Component { } return ( - <div> + <div + id='channel-header' + className='channel-header' + > <table className='channel-header alt'> <tbody> <tr> @@ -518,4 +522,5 @@ export default class ChannelHeader extends React.Component { } ChannelHeader.propTypes = { + channelId: React.PropTypes.string.isRequired }; diff --git a/webapp/components/channel_notifications_modal.jsx b/webapp/components/channel_notifications_modal.jsx index cc1162b77..564776876 100644 --- a/webapp/components/channel_notifications_modal.jsx +++ b/webapp/components/channel_notifications_modal.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import {Modal} from 'react-bootstrap'; import SettingItemMin from './setting_item_min.jsx'; import SettingItemMax from './setting_item_max.jsx'; @@ -33,6 +34,7 @@ export default class ChannelNotificationsModal extends React.Component { }; } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.setState({activeSection: section}); } componentWillReceiveProps(nextProps) { diff --git a/webapp/components/channel_view.jsx b/webapp/components/channel_view.jsx index 34e1666d0..54d796ac1 100644 --- a/webapp/components/channel_view.jsx +++ b/webapp/components/channel_view.jsx @@ -1,14 +1,73 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import CenterPanel from 'components/center_panel.jsx'; - import React from 'react'; +import ChannelHeader from 'components/channel_header.jsx'; +import PostsViewContainer from 'components/posts_view_container.jsx'; +import CreatePost from 'components/create_post.jsx'; + +import ChannelStore from 'stores/channel_store.jsx'; +import UserStore from 'stores/user_store.jsx'; + export default class ChannelView extends React.Component { + constructor(props) { + super(props); + + this.getStateFromStores = this.getStateFromStores.bind(this); + this.isStateValid = this.isStateValid.bind(this); + this.updateState = this.updateState.bind(this); + + this.state = this.getStateFromStores(props); + } + getStateFromStores(props) { + const channel = ChannelStore.getByName(props.params.channel); + const channelId = channel ? channel.id : ''; + const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); + return { + channelId, + profiles + }; + } + isStateValid() { + return this.state.channelId !== '' && this.state.profiles; + } + updateState() { + this.setState(this.getStateFromStores(this.props)); + } + componentDidMount() { + ChannelStore.addChangeListener(this.updateState); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.updateState); + } + componentWillReceiveProps(nextProps) { + this.setState(this.getStateFromStores(nextProps)); + } + shouldComponentUpdate(nextProps, nextState) { + if (nextState.channelId !== this.state.channelId) { + return true; + } + + return false; + } render() { return ( - <CenterPanel/> + <div + id='app-content' + className='app__content' + > + <ChannelHeader + channelId={this.state.channelId} + /> + <PostsViewContainer profiles={this.state.profiles}/> + <div + className='post-create__container' + id='post-create' + > + <CreatePost/> + </div> + </div> ); } } @@ -16,5 +75,5 @@ ChannelView.defaultProps = { }; ChannelView.propTypes = { - params: React.PropTypes.object + params: React.PropTypes.object.isRequired }; diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index c6f7b50b1..53db501bf 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -10,6 +10,8 @@ import BrowserStore from 'stores/browser_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; import ErrorBar from 'components/error_bar.jsx'; import * as Websockets from 'action_creators/websocket_actions.jsx'; @@ -17,6 +19,7 @@ import {browserHistory} from 'react-router'; import SidebarRight from 'components/sidebar_right.jsx'; import SidebarRightMenu from 'components/sidebar_right_menu.jsx'; +import Navbar from 'components/navbar.jsx'; // Modals import GetPostLinkModal from 'components/get_post_link_modal.jsx'; @@ -66,6 +69,12 @@ export default class LoggedIn extends React.Component { Utils.applyTheme(Constants.THEMES.default); } } + + // Go to tutorial if we are first arrivign + const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999); + if (tutorialStep <= TutorialSteps.INTRO_SCREENS) { + browserHistory.push(Utils.getTeamURLFromAddressBar() + '/tutorial'); + } } componentWillMount() { // Emit view action @@ -186,14 +195,36 @@ export default class LoggedIn extends React.Component { $(window).off('keydown.preventBackspace'); } render() { + let content = []; + if (this.props.children) { + content = this.props.children; + } else { + content.push( + this.props.sidebar + ); + content.push( + <div + key='inner-wrap' + className='inner-wrap channel__wrap' + > + <div className='row header'> + <div id='navbar'> + <Navbar/> + </div> + </div> + <div className='row main'> + {this.props.center} + </div> + </div> + ); + } return ( <div className='channel-view'> <ErrorBar/> <div className='container-fluid'> <SidebarRight/> <SidebarRightMenu/> - {this.props.sidebar} - {this.props.center} + {content} <GetPostLinkModal/> <GetTeamInviteLinkModal/> diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx index d0eeec1ef..811bb8101 100644 --- a/webapp/components/more_channels.jsx +++ b/webapp/components/more_channels.jsx @@ -9,6 +9,7 @@ import * as AsyncClient from 'utils/async_client.jsx'; import ChannelStore from 'stores/channel_store.jsx'; import LoadingScreen from './loading_screen.jsx'; import NewChannelFlow from './new_channel_flow.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -64,8 +65,7 @@ export default class MoreChannels extends React.Component { client.joinChannel(channel.id, () => { $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); - AsyncClient.getChannel(channel.id); - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); this.setState({joiningChannel: -1}); }, (err) => { diff --git a/webapp/components/more_direct_channels.jsx b/webapp/components/more_direct_channels.jsx index d1446059d..feab8c9db 100644 --- a/webapp/components/more_direct_channels.jsx +++ b/webapp/components/more_direct_channels.jsx @@ -5,6 +5,7 @@ import {Modal} from 'react-bootstrap'; import FilteredUserList from './filtered_user_list.jsx'; import UserStore from 'stores/user_store.jsx'; import * as Utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; import SpinnerButton from 'components/spinner_button.jsx'; @@ -68,7 +69,7 @@ export default class MoreDirectChannels extends React.Component { Utils.openDirectChannelToUser( teammate, (channel) => { - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); this.setState({loadingDMChannel: -1}); this.handleHide(); }, diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index e58e142d0..5afd7e683 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -45,6 +45,7 @@ export default class Navbar extends React.Component { this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); this.showRenameChannelModal = this.showRenameChannelModal.bind(this); this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this); + this.isStateValid = this.isStateValid.bind(this); this.createCollapseButtons = this.createCollapseButtons.bind(this); this.createDropdown = this.createDropdown.bind(this); @@ -64,7 +65,7 @@ export default class Navbar extends React.Component { currentUser: UserStore.getCurrentUser() }; } - stateValid() { + isStateValid() { return this.state.channel && this.state.member && this.state.users && this.state.currentUser; } componentDidMount() { @@ -422,7 +423,7 @@ export default class Navbar extends React.Component { return buttons; } render() { - if (!this.stateValid()) { + if (!this.isStateValid()) { return null; } diff --git a/webapp/components/new_channel_flow.jsx b/webapp/components/new_channel_flow.jsx index 30035ee5d..8c66ef3ce 100644 --- a/webapp/components/new_channel_flow.jsx +++ b/webapp/components/new_channel_flow.jsx @@ -2,9 +2,9 @@ // See License.txt for license information. import * as Utils from 'utils/utils.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; import * as Client from 'utils/client.jsx'; import UserStore from 'stores/user_store.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import NewChannelModal from './new_channel_modal.jsx'; import ChangeURLModal from './change_url_modal.jsx'; @@ -110,8 +110,7 @@ class NewChannelFlow extends React.Component { Client.createChannel(channel, (data) => { this.props.onModalDismissed(); - AsyncClient.getChannel(data.id); - Utils.switchChannel(data); + GlobalActions.emitChannelClickEvent(data); }, (err) => { if (err.id === 'model.channel.is_valid.2_or_more.app_error') { @@ -247,4 +246,4 @@ NewChannelFlow.propTypes = { onModalDismissed: React.PropTypes.func.isRequired }; -export default injectIntl(NewChannelFlow);
\ No newline at end of file +export default injectIntl(NewChannelFlow); diff --git a/webapp/components/permalink_view.jsx b/webapp/components/permalink_view.jsx new file mode 100644 index 000000000..2ebe52356 --- /dev/null +++ b/webapp/components/permalink_view.jsx @@ -0,0 +1,110 @@ +// 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 team = TeamStore.getCurrent(); + const teamName = team ? team.name : ''; + const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles())); + return { + channelId, + channelName, + profiles, + teamName, + postId + }; + } + isStateValid() { + return this.state.channelId !== '' && this.state.profiles && this.state.teamName; + } + 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.teamName !== this.state.teamName) { + 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.teamName + '/channels/' + this.state.channelName} + > + <FormattedMessage + id='center_panel.recent' + defaultMessage='Click here to jump to recent messages. ' + /> + <i className='fa fa-arrow-down'></i> + </Link> + </div> + </div> + ); + } +} + +PermalinkView.defaultProps = { +}; + +PermalinkView.propTypes = { + params: React.PropTypes.object.isRequired +}; diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx index 819c7f590..cd583e4c3 100644 --- a/webapp/components/popover_list_members.jsx +++ b/webapp/components/popover_list_members.jsx @@ -6,6 +6,7 @@ import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; import {Popover, Overlay} from 'react-bootstrap'; import * as Utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import ChannelStore from 'stores/channel_store.jsx'; @@ -36,7 +37,7 @@ export default class PopoverListMembers extends React.Component { Utils.openDirectChannelToUser( teammate, (channel, channelAlreadyExisted) => { - Utils.switchChannel(channel); + GlobalActions.emitChannelClickEvent(channel); if (channelAlreadyExisted) { this.closePopover(); } diff --git a/webapp/components/post_info.jsx b/webapp/components/post_info.jsx index 0aa71edd7..2d41b0e54 100644 --- a/webapp/components/post_info.jsx +++ b/webapp/components/post_info.jsx @@ -22,7 +22,7 @@ export default class PostInfo extends React.Component { } dropdownPosition(e) { var position = $('#post-list').height() - $(e.target).offset().top; - var dropdown = $(e.target).next('.dropdown-menu'); + var dropdown = $(e.target).closest('.col__reply').find('.dropdown-menu'); if (position < dropdown.height()) { dropdown.addClass('bottom'); } diff --git a/webapp/components/posts_view.jsx b/webapp/components/posts_view.jsx index 8b4b0c662..647c7f086 100644 --- a/webapp/components/posts_view.jsx +++ b/webapp/components/posts_view.jsx @@ -384,6 +384,7 @@ export default class PostsView extends React.Component { } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); + this.scrollStopAction.cancel(); } componentDidUpdate() { if (this.props.postList != null) { @@ -570,7 +571,9 @@ function FloatingTimestamp({isScrolling, post}) { return ( <div className={className}> - <span>{dateString}</span> + <div> + <span>{dateString}</span> + </div> </div> ); } diff --git a/webapp/components/removed_from_channel_modal.jsx b/webapp/components/removed_from_channel_modal.jsx index cdd51bd6e..45018ac99 100644 --- a/webapp/components/removed_from_channel_modal.jsx +++ b/webapp/components/removed_from_channel_modal.jsx @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom'; import ChannelStore from 'stores/channel_store.jsx'; import UserStore from 'stores/user_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; -import * as utils from 'utils/utils.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import {FormattedMessage} from 'react-intl'; @@ -33,7 +33,7 @@ export default class RemovedFromChannelModal extends React.Component { } var townSquare = ChannelStore.getByName('town-square'); - setTimeout(() => utils.switchChannel(townSquare), 1); + setTimeout(() => GlobalActions.emitChannelClickEvent(townSquare), 1); this.setState(newState); } diff --git a/webapp/components/rename_channel_modal.jsx b/webapp/components/rename_channel_modal.jsx index 72828984c..ced3c2d2b 100644 --- a/webapp/components/rename_channel_modal.jsx +++ b/webapp/components/rename_channel_modal.jsx @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'; import * as Utils from 'utils/utils.jsx'; import * as Client from 'utils/client.jsx'; -import * as AsyncClient from 'utils/async_client.jsx'; +import * as GlobalActions from 'action_creators/global_actions.jsx'; import Constants from 'utils/constants.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl'; @@ -165,8 +165,7 @@ export default class RenameChannelModal extends React.Component { Client.updateChannel( channel, () => { - AsyncClient.getChannel(channel.id); - Utils.updateAddressBar(channel.name); + GlobalActions.emitChannelClickEvent(channel); this.handleHide(); }, diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx index 35769d06b..219aa7093 100644 --- a/webapp/components/search_results_item.jsx +++ b/webapp/components/search_results_item.jsx @@ -1,37 +1,25 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; import UserStore from 'stores/user_store.jsx'; import UserProfile from './user_profile.jsx'; import * as GlobalActions from 'action_creators/global_actions.jsx'; import * as TextFormatting from 'utils/text_formatting.jsx'; +import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage, FormattedDate} from 'react-intl'; - import React from 'react'; +import {Link} from 'react-router'; export default class SearchResultsItem extends React.Component { constructor(props) { super(props); - this.handleClick = this.handleClick.bind(this); this.handleFocusRHSClick = this.handleFocusRHSClick.bind(this); } - handleClick(e) { - e.preventDefault(); - - GlobalActions.emitPostFocusEvent(this.props.post.id); - - if ($(window).width() < 768) { - $('.sidebar--right').removeClass('move--left'); - $('.inner-wrap').removeClass('move--left'); - } - } - handleFocusRHSClick(e) { e.preventDefault(); GlobalActions.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch); @@ -99,16 +87,15 @@ export default class SearchResultsItem extends React.Component { </time> </li> <li> - <a - href='#' + <Link + to={Utils.getTeamURLFromAddressBar() + '/pl/' + this.props.post.id} className='search-item__jump' - onClick={this.handleClick} > <FormattedMessage id='search_item.jump' defaultMessage='Jump' /> - </a> + </Link> </li> <li> <a diff --git a/webapp/components/sidebar.jsx b/webapp/components/sidebar.jsx index c0d4755ed..45bca7212 100644 --- a/webapp/components/sidebar.jsx +++ b/webapp/components/sidebar.jsx @@ -15,7 +15,6 @@ import TeamStore from 'stores/team_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; -import * as Client from 'utils/client.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -29,7 +28,7 @@ import {Tooltip, OverlayTrigger} from 'react-bootstrap'; import loadingGif from 'images/load.gif'; import React from 'react'; -import {browserHistory} from 'react-router'; +import {browserHistory, Link} from 'react-router'; import favicon from 'images/favicon/favicon-16x16.png'; import redFavicon from 'images/favicon/redfavicon-16x16.png'; @@ -259,7 +258,7 @@ export default class Sidebar extends React.Component { } if (channel.id === this.state.activeId) { - Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL)); + browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square'); } } @@ -406,48 +405,6 @@ export default class Sidebar extends React.Component { icon = <div className='status'><i className='fa fa-lock'></i></div>; } - // set up click handler to switch channels (or create a new channel for non-existant ones) - var handleClick = null; - - if (!channel.fake) { - handleClick = function clickHandler(e) { - if (e.target.attributes.getNamedItem('data-close')) { - handleClose(channel); - } else { - Utils.switchChannel(channel); - } - - e.preventDefault(); - }; - } else if (channel.fake) { - // It's a direct message channel that doesn't exist yet so let's create it now - var otherUserId = Utils.getUserIdFromChannelName(channel); - - if (this.state.loadingDMChannel === -1) { - handleClick = function clickHandler(e) { - e.preventDefault(); - - if (e.target.attributes.getNamedItem('data-close')) { - handleClose(channel); - } else { - this.setState({loadingDMChannel: index}); - - Client.createDirectChannel(channel, otherUserId, - (data) => { - this.setState({loadingDMChannel: -1}); - AsyncClient.getChannel(data.id); - Utils.switchChannel(data); - }, - () => { - this.setState({loadingDMChannel: -1}); - browserHistory('/' + this.state.currentTeam.name); - } - ); - } - }.bind(this); - } - } - let closeButton = null; const removeTooltip = ( <Tooltip id='remove-dm-tooltip'> @@ -464,12 +421,12 @@ export default class Sidebar extends React.Component { placement='top' overlay={removeTooltip} > - <span - className='btn-close' - data-close='true' - > - {'×'} - </span> + <span + onClick={() => handleClose(channel)} + className='btn-close' + > + {'×'} + </span> </OverlayTrigger> ); @@ -481,23 +438,29 @@ export default class Sidebar extends React.Component { tutorialTip = this.createTutorialTip(); } + let link = ''; + if (channel.fake) { + link = '/' + this.state.currentTeam.name + '/channels/' + channel.name + '?fakechannel=' + encodeURIComponent(JSON.stringify(channel)); + } else { + link = '/' + this.state.currentTeam.name + '/channels/' + channel.name; + } + return ( <li key={channel.name} ref={channel.name} className={linkClass} > - <a + <Link + to={link} className={rowClass} - href={'#'} - onClick={handleClick} > {icon} {status} {channel.display_name} {badge} {closeButton} - </a> + </Link> {tutorialTip} </li> ); @@ -600,6 +563,7 @@ export default class Sidebar extends React.Component { <div className='sidebar--left' id='sidebar-left' + key='sidebar-left' > <NewChannelFlow show={showChannelModal} diff --git a/webapp/components/sidebar_right.jsx b/webapp/components/sidebar_right.jsx index a2e3914f3..594674929 100644 --- a/webapp/components/sidebar_right.jsx +++ b/webapp/components/sidebar_right.jsx @@ -29,7 +29,7 @@ export default class SidebarRight extends React.Component { this.doStrangeThings = this.doStrangeThings.bind(this); this.state = { - searchVisible: !!SearchStore.getSearchResults(), + searchVisible: SearchStore.getSearchResults() !== null, isMentionSearch: SearchStore.getIsMentionSearch(), postRightVisible: !!PostStore.getSelectedPost(), fromSearch: false, @@ -111,7 +111,7 @@ export default class SidebarRight extends React.Component { } onSearchChange() { this.setState({ - searchVisible: !!SearchStore.getSearchResults(), + searchVisible: SearchStore.getSearchResults() !== null, isMentionSearch: SearchStore.getIsMentionSearch() }); } diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx index 776c84b48..c27e8ca59 100644 --- a/webapp/components/team_general_tab.jsx +++ b/webapp/components/team_general_tab.jsx @@ -84,6 +84,7 @@ class GeneralTab extends React.Component { } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.setState(this.setupInitialState(this.props)); this.props.updateSection(section); } diff --git a/webapp/components/team_settings_modal.jsx b/webapp/components/team_settings_modal.jsx index c19787993..657643367 100644 --- a/webapp/components/team_settings_modal.jsx +++ b/webapp/components/team_settings_modal.jsx @@ -62,6 +62,7 @@ class TeamSettingsModal extends React.Component { } } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.setState({activeSection: section}); } render() { diff --git a/webapp/components/textbox.jsx b/webapp/components/textbox.jsx index 371c581e5..63d5486a4 100644 --- a/webapp/components/textbox.jsx +++ b/webapp/components/textbox.jsx @@ -131,7 +131,7 @@ export default class Textbox extends React.Component { const helpText = ( <div style={{visibility: hasText ? 'visible' : 'hidden', opacity: hasText ? '0.5' : '0'}} - className='help_format_text' + className='help__format-text' > <b> <FormattedMessage @@ -208,8 +208,8 @@ export default class Textbox extends React.Component { dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.messageText) : ''}} > </div> - {helpText} <div className='help__text'> + {helpText} {previewLink} <a target='_blank' diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx index 913a30483..bad426cfc 100644 --- a/webapp/components/tutorial/tutorial_intro_screens.jsx +++ b/webapp/components/tutorial/tutorial_intro_screens.jsx @@ -2,7 +2,6 @@ // See License.txt for license information. import UserStore from 'stores/user_store.jsx'; -import ChannelStore from 'stores/channel_store.jsx'; import TeamStore from 'stores/team_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -11,6 +10,7 @@ import * as AsyncClient from 'utils/async_client.jsx'; import Constants from 'utils/constants.jsx'; import {FormattedMessage, FormattedHTMLMessage} from 'react-intl'; +import {browserHistory} from 'react-router'; const Preferences = Constants.Preferences; @@ -34,7 +34,7 @@ export default class TutorialIntroScreens extends React.Component { return; } - Utils.switchChannel(ChannelStore.getByName(Constants.DEFAULT_CHANNEL)); + browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square'); const step = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 0); @@ -52,6 +52,8 @@ export default class TutorialIntroScreens extends React.Component { UserStore.getCurrentId(), '999' ); + + browserHistory.push(TeamStore.getCurrentTeamUrl() + '/channels/town-square'); } createScreen() { switch (this.state.currentScreen) { diff --git a/webapp/components/tutorial/tutorial_view.jsx b/webapp/components/tutorial/tutorial_view.jsx new file mode 100644 index 000000000..d9e0ef40d --- /dev/null +++ b/webapp/components/tutorial/tutorial_view.jsx @@ -0,0 +1,19 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import TutorialIntroScreens from './tutorial_intro_screens.jsx'; + +export default class TutorialView extends React.Component { + render() { + return ( + <div + id='app-content' + className='app__content' + > + <TutorialIntroScreens/> + </div> + ); + } +} diff --git a/webapp/components/user_settings/premade_theme_chooser.jsx b/webapp/components/user_settings/premade_theme_chooser.jsx index c35748b41..326120957 100644 --- a/webapp/components/user_settings/premade_theme_chooser.jsx +++ b/webapp/components/user_settings/premade_theme_chooser.jsx @@ -7,6 +7,8 @@ import Constants from 'utils/constants.jsx'; import React from 'react'; +import {FormattedMessage} from 'react-intl'; + export default class PremadeThemeChooser extends React.Component { constructor(props) { super(props); @@ -50,6 +52,17 @@ export default class PremadeThemeChooser extends React.Component { return ( <div className='row appearance-section'> {premadeThemes} + <div className='col-sm-12 padding-bottom x2'> + <a + href='http://docs.mattermost.com/help/settings/theme-colors.html#custom-themes' + target='_blank' + > + <FormattedMessage + id='user.settings.display.theme.otherThemes' + defaultMessage='See other themes' + /> + </a> + </div> </div> ); } diff --git a/webapp/components/user_settings/user_settings_advanced.jsx b/webapp/components/user_settings/user_settings_advanced.jsx index 4fcdc9a41..61e0e1dad 100644 --- a/webapp/components/user_settings/user_settings_advanced.jsx +++ b/webapp/components/user_settings/user_settings_advanced.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import * as AsyncClient from 'utils/async_client.jsx'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; @@ -151,6 +152,7 @@ class AdvancedSettingsDisplay extends React.Component { } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.props.updateSection(section); } diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx index e56156049..d815bd371 100644 --- a/webapp/components/user_settings/user_settings_display.jsx +++ b/webapp/components/user_settings/user_settings_display.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import ManageLanguages from './manage_languages.jsx'; @@ -83,6 +84,7 @@ export default class UserSettingsDisplay extends React.Component { this.setState({selectedFont}); } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.updateState(); this.props.updateSection(section); } diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx index 2129847aa..87cce9dc0 100644 --- a/webapp/components/user_settings/user_settings_general.jsx +++ b/webapp/components/user_settings/user_settings_general.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import SettingPicture from '../setting_picture.jsx'; @@ -286,6 +287,7 @@ class UserSettingsGeneralTab extends React.Component { } } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); const emailChangeInProgress = this.state.emailChangeInProgress; this.setState(Object.assign({}, this.setupInitialState(this.props), {emailChangeInProgress, clientError: '', serverError: '', emailError: ''})); this.submitActive = false; diff --git a/webapp/components/user_settings/user_settings_integrations.jsx b/webapp/components/user_settings/user_settings_integrations.jsx index 94fc184bd..9061e34df 100644 --- a/webapp/components/user_settings/user_settings_integrations.jsx +++ b/webapp/components/user_settings/user_settings_integrations.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; import ManageIncomingHooks from './manage_incoming_hooks.jsx'; @@ -47,6 +48,7 @@ class UserSettingsIntegrationsTab extends React.Component { this.state = {}; } updateSection(section) { + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); this.props.updateSection(section); } render() { diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx index fe2db6727..b119c42f9 100644 --- a/webapp/components/user_settings/user_settings_notifications.jsx +++ b/webapp/components/user_settings/user_settings_notifications.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import $ from 'jquery'; import ReactDOM from 'react-dom'; import SettingItemMin from '../setting_item_min.jsx'; import SettingItemMax from '../setting_item_max.jsx'; @@ -162,6 +163,7 @@ class NotificationsTab extends React.Component { this.updateState(); this.props.updateSection(''); e.preventDefault(); + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); } updateSection(section) { this.updateState(); diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx index f937844ec..b34fa8582 100644 --- a/webapp/components/user_settings/user_settings_security.jsx +++ b/webapp/components/user_settings/user_settings_security.jsx @@ -204,6 +204,7 @@ class SecurityTab extends React.Component { this.props.updateSection(''); this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); e.preventDefault(); + $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update'); }.bind(this); return ( |