diff options
Diffstat (limited to 'web')
18 files changed, 115 insertions, 150 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 08c4a48ea..d5a46721e 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -40,7 +40,6 @@ export default class ChannelHeader extends React.Component { const state = this.getStateFromStores(); state.showEditChannelPurposeModal = false; - state.showInviteModal = false; state.showMembersModal = false; this.state = state; } @@ -201,13 +200,13 @@ export default class ChannelHeader extends React.Component { key='add_members' role='presentation' > - <a + <ToggleModalButton role='menuitem' - href='#' - onClick={() => this.setState({showInviteModal: true})} + dialogType={ChannelInviteModal} + dialogProps={{channel}} > {'Add Members'} - </a> + </ToggleModalButton> </li> ); @@ -402,13 +401,10 @@ export default class ChannelHeader extends React.Component { onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})} channel={channel} /> - <ChannelInviteModal - show={this.state.showInviteModal} - onModalDismissed={() => this.setState({showInviteModal: false})} - /> <ChannelMembersModal show={this.state.showMembersModal} onModalDismissed={() => this.setState({showMembersModal: false})} + channel={channel} /> </div> ); diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx index 0518ccb86..56e2e53f9 100644 --- a/web/react/components/channel_invite_modal.jsx +++ b/web/react/components/channel_invite_modal.jsx @@ -53,15 +53,8 @@ export default class ChannelInviteModal extends React.Component { return a.username.localeCompare(b.username); }); - var channelName = ''; - if (ChannelStore.getCurrent()) { - channelName = ChannelStore.getCurrent().display_name; - } - return { nonmembers, - memberIds, - channelName, loading }; } @@ -94,28 +87,14 @@ export default class ChannelInviteModal extends React.Component { } } handleInvite(userId) { - // Make sure the user isn't already a member of the channel - if (this.state.memberIds.indexOf(userId) > -1) { - return; - } - var data = {}; data.user_id = userId; - Client.addChannelMember(ChannelStore.getCurrentId(), data, + Client.addChannelMember( + this.props.channel.id, + data, () => { - var nonmembers = this.state.nonmembers; - var memberIds = this.state.memberIds; - - for (var i = 0; i < nonmembers.length; i++) { - if (userId === nonmembers[i].id) { - nonmembers[i].invited = true; - memberIds.push(userId); - break; - } - } - - this.setState({inviteError: null, memberIds, nonmembers}); + this.setState({inviteError: null}); AsyncClient.getChannelExtraInfo(); }, (err) => { @@ -157,10 +136,10 @@ export default class ChannelInviteModal extends React.Component { <Modal dialogClassName='more-modal' show={this.props.show} - onHide={this.props.onModalDismissed} + onHide={this.props.onHide} > <Modal.Header closeButton={true}> - <Modal.Title>{'Add New Members to '}<span className='name'>{this.state.channelName}</span></Modal.Title> + <Modal.Title>{'Add New Members to '}<span className='name'>{this.props.channel.display_nam}</span></Modal.Title> </Modal.Header> <Modal.Body ref='modalBody' @@ -173,7 +152,7 @@ export default class ChannelInviteModal extends React.Component { <button type='button' className='btn btn-default' - onClick={this.props.onModalDismissed} + onClick={this.props.onHide} > {'Close'} </button> @@ -185,5 +164,6 @@ export default class ChannelInviteModal extends React.Component { ChannelInviteModal.propTypes = { show: React.PropTypes.bool.isRequired, - onModalDismissed: React.PropTypes.func.isRequired + onHide: React.PropTypes.func.isRequired, + channel: React.PropTypes.object.isRequired }; diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx index f07fc166a..d1b9df988 100644 --- a/web/react/components/channel_members_modal.jsx +++ b/web/react/components/channel_members_modal.jsx @@ -69,16 +69,9 @@ export default class ChannelMembersModal extends React.Component { memberList.sort(compareByUsername); nonmemberList.sort(compareByUsername); - const channel = ChannelStore.getCurrent(); - let channelName = ''; - if (channel) { - channelName = channel.display_name; - } - return { nonmemberList, - memberList, - channelName + memberList }; } onShow() { @@ -169,7 +162,7 @@ export default class ChannelMembersModal extends React.Component { onHide={this.props.onModalDismissed} > <Modal.Header closeButton={true}> - <Modal.Title><span className='name'>{this.state.channelName}</span>{' Members'}</Modal.Title> + <Modal.Title><span className='name'>{this.props.channel.display_name}</span>{' Members'}</Modal.Title> <a className='btn btn-md btn-primary' href='#' @@ -205,7 +198,8 @@ export default class ChannelMembersModal extends React.Component { </Modal> <ChannelInviteModal show={this.state.showInviteModal} - onModalDismissed={() => this.setState({showInviteModal: false})} + onHide={() => this.setState({showInviteModal: false})} + channel={this.props.channel} /> </div> ); @@ -218,5 +212,6 @@ ChannelMembersModal.defaultProps = { ChannelMembersModal.propTypes = { show: React.PropTypes.bool.isRequired, - onModalDismissed: React.PropTypes.func.isRequired + onModalDismissed: React.PropTypes.func.isRequired, + channel: React.PropTypes.object.isRequired }; diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx index f5d5ab28b..f7f77f48a 100644 --- a/web/react/components/member_list_item.jsx +++ b/web/react/components/member_list_item.jsx @@ -31,9 +31,7 @@ export default class MemberListItem extends React.Component { var timestamp = UserStore.getCurrentUser().update_at; var invite; - if (member.invited && this.props.handleInvite) { - invite = <span className='member-role'>Added</span>; - } else if (this.props.handleInvite) { + if (this.props.handleInvite) { invite = ( <a onClick={this.handleInvite} diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 6c3bfc7db..3bdc9efac 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -44,7 +44,6 @@ export default class Navbar extends React.Component { state.showEditChannelPurposeModal = false; state.showEditChannelHeaderModal = false; state.showMembersModal = false; - state.showInviteModal = false; this.state = state; } getStateFromStores() { @@ -171,13 +170,13 @@ export default class Navbar extends React.Component { if (!isDirect && !ChannelStore.isDefault(channel)) { addMembersOption = ( <li role='presentation'> - <a + <ToggleModalButton role='menuitem' - href='#' - onClick={() => this.setState({showInviteModal: true})} + dialogType={ChannelInviteModal} + dialogProps={{channel}} > {'Add Members'} - </a> + </ToggleModalButton> </li> ); @@ -475,10 +474,7 @@ export default class Navbar extends React.Component { <ChannelMembersModal show={this.state.showMembersModal} onModalDismissed={() => this.setState({showMembersModal: false})} - /> - <ChannelInviteModal - show={this.state.showInviteModal} - onModalDismissed={() => this.setState({showInviteModal: false})} + channel={{channel}} /> </div> ); diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx index 6d6694fec..631bd1872 100644 --- a/web/react/components/posts_view_container.jsx +++ b/web/react/components/posts_view_container.jsx @@ -3,7 +3,6 @@ import PostsView from './posts_view.jsx'; import LoadingScreen from './loading_screen.jsx'; -import ChannelInviteModal from './channel_invite_modal.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import PostStore from '../stores/post_store.jsx'; @@ -13,7 +12,7 @@ import * as EventHelpers from '../dispatcher/event_helpers.jsx'; import Constants from '../utils/constants.jsx'; -import {createChannelIntroMessage} from '../utils/channel_intro_mssages.jsx'; +import {createChannelIntroMessage} from '../utils/channel_intro_messages.jsx'; export default class PostsViewContainer extends React.Component { constructor() { @@ -177,7 +176,7 @@ export default class PostsViewContainer extends React.Component { loadMorePostsBottomClicked={() => {}} showMoreMessagesTop={!this.state.atTop[this.state.currentChannelIndex]} showMoreMessagesBottom={false} - introText={channel ? createChannelIntroMessage(channel, () => this.setState({showInviteModal: true})) : null} + introText={channel ? createChannelIntroMessage(channel) : null} messageSeparatorTime={this.state.currentLastViewed} /> ); @@ -194,10 +193,6 @@ export default class PostsViewContainer extends React.Component { return ( <div id='post-list'> {postListCtls} - <ChannelInviteModal - show={this.state.showInviteModal} - onModalDismissed={() => this.setState({showInviteModal: false})} - /> </div> ); } diff --git a/web/react/components/suggestion/search_suggestion_list.jsx b/web/react/components/suggestion/search_suggestion_list.jsx index 542d28ddd..3378a33a0 100644 --- a/web/react/components/suggestion/search_suggestion_list.jsx +++ b/web/react/components/suggestion/search_suggestion_list.jsx @@ -35,7 +35,7 @@ export default class SearchSuggestionList extends SuggestionList { } render() { - if (this.state.items.length === 0 || !this.props.show) { + if (this.state.items.length === 0) { return null; } diff --git a/web/react/components/suggestion/suggestion_box.jsx b/web/react/components/suggestion/suggestion_box.jsx index 4ca461e82..4cfb38f8e 100644 --- a/web/react/components/suggestion/suggestion_box.jsx +++ b/web/react/components/suggestion/suggestion_box.jsx @@ -13,7 +13,6 @@ export default class SuggestionBox extends React.Component { super(props); this.handleDocumentClick = this.handleDocumentClick.bind(this); - this.handleFocus = this.handleFocus.bind(this); this.handleChange = this.handleChange.bind(this); this.handleCompleteWord = this.handleCompleteWord.bind(this); @@ -21,10 +20,6 @@ export default class SuggestionBox extends React.Component { this.handlePretextChanged = this.handlePretextChanged.bind(this); this.suggestionId = Utils.generateId(); - - this.state = { - focused: false - }; } componentDidMount() { @@ -49,27 +44,11 @@ export default class SuggestionBox extends React.Component { } handleDocumentClick(e) { - if (!this.state.focused) { - return; - } - const container = $(ReactDOM.findDOMNode(this)); if (!(container.is(e.target) || container.has(e.target).length > 0)) { // we can't just use blur for this because it fires and hides the children before // their click handlers can be called - this.setState({ - focused: false - }); - } - } - - handleFocus() { - this.setState({ - focused: true - }); - - if (this.props.onFocus) { - this.props.onFocus(); + EventHelpers.emitClearSuggestions(this.suggestionId); } } @@ -134,7 +113,6 @@ export default class SuggestionBox extends React.Component { render() { const newProps = Object.assign({}, this.props, { - onFocus: this.handleFocus, onChange: this.handleChange, onKeyDown: this.handleKeyDown }); @@ -162,10 +140,7 @@ export default class SuggestionBox extends React.Component { return ( <div> {textbox} - <SuggestionListComponent - suggestionId={this.suggestionId} - show={this.state.focused} - /> + <SuggestionListComponent suggestionId={this.suggestionId} /> </div> ); } @@ -184,6 +159,5 @@ SuggestionBox.propTypes = { // explicitly name any input event handlers we override and need to manually call onChange: React.PropTypes.func, - onKeyDown: React.PropTypes.func, - onFocus: React.PropTypes.func + onKeyDown: React.PropTypes.func }; diff --git a/web/react/components/suggestion/suggestion_list.jsx b/web/react/components/suggestion/suggestion_list.jsx index 87021fd94..e3ccd0f08 100644 --- a/web/react/components/suggestion/suggestion_list.jsx +++ b/web/react/components/suggestion/suggestion_list.jsx @@ -82,7 +82,7 @@ export default class SuggestionList extends React.Component { } render() { - if (this.state.items.length === 0 || !this.props.show) { + if (this.state.items.length === 0) { return null; } @@ -121,6 +121,5 @@ export default class SuggestionList extends React.Component { } SuggestionList.propTypes = { - suggestionId: React.PropTypes.string.isRequired, - show: React.PropTypes.bool.isRequired + suggestionId: React.PropTypes.string.isRequired }; diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx index dc3865c68..c464258de 100644 --- a/web/react/components/user_settings/user_settings_display.jsx +++ b/web/react/components/user_settings/user_settings_display.jsx @@ -241,7 +241,7 @@ export default class UserSettingsDisplay extends React.Component { const inputs = [ <div key='userDisplayNameOptions'> <div - className='input-group theme-group dropdown' + className='dropdown' > <select className='form-control' @@ -251,9 +251,6 @@ export default class UserSettingsDisplay extends React.Component { > {options} </select> - <span className={'input-group-addon ' + Constants.FONTS[this.state.selectedFont]}> - {this.state.selectedFont} - </span> </div> <div><br/>{'Select the font displayed in the Mattermost user interface.'}</div> </div> @@ -312,12 +309,12 @@ export default class UserSettingsDisplay extends React.Component { <div className='user-settings'> <h3 className='tab-header'>{'Display Settings'}</h3> <div className='divider-dark first'/> + {fontSection} + <div className='divider-dark'/> {clockSection} <div className='divider-dark'/> {nameFormatSection} <div className='divider-dark'/> - {fontSection} - <div className='divider-dark'/> </div> </div> ); diff --git a/web/react/dispatcher/event_helpers.jsx b/web/react/dispatcher/event_helpers.jsx index f792c610f..3deddd754 100644 --- a/web/react/dispatcher/event_helpers.jsx +++ b/web/react/dispatcher/event_helpers.jsx @@ -141,3 +141,10 @@ export function emitCompleteWordSuggestion(suggestionId, term = '') { term }); } + +export function emitClearSuggestions(suggestionId) { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS, + id: suggestionId + }); +} diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index 2e3a26cff..ff6ae45ea 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -1,6 +1,8 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import {generateId} from '../utils/utils.jsx'; + function getPrefix() { if (global.window.mm_user) { return global.window.mm_user.id + '_'; @@ -26,6 +28,7 @@ class BrowserStoreClass { this.clearAll = this.clearAll.bind(this); this.checkedLocalStorageSupported = ''; this.signalLogout = this.signalLogout.bind(this); + this.isSignallingLogout = this.isSignallingLogout.bind(this); var currentVersion = sessionStorage.getItem('storage_version'); if (currentVersion !== global.window.mm_config.Version) { @@ -113,11 +116,19 @@ class BrowserStoreClass { signalLogout() { if (this.isLocalStorageSupported()) { - localStorage.setItem('__logout__', 'yes'); + // PLT-1285 store an identifier in session storage so we can catch if the logout came from this tab on IE11 + const logoutId = generateId(); + + sessionStorage.setItem('__logout__', logoutId); + localStorage.setItem('__logout__', logoutId); localStorage.removeItem('__logout__'); } } + isSignallingLogout(logoutId) { + return logoutId === sessionStorage.getItem('__logout__'); + } + /** * Preforms the given action on each item that has the given prefix * Signature for action is action(key, value) @@ -151,7 +162,14 @@ class BrowserStoreClass { } clear() { + // don't clear the logout id so IE11 can tell which tab sent a logout request + const logoutId = sessionStorage.getItem('__logout__'); + sessionStorage.clear(); + + if (logoutId) { + sessionStorage.setItem('__logout__', logoutId); + } } clearAll() { @@ -185,3 +203,4 @@ class BrowserStoreClass { var BrowserStore = new BrowserStoreClass(); export default BrowserStore; +window.BrowserStore = BrowserStore; diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 5dec86951..0bfde77b4 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -167,18 +167,7 @@ class ChannelStoreClass extends EventEmitter { this.emitChange(); } getCurrentExtraInfo() { - var currentId = this.getCurrentId(); - var extra = null; - - if (currentId) { - extra = this.pGetExtraInfos()[currentId]; - } - - if (extra == null) { - extra = {members: []}; - } - - return extra; + return this.getExtraInfo(this.getCurrentId()); } getExtraInfo(channelId) { var extra = null; @@ -187,7 +176,10 @@ class ChannelStoreClass extends EventEmitter { extra = this.pGetExtraInfos()[channelId]; } - if (extra == null) { + if (extra) { + // create a defensive copy + extra = JSON.parse(JSON.stringify(extra)); + } else { extra = {members: []}; } diff --git a/web/react/stores/suggestion_store.jsx b/web/react/stores/suggestion_store.jsx index 182f5810f..2250ec234 100644 --- a/web/react/stores/suggestion_store.jsx +++ b/web/react/stores/suggestion_store.jsx @@ -244,6 +244,11 @@ class SuggestionStore extends EventEmitter { this.emitSuggestionsChanged(id); } break; + case ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS: + this.clearSuggestions(id); + this.clearSelection(id); + this.emitSuggestionsChanged(id); + break; case ActionTypes.SUGGESTION_SELECT_NEXT: this.selectNext(id); this.emitSuggestionsChanged(id); diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_messages.jsx index 6f83778c9..9685f94b0 100644 --- a/web/react/utils/channel_intro_mssages.jsx +++ b/web/react/utils/channel_intro_messages.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. import * as Utils from './utils.jsx'; +import ChannelInviteModal from '../components/channel_invite_modal.jsx'; import EditChannelHeaderModal from '../components/edit_channel_header_modal.jsx'; import ToggleModalButton from '../components/toggle_modal_button.jsx'; import UserProfile from '../components/user_profile.jsx'; @@ -10,15 +11,15 @@ import Constants from '../utils/constants.jsx'; import TeamStore from '../stores/team_store.jsx'; import * as EventHelpers from '../dispatcher/event_helpers.jsx'; -export function createChannelIntroMessage(channel, showInviteModal) { +export function createChannelIntroMessage(channel) { if (channel.type === 'D') { return createDMIntroMessage(channel); } else if (ChannelStore.isDefault(channel)) { return createDefaultIntroMessage(channel); } else if (channel.name === Constants.OFFTOPIC_CHANNEL) { - return createOffTopicIntroMessage(channel, showInviteModal); + return createOffTopicIntroMessage(channel); } else if (channel.type === 'O' || channel.type === 'P') { - return createStandardIntroMessage(channel, showInviteModal); + return createStandardIntroMessage(channel); } } @@ -62,7 +63,7 @@ export function createDMIntroMessage(channel) { ); } -export function createOffTopicIntroMessage(channel, showInviteModal) { +export function createOffTopicIntroMessage(channel) { return ( <div className='channel-intro'> <h4 className='channel-intro__title'>{'Beginning of ' + channel.display_name}</h4> @@ -71,13 +72,7 @@ export function createOffTopicIntroMessage(channel, showInviteModal) { <br/> </p> {createSetHeaderButton(channel)} - <a - href='#' - className='intro-links' - onClick={showInviteModal} - > - <i className='fa fa-user-plus'></i>{'Invite others to this channel'} - </a> + {createInviteChannelMemberButton(channel, 'channel')} </div> ); } @@ -122,7 +117,7 @@ export function createDefaultIntroMessage(channel) { ); } -export function createStandardIntroMessage(channel, showInviteModal) { +export function createStandardIntroMessage(channel) { var uiName = channel.display_name; var creatorName = ''; @@ -162,17 +157,23 @@ export function createStandardIntroMessage(channel, showInviteModal) { <br/> </p> {createSetHeaderButton(channel)} - <a - className='intro-links' - href='#' - onClick={showInviteModal} - > - <i className='fa fa-user-plus'></i>{'Invite others to this ' + uiType} - </a> + {createInviteChannelMemberButton(channel, uiType)} </div> ); } +function createInviteChannelMemberButton(channel, uiType) { + return ( + <ToggleModalButton + className='intro-links' + dialogType={ChannelInviteModal} + dialogProps={{channel}} + > + <i className='fa fa-user-plus'></i>{'Invite others to this ' + uiType} + </ToggleModalButton> + ); +} + function createSetHeaderButton(channel) { return ( <ToggleModalButton diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 8164095b9..b641e966b 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -55,6 +55,7 @@ export default { SUGGESTION_PRETEXT_CHANGED: null, SUGGESTION_RECEIVED_SUGGESTIONS: null, + SUGGESTION_CLEAR_SUGGESTIONS: null, SUGGESTION_COMPLETE_WORD: null, SUGGESTION_SELECT_NEXT: null, SUGGESTION_SELECT_PREVIOUS: null diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 788d8a45c..0a52f5b37 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -695,15 +695,19 @@ export function applyTheme(theme) { } export function applyFont(fontName) { - const body = document.querySelector('body'); - const keys = Object.getOwnPropertyNames(body.classList); - keys.forEach((k) => { - const className = body.classList[k]; - if (className && className.lastIndexOf('font') === 0) { - body.classList.remove(className); + const body = $('body'); + + for (const key of Reflect.ownKeys(Constants.FONTS)) { + const className = Constants.FONTS[key]; + + if (fontName === key) { + if (!body.hasClass(className)) { + body.addClass(className); + } + } else { + body.removeClass(className); } - }); - body.classList.add(Constants.FONTS[fontName]); + } } export function changeCss(className, classValue, classRepeat) { @@ -1238,4 +1242,4 @@ export function getPostTerm(post) { export function isFeatureEnabled(feature) { return PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, Constants.FeatureTogglePrefix + feature.label, {value: 'false'}).value === 'true'; -}
\ No newline at end of file +} diff --git a/web/templates/head.html b/web/templates/head.html index 709edbaec..be4ed2b25 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -58,7 +58,13 @@ $(function () { $(window).bind('storage', function (e) { - if (e.originalEvent.key === '__logout__') { + // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out + if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) { + // make sure it isn't this tab that is sending the logout signal (only necessary for IE11) + if (window.BrowserStore.isSignallingLogout(e.originalEvent.newValue)) { + return; + } + console.log('detected logout from a different tab'); window.location.href = '/' + window.mm_team.name; } |