diff options
Diffstat (limited to 'web')
96 files changed, 7442 insertions, 4654 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index a19e5c16e..9c8e7c6c3 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -4,48 +4,49 @@ var UserStore = require('../stores/user_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var LoadingScreen = require('./loading_screen.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -function getStateFromStoresForAudits() { - return { - audits: UserStore.getAudits() - }; -} +export default class AccessHistoryModal extends React.Component { + constructor(props) { + super(props); + + this.onAuditChange = this.onAuditChange.bind(this); + this.handleMoreInfo = this.handleMoreInfo.bind(this); -module.exports = React.createClass({ - displayName: 'AccessHistoryModal', - componentDidMount: function() { - UserStore.addAuditsChangeListener(this.onListenerChange); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() { + this.state = this.getStateFromStoresForAudits(); + this.state.moreInfo = []; + } + getStateFromStoresForAudits() { + return { + audits: UserStore.getAudits() + }; + } + componentDidMount() { + UserStore.addAuditsChangeListener(this.onAuditChange); + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function show() { AsyncClient.getAudits(); }); - var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() { + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function hide() { $('#user_settings').modal('show'); - self.setState({moreInfo: []}); - }); - }, - componentWillUnmount: function() { - UserStore.removeAuditsChangeListener(this.onListenerChange); - }, - onListenerChange: function() { - var newState = getStateFromStoresForAudits(); - if (!utils.areStatesEqual(newState.audits, this.state.audits)) { + this.setState({moreInfo: []}); + }.bind(this)); + } + componentWillUnmount() { + UserStore.removeAuditsChangeListener(this.onAuditChange); + } + onAuditChange() { + var newState = this.getStateFromStoresForAudits(); + if (!Utils.areStatesEqual(newState.audits, this.state.audits)) { this.setState(newState); } - }, - handleMoreInfo: function(index) { + } + handleMoreInfo(index) { var newMoreInfo = this.state.moreInfo; newMoreInfo[index] = true; this.setState({moreInfo: newMoreInfo}); - }, - getInitialState: function() { - var initialState = getStateFromStoresForAudits(); - initialState.moreInfo = []; - return initialState; - }, - render: function() { + } + render() { var accessList = []; var currentHistoryDate = null; @@ -63,7 +64,16 @@ module.exports = React.createClass({ currentAudit.session_id = 'N/A (Login attempt)'; } - var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>); + var moreInfo = ( + <a + href='#' + className='theme' + onClick={this.handleMoreInfo.bind(this, i)} + > + More info + </a> + ); + if (this.state.moreInfo[i]) { moreInfo = ( <div> @@ -75,7 +85,7 @@ module.exports = React.createClass({ var divider = null; if (i < this.state.audits.length - 1) { - divider = (<div className='divider-light'></div>) + divider = (<div className='divider-light'></div>); } accessList[i] = ( @@ -102,14 +112,36 @@ module.exports = React.createClass({ return ( <div> - <div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'> + <div + className='modal fade' + ref='modal' + id='access-history' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > <div className='modal-dialog modal-lg'> <div className='modal-content'> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' id='myModalLabel'>Access History</h4> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + id='myModalLabel' + > + Access History + </h4> </div> - <div ref='modalBody' className='modal-body'> + <div + ref='modalBody' + className='modal-body' + > {content} </div> </div> @@ -118,4 +150,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 1192a72bc..2a83b3c40 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -1,102 +1,104 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); -var Client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var LoadingScreen = require('./loading_screen.jsx'); -var utils = require('../utils/utils.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const LoadingScreen = require('./loading_screen.jsx'); +const Utils = require('../utils/utils.jsx'); -function getStateFromStoresForSessions() { - return { - sessions: UserStore.getSessions(), - serverError: null, - clientError: null - }; -} +export default class ActivityLogModal extends React.Component { + constructor(props) { + super(props); + + this.submitRevoke = this.submitRevoke.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + this.handleMoreInfo = this.handleMoreInfo.bind(this); -module.exports = React.createClass({ - displayName: 'ActivityLogModal', - submitRevoke: function(altId) { + this.state = this.getStateFromStores(); + this.state.moreInfo = []; + } + getStateFromStores() { + return { + sessions: UserStore.getSessions(), + serverError: null, + clientError: null + }; + } + submitRevoke(altId) { Client.revokeSession(altId, - function(data) { + function handleRevokeSuccess() { AsyncClient.getSessions(); - }.bind(this), - function(err) { - var state = getStateFromStoresForSessions(); + }, + function handleRevokeError(err) { + let state = this.getStateFromStores(); state.serverError = err; this.setState(state); }.bind(this) ); - }, - componentDidMount: function() { + } + componentDidMount() { UserStore.addSessionsChangeListener(this.onListenerChange); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() { AsyncClient.getSessions(); }); - var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { $('#user_settings').modal('show'); - self.setState({moreInfo: []}); - }); - }, - componentWillUnmount: function() { + this.setState({moreInfo: []}); + }.bind(this)); + } + componentWillUnmount() { UserStore.removeSessionsChangeListener(this.onListenerChange); - }, - onListenerChange: function() { - var newState = getStateFromStoresForSessions(); - if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) { + } + onListenerChange() { + const newState = this.getStateFromStores(); + if (!Utils.areStatesEqual(newState.sessions, this.state.sessions)) { this.setState(newState); } - }, - handleMoreInfo: function(index) { - var newMoreInfo = this.state.moreInfo; + } + handleMoreInfo(index) { + let newMoreInfo = this.state.moreInfo; newMoreInfo[index] = true; this.setState({moreInfo: newMoreInfo}); - }, - getInitialState: function() { - var initialState = getStateFromStoresForSessions(); - initialState.moreInfo = []; - return initialState; - }, - render: function() { - var activityList = []; - var serverError = this.state.serverError; - - // Squash any false-y value for server error into null - if (!serverError) { - serverError = null; - } + } + render() { + let activityList = []; - for (var i = 0; i < this.state.sessions.length; i++) { - var currentSession = this.state.sessions[i]; - var lastAccessTime = new Date(currentSession.last_activity_at); - var firstAccessTime = new Date(currentSession.create_at); - var devicePicture = ''; + for (let i = 0; i < this.state.sessions.length; i++) { + const currentSession = this.state.sessions[i]; + const lastAccessTime = new Date(currentSession.last_activity_at); + const firstAccessTime = new Date(currentSession.create_at); + let devicePicture = ''; if (currentSession.props.platform === 'Windows') { devicePicture = 'fa fa-windows'; - } - else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { + } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { devicePicture = 'fa fa-apple'; - } - else if (currentSession.props.platform === 'Linux') { + } else if (currentSession.props.platform === 'Linux') { devicePicture = 'fa fa-linux'; } - var moreInfo; + let moreInfo; if (this.state.moreInfo[i]) { moreInfo = ( <div> - <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> - <div>{'OS: ' + currentSession.props.os}</div> - <div>{'Browser: ' + currentSession.props.browser}</div> - <div>{'Session ID: ' + currentSession.alt_id}</div> + <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div> + <div>{`OS: ${currentSession.props.os}`}</div> + <div>{`Browser: ${currentSession.props.browser}`}</div> + <div>{`Session ID: ${currentSession.alt_id}`}</div> </div> ); } else { - moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>); + moreInfo = ( + <a + className='theme' + href='#' + onClick={this.handleMoreInfo.bind(this, i)} + > + More info + </a> + ); } activityList[i] = ( @@ -104,33 +106,62 @@ module.exports = React.createClass({ <div className='activity-log__report'> <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div> <div className='report__info'> - <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> + <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div> {moreInfo} </div> </div> - <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div> + <div className='activity-log__action'> + <button + onClick={this.submitRevoke.bind(this, currentSession.alt_id)} + className='btn btn-primary' + > + Logout + </button> + </div> </div> ); } - var content; + let content; if (this.state.sessions.loading) { - content = (<LoadingScreen />); + content = <LoadingScreen />; } else { - content = (<form role='form'>{activityList}</form>); + content = <form role='form'>{activityList}</form>; } return ( <div> - <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'> + <div + className='modal fade' + ref='modal' + id='activity-log' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > <div className='modal-dialog modal-lg'> <div className='modal-content'> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + id='myModalLabel' + > + Active Sessions + </h4> </div> <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p> - <div ref='modalBody' className='modal-body'> + <div + ref='modalBody' + className='modal-body' + > {content} </div> </div> @@ -139,4 +170,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 0254d0e82..87b9cab04 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -1,139 +1,85 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var ChannelStore = require('../stores/channel_store.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var PostStore = require('../stores/post_store.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); -var NavbarSearchBox = require('./search_bar.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var Client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); -var MessageWrapper = require('./message_wrapper.jsx'); - -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; - -var PopoverListMembers = React.createClass({ - componentDidMount: function() { - var originalLeave = $.fn.popover.Constructor.prototype.leave; - $.fn.popover.Constructor.prototype.leave = function(obj) { - var selfObj; - if (obj instanceof this.constructor) { - selfObj = obj; - } else { - selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type); - } - originalLeave.call(this, obj); - - if (obj.currentTarget && selfObj.$tip) { - selfObj.$tip.one('mouseenter', function() { - clearTimeout(selfObj.timeout); - selfObj.$tip.one('mouseleave', function() { - $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj); - }); - }); - } - }; +const ChannelStore = require('../stores/channel_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const SocketStore = require('../stores/socket_store.jsx'); +const NavbarSearchBox = require('./search_bar.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const Client = require('../utils/client.jsx'); +const Utils = require('../utils/utils.jsx'); +const MessageWrapper = require('./message_wrapper.jsx'); +const PopoverListMembers = require('./popover_list_members.jsx'); - $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); - $('body').on('click', function(e) { - if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { - $('#member_popover').popover('hide'); - } - }); - }, - - render: function() { - var popoverHtml = ''; - var members = this.props.members; - var count; - if (members.length > 20) { - count = '20+'; - } else { - count = members.length || '-'; - } +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; - if (members) { - members.sort(function(a, b) { - return a.username.localeCompare(b.username); - }); +export default class ChannelHeader extends React.Component { + constructor(props) { + super(props); - members.forEach(function(m) { - popoverHtml += "<div class='text--nowrap'>" + m.username + '</div>'; - }); - } + this.onListenerChange = this.onListenerChange.bind(this); + this.onSocketChange = this.onSocketChange.bind(this); + this.handleLeave = this.handleLeave.bind(this); + this.searchMentions = this.searchMentions.bind(this); - return ( - <div id='member_popover' data-toggle='popover' data-content={popoverHtml} data-original-title='Members' > - <div id='member_tooltip' data-placement='left' data-toggle='tooltip' title='View Channel Members'> - {count} <span className='glyphicon glyphicon-user' aria-hidden='true'></span> - </div> - </div> - ); + this.state = this.getStateFromStores(); } -}); - -function getStateFromStores() { - return { - channel: ChannelStore.getCurrent(), - memberChannel: ChannelStore.getCurrentMember(), - memberTeam: UserStore.getCurrentUser(), - users: ChannelStore.getCurrentExtraInfo().members, - searchVisible: PostStore.getSearchResults() != null - }; -} - -module.exports = React.createClass({ - displayName: 'ChannelHeader', - componentDidMount: function() { + getStateFromStores() { + return { + channel: ChannelStore.getCurrent(), + memberChannel: ChannelStore.getCurrentMember(), + memberTeam: UserStore.getCurrentUser(), + users: ChannelStore.getCurrentExtraInfo().members, + searchVisible: PostStore.getSearchResults() !== null + }; + } + componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); ChannelStore.addExtraInfoChangeListener(this.onListenerChange); PostStore.addSearchChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange); SocketStore.addChangeListener(this.onSocketChange); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); ChannelStore.removeExtraInfoChangeListener(this.onListenerChange); PostStore.removeSearchChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange); - }, - onListenerChange: function() { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { + } + onListenerChange() { + const newState = this.getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { this.setState(newState); } $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}}); - }, - onSocketChange: function(msg) { + } + onSocketChange(msg) { if (msg.action === 'new_user') { AsyncClient.getChannelExtraInfo(true); } - }, - getInitialState: function() { - return getStateFromStores(); - }, - handleLeave: function() { + } + handleLeave() { Client.leaveChannel(this.state.channel.id, - function() { - var townsquare = ChannelStore.getByName('town-square'); - utils.switchChannel(townsquare); + function handleLeaveSuccess() { + const townsquare = ChannelStore.getByName('town-square'); + Utils.switchChannel(townsquare); }, - function(err) { + function handleLeaveError(err) { AsyncClient.dispatchError(err, 'handleLeave'); } ); - }, - searchMentions: function(e) { + } + searchMentions(e) { e.preventDefault(); - var user = UserStore.getCurrentUser(); + const user = UserStore.getCurrentUser(); - var terms = ''; + let terms = ''; if (user.notify_props && user.notify_props.mention_keys) { - var termKeys = UserStore.getCurrentMentionKeys(); + let termKeys = UserStore.getCurrentMentionKeys(); if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) { termKeys.splice(termKeys.indexOf('@all'), 1); } @@ -149,23 +95,23 @@ module.exports = React.createClass({ do_search: true, is_mention_search: true }); - }, - render: function() { - if (this.state.channel == null) { + } + render() { + if (this.state.channel === null) { return null; } - var channel = this.state.channel; - var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true}); - var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); - var channelTitle = channel.display_name; - var currentId = UserStore.getCurrentId(); - var isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1; - var isDirect = (this.state.channel.type === 'D'); + const channel = this.state.channel; + const description = Utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true}); + const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>); + let channelTitle = channel.display_name; + const currentId = UserStore.getCurrentId(); + const isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1; + const isDirect = (this.state.channel.type === 'D'); if (isDirect) { if (this.state.users.length > 1) { - var contact; + let contact; if (this.state.users[0].id === currentId) { contact = this.state.users[1]; } else { @@ -175,64 +121,244 @@ module.exports = React.createClass({ } } - var channelTerm = 'Channel'; + let channelTerm = 'Channel'; if (channel.type === 'P') { channelTerm = 'Group'; } + let dropdownContents = []; + if (!isDirect) { + dropdownContents.push( + <li + key='view_info' + role='presentation' + > + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_info' + data-channelid={channel.id} + href='#' + > + View Info + </a> + </li> + ); + + if (!ChannelStore.isDefault(channel)) { + dropdownContents.push( + <li + key='add_members' + role='presentation' + > + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_invite' + href='#' + > + Add Members + </a> + </li> + ); + + if (isAdmin) { + dropdownContents.push( + <li + key='manage_members' + role='presentation' + > + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_members' + href='#' + > + Manage Members + </a> + </li> + ); + } + } + + dropdownContents.push( + <li + key='set_channel_description' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#edit_channel' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + > + Set {channelTerm} Description... + </a> + </li> + ); + dropdownContents.push( + <li + key='notification_preferences' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#channel_notifications' + data-title={channel.display_name} + data-channelid={channel.id} + > + Notification Preferences + </a> + </li> + ); + + if (!ChannelStore.isDefault(channel)) { + if (isAdmin) { + dropdownContents.push( + <li + key='rename_channel' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#rename_channel' + data-display={channel.display_name} + data-name={channel.name} + data-channelid={channel.id} + > + Rename {channelTerm}... + </a> + </li> + ); + dropdownContents.push( + <li + key='delete_channel' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#delete_channel' + data-title={channel.display_name} + data-channelid={channel.id} + > + Delete {channelTerm}... + </a> + </li> + ); + } + + dropdownContents.push( + <li + key='leave_channel' + role='presentation' + > + <a + role='menuitem' + href='#' + onClick={this.handleLeave} + > + Leave {channelTerm} + </a> + </li> + ); + } + } else { + dropdownContents.push( + <li + key='edit_description_direct' + role='presentation' + > + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#edit_channel' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + > + Set Channel Description... + </a> + </li> + ); + } + return ( <table className='channel-header alt'> <tr> <th> <div className='channel-header__info'> <div className='dropdown'> - <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_dropdown' + data-toggle='dropdown' + aria-expanded='true' + > <strong className='heading'>{channelTitle} </strong> - <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span> + <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' /> </a> - {!isDirect ? - <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'> - <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li> - {!ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li> - : null - } - {isAdmin && !ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li> - : null - } - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set {channelTerm} Description...</a></li> - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li> - {isAdmin && !ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename {channelTerm}...</a></li> - : null - } - {isAdmin && !ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete {channelTerm}...</a></li> - : null - } - {!ChannelStore.isDefault(channel) ? - <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave {channelTerm}</a></li> - : null - } - </ul> - : - <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'> - <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li> + <ul + className='dropdown-menu' + role='menu' + aria-labelledby='channel_header_dropdown' + > + {dropdownContents} </ul> - } </div> - <div data-toggle='popover' data-content={popoverContent} className='description'>{description}</div> + <div + data-toggle='popover' + data-content={popoverContent} + className='description' + > + {description} + </div> </div> </th> - <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th> + <th> + <PopoverListMembers + members={this.state.users} + channelId={channel.id} + /> + </th> <th className='search-bar__container'><NavbarSearchBox /></th> <th> <div className='dropdown channel-header__links'> - <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_right_dropdown' data-toggle='dropdown' aria-expanded='true'> - <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a> - <ul className='dropdown-menu dropdown-menu-right' role='menu' aria-labelledby='channel_header_right_dropdown'> - <li role='presentation'><a role='menuitem' href='#' onClick={this.searchMentions}>Recent Mentions</a></li> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_right_dropdown' + data-toggle='dropdown' + aria-expanded='true' + > + <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> + </a> + <ul + className='dropdown-menu dropdown-menu-right' + role='menu' + aria-labelledby='channel_header_right_dropdown' + > + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.searchMentions} + > + Recent Mentions + </a> + </li> </ul> </div> </th> @@ -240,4 +366,4 @@ module.exports = React.createClass({ </table> ); } -}); +} diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 0fa433383..8e8ed3f73 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -10,13 +10,18 @@ var SocketStore = require('../stores/socket_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var PostStore = require('../stores/post_store.jsx'); var UserStore = require('../stores/user_store.jsx'); -var Constants = require('../utils/constants.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - componentDidMount: function() { +export default class ChannelLoader extends React.Component { + constructor(props) { + super(props); + this.onSocketChange = this.onSocketChange.bind(this); + + this.state = {}; + } + componentDidMount() { /* Initial aysnc loads */ AsyncClient.getMe(); AsyncClient.getPosts(ChannelStore.getCurrentId()); @@ -60,32 +65,32 @@ module.exports = React.createClass({ var user = UserStore.getCurrentUser(); if (user.props && user.props.theme) { - utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); - utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); - utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); - utils.changeCss('.mention', 'background: ' + user.props.theme + ';'); - utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';'); - utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}'); - utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';'); + Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); + Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.mention', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';'); + Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}'); + Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';'); } if (user.props.theme !== '#000000' && user.props.theme !== '#585858') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) + ';'); - utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';'); + Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;'); } else if (user.props.theme === '#000000') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) + ';'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';'); $('.team__header').addClass('theme--black'); } else if (user.props.theme === '#585858') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) + ';'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';'); $('.team__header').addClass('theme--gray'); } - }, - onSocketChange: function(msg) { + } + onSocketChange(msg) { if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) { UserStore.setStatus(msg.user_id, 'online'); } - }, - render: function() { + } + render() { return <div/>; } -}); +} diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx index db4bec400..04fa2c7a2 100644 --- a/web/react/components/channel_members.jsx +++ b/web/react/components/channel_members.jsx @@ -1,154 +1,200 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var MemberList = require('./member_list.jsx'); -var client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); - -function getStateFromStores() { - var users = UserStore.getActiveOnlyProfiles(); - var member_list = ChannelStore.getCurrentExtraInfo().members; - - var nonmember_list = []; - for (var id in users) { - var found = false; - for (var i = 0; i < member_list.length; i++) { - if (member_list[i].id === id) { - found = true; - break; +const UserStore = require('../stores/user_store.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const MemberList = require('./member_list.jsx'); +const Client = require('../utils/client.jsx'); +const Utils = require('../utils/utils.jsx'); + +export default class ChannelMembers extends React.Component { + constructor(props) { + super(props); + + this.getStateFromStores = this.getStateFromStores.bind(this); + this.onChange = this.onChange.bind(this); + this.handleRemove = this.handleRemove.bind(this); + + this.state = this.getStateFromStores(); + } + getStateFromStores() { + const users = UserStore.getActiveOnlyProfiles(); + let memberList = ChannelStore.getCurrentExtraInfo().members; + + let nonmemberList = []; + for (let id in users) { + if (users.hasOwnProperty(id)) { + let found = false; + for (let i = 0; i < memberList.length; i++) { + if (memberList[i].id === id) { + found = true; + break; + } + } + if (!found) { + nonmemberList.push(users[id]); + } } } - if (!found) { - nonmember_list.push(users[id]); + + function compareByUsername(a, b) { + if (a.username < b.username) { + return -1; + } else if (a.username > b.username) { + return 1; + } + + return 0; } - } - member_list.sort(function(a,b) { - if (a.username < b.username) return -1; - if (a.username > b.username) return 1; - return 0; - }); - - nonmember_list.sort(function(a,b) { - if (a.username < b.username) return -1; - if (a.username > b.username) return 1; - return 0; - }); - - var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : ""; - - return { - nonmember_list: nonmember_list, - member_list: member_list, - channel_name: channel_name - }; -} + memberList.sort(compareByUsername); + nonmemberList.sort(compareByUsername); + + const channel = ChannelStore.getCurrent(); + let channelName = ''; + if (channel) { + channelName = channel.display_name; + } -module.exports = React.createClass({ - componentDidMount: function() { - ChannelStore.addExtraInfoChangeListener(this._onChange); - ChannelStore.addChangeListener(this._onChange); - var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ render_members: false }); - }); - - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { - self.setState({ render_members: true }); - }); - }, - componentWillUnmount: function() { - ChannelStore.removeExtraInfoChangeListener(this._onChange); - ChannelStore.removeChangeListener(this._onChange); - }, - _onChange: function() { - var new_state = getStateFromStores(); - if (!utils.areStatesEqual(this.state, new_state)) { - this.setState(new_state); + return { + nonmemberList: nonmemberList, + memberList: memberList, + channelName: channelName + }; + } + componentDidMount() { + ChannelStore.addExtraInfoChangeListener(this.onChange); + ChannelStore.addChangeListener(this.onChange); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() { + this.setState({renderMembers: false}); + }.bind(this)); + + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() { + this.setState({renderMembers: true}); + }.bind(this)); + } + componentWillUnmount() { + ChannelStore.removeExtraInfoChangeListener(this.onChange); + ChannelStore.removeChangeListener(this.onChange); + } + onChange() { + const newState = this.getStateFromStores(); + if (!Utils.areStatesEqual(this.state, newState)) { + this.setState(newState); } - }, - handleRemove: function(user_id) { + } + handleRemove(userId) { // Make sure the user is a member of the channel - var member_list = this.state.member_list; - var found = false; - for (var i = 0; i < member_list.length; i++) { - if (member_list[i].id === user_id) { + let memberList = this.state.memberList; + let found = false; + for (let i = 0; i < memberList.length; i++) { + if (memberList[i].id === userId) { found = true; break; } } - if (!found) { return }; + if (!found) { + return; + } - var data = {}; - data['user_id'] = user_id; + let data = {}; + data.user_id = userId; - client.removeChannelMember(ChannelStore.getCurrentId(), data, - function(data) { - var old_member; - for (var i = 0; i < member_list.length; i++) { - if (user_id === member_list[i].id) { - old_member = member_list[i]; - member_list.splice(i, 1); + Client.removeChannelMember(ChannelStore.getCurrentId(), data, + function handleRemoveSuccess() { + let oldMember; + for (let i = 0; i < memberList.length; i++) { + if (userId === memberList[i].id) { + oldMember = memberList[i]; + memberList.splice(i, 1); break; } } - var nonmember_list = this.state.nonmember_list; - if (old_member) { - nonmember_list.push(old_member); + let nonmemberList = this.state.nonmemberList; + if (oldMember) { + nonmemberList.push(oldMember); } - this.setState({ member_list: member_list, nonmember_list: nonmember_list }); + this.setState({memberList: memberList, nonmemberList: nonmemberList}); AsyncClient.getChannelExtraInfo(true); }.bind(this), - function(err) { - this.setState({ invite_error: err.message }); + function handleRemoveError(err) { + this.setState({inviteError: err.message}); }.bind(this) ); - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { - var currentMember = ChannelStore.getCurrentMember(); - var isAdmin = false; + } + render() { + const currentMember = ChannelStore.getCurrentMember(); + let isAdmin = false; if (currentMember) { - isAdmin = currentMember.roles.indexOf("admin") > -1 || UserStore.getCurrentUser().roles.indexOf("admin") > -1; + isAdmin = currentMember.roles.indexOf('admin') > -1 || UserStore.getCurrentUser().roles.indexOf('admin') > -1; + } + + var memberList = null; + if (this.state.renderMembers) { + memberList = ( + <MemberList + memberList={this.state.memberList} + isAdmin={isAdmin} + handleRemove={this.handleRemove} + /> + ); } return ( - <div className="modal fade" ref="modal" id="channel_members" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title"><span className="name">{this.state.channel_name}</span> Members</h4> - <a className="btn btn-md btn-primary" data-toggle="modal" data-target="#channel_invite"><i className="glyphicon glyphicon-envelope"/> Add New Members</a> - </div> - <div ref="modalBody" className="modal-body"> - <div className="col-sm-12"> - <div className="team-member-list"> - { this.state.render_members ? - <MemberList - memberList={this.state.member_list} - isAdmin={isAdmin} - handleRemove={this.handleRemove} - /> - : "" } + <div + className='modal fade' + ref='modal' + id='channel_members' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4> + <a + className='btn btn-md btn-primary' + data-toggle='modal' + data-target='#channel_invite' + > + <i className='glyphicon glyphicon-envelope'/> Add New Members + </a> + </div> + <div + ref='modalBody' + className='modal-body' + > + <div className='col-sm-12'> + <div className='team-member-list'> + {memberList} + </div> </div> </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Close + </button> + </div> </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> - </div> - </div> - </div> + </div> </div> - ); } -}); +} diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx index 884bad9d2..173646597 100644 --- a/web/react/components/channel_notifications.jsx +++ b/web/react/components/channel_notifications.jsx @@ -4,26 +4,29 @@ var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +var Utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); export default class ChannelNotifications extends React.Component { constructor(props) { super(props); + this.onListenerChange = this.onListenerChange.bind(this); this.updateSection = this.updateSection.bind(this); this.handleUpdate = this.handleUpdate.bind(this); this.handleRadioClick = this.handleRadioClick.bind(this); this.handleQuietToggle = this.handleQuietToggle.bind(this); + this.createDesktopSection = this.createDesktopSection.bind(this); + this.createQuietSection = this.createQuietSection.bind(this); + this.state = {notifyLevel: '', title: '', channelId: '', activeSection: ''}; } componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function showModal(e) { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function showModal(e) { var button = e.relatedTarget; var channelId = button.getAttribute('data-channelid'); @@ -34,8 +37,8 @@ export default class ChannelNotifications extends React.Component { quietMode = true; } - self.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId}); - }); + this.setState({notifyLevel: notifyLevel, quietMode: quietMode, title: button.getAttribute('data-title'), channelId: channelId}); + }.bind(this)); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); @@ -55,7 +58,7 @@ export default class ChannelNotifications extends React.Component { newState.notifyLevel = notifyLevel; newState.quietMode = quietMode; - if (!utils.areStatesEqual(this.state, newState)) { + if (!Utils.areStatesEqual(this.state, newState)) { this.setState(newState); } } @@ -78,7 +81,7 @@ export default class ChannelNotifications extends React.Component { return; } - client.updateNotifyLevel(data, + Client.updateNotifyLevel(data, function success() { var member = ChannelStore.getMember(channelId); member.notify_level = notifyLevel; @@ -92,25 +95,15 @@ export default class ChannelNotifications extends React.Component { } handleRadioClick(notifyLevel) { this.setState({notifyLevel: notifyLevel, quietMode: false}); - this.refs.modal.getDOMNode().focus(); + React.findDOMNode(this.refs.modal).focus(); } handleQuietToggle(quietMode) { this.setState({notifyLevel: 'none', quietMode: quietMode}); - this.refs.modal.getDOMNode().focus(); + React.findDOMNode(this.refs.modal).focus(); } - render() { - var serverError = null; - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var self = this; - var describe = ''; - var inputs = []; - + createDesktopSection(serverError) { var handleUpdateSection; - var desktopSection; if (this.state.activeSection === 'desktop') { var notifyActive = [false, false, false]; if (this.state.notifyLevel === 'mention') { @@ -121,6 +114,8 @@ export default class ChannelNotifications extends React.Component { notifyActive[2] = true; } + var inputs = []; + inputs.push( <div> <div className='radio'> @@ -128,7 +123,7 @@ export default class ChannelNotifications extends React.Component { <input type='radio' checked={notifyActive[0]} - onChange={self.handleRadioClick.bind(this, 'all')} + onChange={this.handleRadioClick.bind(this, 'all')} > For all activity </input> @@ -140,7 +135,7 @@ export default class ChannelNotifications extends React.Component { <input type='radio' checked={notifyActive[1]} - onChange={self.handleRadioClick.bind(this, 'mention')} + onChange={this.handleRadioClick.bind(this, 'mention')} > Only for mentions </input> @@ -152,7 +147,7 @@ export default class ChannelNotifications extends React.Component { <input type='radio' checked={notifyActive[2]} - onChange={self.handleRadioClick.bind(this, 'none')} + onChange={this.handleRadioClick.bind(this, 'none')} > Never </input> @@ -162,12 +157,12 @@ export default class ChannelNotifications extends React.Component { ); handleUpdateSection = function updateSection(e) { - self.updateSection(''); - self.onListenerChange(); + this.updateSection(''); + this.onListenerChange(); e.preventDefault(); - }; + }.bind(this); - desktopSection = ( + return ( <SettingItemMax title='Send desktop notifications' inputs={inputs} @@ -176,30 +171,32 @@ export default class ChannelNotifications extends React.Component { updateSection={handleUpdateSection} /> ); - } else { - if (this.state.notifyLevel === 'mention') { - describe = 'Only for mentions'; - } else if (this.state.notifyLevel === 'all') { - describe = 'For all activity'; - } else { - describe = 'Never'; - } - - handleUpdateSection = function updateSection(e) { - self.updateSection('desktop'); - e.preventDefault(); - }; + } - desktopSection = ( - <SettingItemMin - title='Send desktop notifications' - describe={describe} - updateSection={handleUpdateSection} - /> - ); + var describe; + if (this.state.notifyLevel === 'mention') { + describe = 'Only for mentions'; + } else if (this.state.notifyLevel === 'all') { + describe = 'For all activity'; + } else { + describe = 'Never'; } - var quietSection; + handleUpdateSection = function updateSection(e) { + this.updateSection('desktop'); + e.preventDefault(); + }.bind(this); + + return ( + <SettingItemMin + title='Send desktop notifications' + describe={describe} + updateSection={handleUpdateSection} + /> + ); + } + createQuietSection(serverError) { + var handleUpdateSection; if (this.state.activeSection === 'quiet') { var quietActive = [false, false]; if (this.state.quietMode) { @@ -208,6 +205,8 @@ export default class ChannelNotifications extends React.Component { quietActive[1] = true; } + var inputs = []; + inputs.push( <div> <div className='radio'> @@ -215,7 +214,7 @@ export default class ChannelNotifications extends React.Component { <input type='radio' checked={quietActive[0]} - onChange={self.handleQuietToggle.bind(this, true)} + onChange={this.handleQuietToggle.bind(this, true)} > On </input> @@ -227,7 +226,7 @@ export default class ChannelNotifications extends React.Component { <input type='radio' checked={quietActive[1]} - onChange={self.handleQuietToggle.bind(this, false)} + onChange={this.handleQuietToggle.bind(this, false)} > Off </input> @@ -245,12 +244,12 @@ export default class ChannelNotifications extends React.Component { ); handleUpdateSection = function updateSection(e) { - self.updateSection(''); - self.onListenerChange(); + this.updateSection(''); + this.onListenerChange(); e.preventDefault(); - }; + }.bind(this); - quietSection = ( + return ( <SettingItemMax title='Quiet mode' inputs={inputs} @@ -259,27 +258,38 @@ export default class ChannelNotifications extends React.Component { updateSection={handleUpdateSection} /> ); + } + + var describe; + if (this.state.quietMode) { + describe = 'On'; } else { - if (this.state.quietMode) { - describe = 'On'; - } else { - describe = 'Off'; - } + describe = 'Off'; + } - handleUpdateSection = function updateSection(e) { - self.updateSection('quiet'); - e.preventDefault(); - }; + handleUpdateSection = function updateSection(e) { + this.updateSection('quiet'); + e.preventDefault(); + }.bind(this); - quietSection = ( - <SettingItemMin - title='Quiet mode' - describe={describe} - updateSection={handleUpdateSection} - /> - ); + return ( + <SettingItemMin + title='Quiet mode' + describe={describe} + updateSection={handleUpdateSection} + /> + ); + } + render() { + var serverError = null; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; } + var desktopSection = this.createDesktopSection(serverError); + + var quietSection = this.createQuietSection(serverError); + return ( <div className='modal fade' diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index 3be13cf9b..cb3b9c5e3 100644 --- a/web/react/components/confirm_modal.jsx +++ b/web/react/components/confirm_modal.jsx @@ -1,31 +1,70 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - handleConfirm: function() { - $('#'+this.props.parent_id).attr('data-confirm', 'true'); - $('#'+this.props.parent_id).modal('hide'); - $('#'+this.props.id).modal('hide'); - }, - render: function() { +export default class ConfirmModal extends React.Component { + constructor(props) { + super(props); + + this.handleConfirm = this.handleConfirm.bind(this); + + this.state = {}; + } + handleConfirm() { + $('#' + this.props.parent_id).attr('data-confirm', 'true'); + $('#' + this.props.parent_id).modal('hide'); + $('#' + this.props.id).modal('hide'); + } + render() { return ( - <div className="modal fade" id={this.props.id} tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <h4 className="modal-title">{this.props.title}</h4> - </div> - <div className="modal-body"> - {this.props.message} - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button onClick={this.handleConfirm} type="button" className="btn btn-primary">{this.props.confirm_button}</button> + <div + className='modal fade' + id={this.props.id} + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <h4 className='modal-title'>{this.props.title}</h4> + </div> + <div className='modal-body'> + {this.props.message} + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + onClick={this.handleConfirm} + type='button' + className='btn btn-primary' + > + {this.props.confirm_button} + </button> + </div> </div> - </div> - </div> + </div> </div> ); } -}); +} +ConfirmModal.defaultProps = { + parent_id: '', + id: '', + title: '', + message: '', + confirm_button: '' +}; +ConfirmModal.propTypes = { + parent_id: React.PropTypes.string, + id: React.PropTypes.string, + title: React.PropTypes.string, + message: React.PropTypes.string, + confirm_button: React.PropTypes.string +}; diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index c2b7e222f..c2fc0dcf3 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -1,24 +1,48 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var PostStore = require('../stores/post_store.jsx'); -var Textbox = require('./textbox.jsx'); -var MsgTyping = require('./msg_typing.jsx'); -var FileUpload = require('./file_upload.jsx'); -var FilePreview = require('./file_preview.jsx'); -var utils = require('../utils/utils.jsx'); -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; - -module.exports = React.createClass({ - lastTime: 0, - handleSubmit: function(e) { +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const SocketStore = require('../stores/socket_store.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const Textbox = require('./textbox.jsx'); +const MsgTyping = require('./msg_typing.jsx'); +const FileUpload = require('./file_upload.jsx'); +const FilePreview = require('./file_preview.jsx'); +const Utils = require('../utils/utils.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; + +export default class CreateComment extends React.Component { + constructor(props) { + super(props); + + this.lastTime = 0; + + this.handleSubmit = this.handleSubmit.bind(this); + this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this); + this.handleUserInput = this.handleUserInput.bind(this); + this.handleUploadStart = this.handleUploadStart.bind(this); + this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); + this.handleUploadError = this.handleUploadError.bind(this); + this.removePreview = this.removePreview.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.getFileCount = this.getFileCount.bind(this); + + PostStore.clearCommentDraftUploads(); + + const draft = PostStore.getCommentDraft(this.props.rootId); + this.state = { + messageText: draft.message, + uploadsInProgress: draft.uploadsInProgress, + previews: draft.previews, + submitting: false + }; + } + handleSubmit(e) { e.preventDefault(); if (this.state.uploadsInProgress.length > 0) { @@ -29,7 +53,7 @@ module.exports = React.createClass({ return; } - var post = {}; + let post = {}; post.filenames = []; post.message = this.state.messageText; @@ -38,30 +62,30 @@ module.exports = React.createClass({ } if (post.message.length > Constants.CHARACTER_LIMIT) { - this.setState({postError: 'Comment length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'}); + this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`}); return; } - var user_id = UserStore.getCurrentId(); + const userId = UserStore.getCurrentId(); post.channel_id = this.props.channelId; post.root_id = this.props.rootId; post.parent_id = this.props.rootId; post.filenames = this.state.previews; - var time = utils.getTimestamp(); - post.pending_post_id = user_id + ':'+ time; - post.user_id = user_id; + const time = Utils.getTimestamp(); + post.pending_post_id = `${userId}:${time}`; + post.user_id = userId; post.create_at = time; PostStore.storePendingPost(post); PostStore.storeCommentDraft(this.props.rootId, null); - client.createPost(post, ChannelStore.getCurrent(), - function(data) { + Client.createPost(post, ChannelStore.getCurrent(), + function handlePostSuccess(data) { AsyncClient.getPosts(this.props.channelId); - var channel = ChannelStore.get(this.props.channelId); - var member = ChannelStore.getMember(this.props.channelId); + const channel = ChannelStore.get(this.props.channelId); + let member = ChannelStore.getMember(this.props.channelId); member.msg_count = channel.total_msg_count; member.last_viewed_at = Date.now(); ChannelStore.setChannelMember(member); @@ -71,8 +95,8 @@ module.exports = React.createClass({ post: data }); }.bind(this), - function(err) { - var state = {}; + function handlePostError(err) { + let state = {}; if (err.message === 'Invalid RootId parameter') { if ($('#post_deleted').length > 0) { @@ -90,76 +114,76 @@ module.exports = React.createClass({ ); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - }, - commentMsgKeyPress: function(e) { + } + commentMsgKeyPress(e) { if (e.which === 13 && !e.shiftKey && !e.altKey) { e.preventDefault(); - this.refs.textbox.getDOMNode().blur(); + React.findDOMNode(this.refs.textbox).blur(); this.handleSubmit(e); } - var t = Date.now(); + const t = Date.now(); if ((t - this.lastTime) > 5000) { - SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {'parent_id': this.props.rootId}}); + SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}}); this.lastTime = t; } - }, - handleUserInput: function(messageText) { - var draft = PostStore.getCommentDraft(this.props.rootId); + } + handleUserInput(messageText) { + let draft = PostStore.getCommentDraft(this.props.rootId); draft.message = messageText; PostStore.storeCommentDraft(this.props.rootId, draft); $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').perfectScrollbar('update'); this.setState({messageText: messageText}); - }, - handleUploadStart: function(clientIds, channelId) { - var draft = PostStore.getCommentDraft(this.props.rootId); + } + handleUploadStart(clientIds) { + let draft = PostStore.getCommentDraft(this.props.rootId); - draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); + draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds); PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress']}); - }, - handleFileUploadComplete: function(filenames, clientIds, channelId) { - var draft = PostStore.getCommentDraft(this.props.rootId); + this.setState({uploadsInProgress: draft.uploadsInProgress}); + } + handleFileUploadComplete(filenames, clientIds) { + let draft = PostStore.getCommentDraft(this.props.rootId); // remove each finished file from uploads - for (var i = 0; i < clientIds.length; i++) { - var index = draft['uploadsInProgress'].indexOf(clientIds[i]); + for (let i = 0; i < clientIds.length; i++) { + const index = draft.uploadsInProgress.indexOf(clientIds[i]); if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); + draft.uploadsInProgress.splice(index, 1); } } - draft['previews'] = draft['previews'].concat(filenames); + draft.previews = draft.previews.concat(filenames); PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); - }, - handleUploadError: function(err, clientId) { + this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); + } + handleUploadError(err, clientId) { if (clientId !== -1) { - var draft = PostStore.getCommentDraft(this.props.rootId); + let draft = PostStore.getCommentDraft(this.props.rootId); - var index = draft['uploadsInProgress'].indexOf(clientId); + const index = draft.uploadsInProgress.indexOf(clientId); if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); + draft.uploadsInProgress.splice(index, 1); } PostStore.storeCommentDraft(this.props.rootId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); } else { this.setState({serverError: err}); } - }, - removePreview: function(id) { - var previews = this.state.previews; - var uploadsInProgress = this.state.uploadsInProgress; + } + removePreview(id) { + let previews = this.state.previews; + let uploadsInProgress = this.state.uploadsInProgress; // id can either be the path of an uploaded file or the client id of an in progress upload - var index = previews.indexOf(id); + let index = previews.indexOf(id); if (index !== -1) { previews.splice(index, 1); } else { @@ -171,30 +195,24 @@ module.exports = React.createClass({ } } - var draft = PostStore.getCommentDraft(this.props.rootId); + let draft = PostStore.getCommentDraft(this.props.rootId); draft.previews = previews; draft.uploadsInProgress = uploadsInProgress; PostStore.storeCommentDraft(this.props.rootId, draft); this.setState({previews: previews, uploadsInProgress: uploadsInProgress}); - }, - getInitialState: function() { - PostStore.clearCommentDraftUploads(); - - var draft = PostStore.getCommentDraft(this.props.rootId); - return {messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews'], submitting: false}; - }, - componentWillReceiveProps: function(newProps) { + } + componentWillReceiveProps(newProps) { if (newProps.rootId !== this.props.rootId) { - var draft = PostStore.getCommentDraft(newProps.rootId); - this.setState({messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); + const draft = PostStore.getCommentDraft(newProps.rootId); + this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } - }, - getFileCount: function(channelId) { + } + getFileCount() { return this.state.previews.length + this.state.uploadsInProgress.length; - }, - render: function() { - var serverError = null; + } + render() { + let serverError = null; if (this.state.serverError) { serverError = ( <div className='form-group has-error'> @@ -203,22 +221,23 @@ module.exports = React.createClass({ ); } - var postError = null; + let postError = null; if (this.state.postError) { postError = <label className='control-label'>{this.state.postError}</label>; } - var preview = null; + let preview = null; if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { preview = ( <FilePreview files={this.state.previews} onRemove={this.removePreview} - uploadsInProgress={this.state.uploadsInProgress} /> + uploadsInProgress={this.state.uploadsInProgress} + /> ); } - var postFooterClassName = 'post-create-footer'; + let postFooterClassName = 'post-create-footer'; if (postError) { postFooterClassName += ' has-error'; } @@ -226,7 +245,10 @@ module.exports = React.createClass({ return ( <form onSubmit={this.handleSubmit}> <div className='post-create'> - <div id={this.props.rootId} className='post-create-body comment-create-body'> + <div + id={this.props.rootId} + className='post-create-body comment-create-body' + > <Textbox onUserInput={this.handleUserInput} onKeyPress={this.commentMsgKeyPress} @@ -234,7 +256,8 @@ module.exports = React.createClass({ createMessage='Add a comment...' initialText='' id='reply_textbox' - ref='textbox' /> + ref='textbox' + /> <FileUpload ref='fileUpload' getFileCount={this.getFileCount} @@ -242,11 +265,20 @@ module.exports = React.createClass({ onFileUpload={this.handleFileUploadComplete} onUploadError={this.handleUploadError} postType='comment' - channelId={this.props.channelId} /> + channelId={this.props.channelId} + /> </div> - <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} /> + <MsgTyping + channelId={this.props.channelId} + parentId={this.props.rootId} + /> <div className={postFooterClassName}> - <input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} /> + <input + type='button' + className='btn btn-primary comment-btn pull-right' + value='Add Comment' + onClick={this.handleSubmit} + /> {postError} {serverError} </div> @@ -255,4 +287,9 @@ module.exports = React.createClass({ </form> ); } -}); +} + +CreateComment.propTypes = { + channelId: React.PropTypes.string.isRequired, + rootId: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index b9142223f..ce4ebac9e 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -1,33 +1,68 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var PostStore = require('../stores/post_store.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); -var MsgTyping = require('./msg_typing.jsx'); -var Textbox = require('./textbox.jsx'); -var FileUpload = require('./file_upload.jsx'); -var FilePreview = require('./file_preview.jsx'); -var utils = require('../utils/utils.jsx'); - -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; - -module.exports = React.createClass({ - displayName: 'CreatePost', - lastTime: 0, - handleSubmit: function(e) { +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const SocketStore = require('../stores/socket_store.jsx'); +const MsgTyping = require('./msg_typing.jsx'); +const Textbox = require('./textbox.jsx'); +const FileUpload = require('./file_upload.jsx'); +const FilePreview = require('./file_preview.jsx'); +const Utils = require('../utils/utils.jsx'); + +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; + +export default class CreatePost extends React.Component { + constructor(props) { + super(props); + + this.lastTime = 0; + + this.handleSubmit = this.handleSubmit.bind(this); + this.postMsgKeyPress = this.postMsgKeyPress.bind(this); + this.handleUserInput = this.handleUserInput.bind(this); + this.resizePostHolder = this.resizePostHolder.bind(this); + this.handleUploadStart = this.handleUploadStart.bind(this); + this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); + this.handleUploadError = this.handleUploadError.bind(this); + this.removePreview = this.removePreview.bind(this); + this.onChange = this.onChange.bind(this); + this.getFileCount = this.getFileCount.bind(this); + + PostStore.clearDraftUploads(); + + const draft = PostStore.getCurrentDraft(); + let previews = []; + let messageText = ''; + let uploadsInProgress = []; + if (draft && draft.previews && draft.message) { + previews = draft.previews; + messageText = draft.message; + uploadsInProgress = draft.uploadsInProgress; + } + + this.state = { + channelId: ChannelStore.getCurrentId(), + messageText: messageText, + uploadsInProgress: uploadsInProgress, + previews: previews, + submitting: false, + initialText: messageText + }; + } + handleSubmit(e) { e.preventDefault(); if (this.state.uploadsInProgress.length > 0 || this.state.submitting) { return; } - var post = {}; + let post = {}; post.filenames = []; post.message = this.state.messageText; @@ -36,18 +71,18 @@ module.exports = React.createClass({ } if (post.message.length > Constants.CHARACTER_LIMIT) { - this.setState({postError: 'Post length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'}); + this.setState({postError: `Post length must be less than ${Constants.CHARACTER_LIMIT} characters.`}); return; } this.setState({submitting: true, serverError: null}); if (post.message.indexOf('/') === 0) { - client.executeCommand( + Client.executeCommand( this.state.channelId, post.message, false, - function(data) { + function handleCommandSuccess(data) { PostStore.storeDraft(data.channel_id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); @@ -55,8 +90,8 @@ module.exports = React.createClass({ window.location.href = data.goto_location; } }.bind(this), - function(err) { - var state = {}; + function handleCommandError(err) { + let state = {}; state.serverError = err.message; state.submitting = false; this.setState(state); @@ -66,26 +101,26 @@ module.exports = React.createClass({ post.channel_id = this.state.channelId; post.filenames = this.state.previews; - var time = utils.getTimestamp(); - var userId = UserStore.getCurrentId(); - post.pending_post_id = userId + ':' + time; + const time = Utils.getTimestamp(); + const userId = UserStore.getCurrentId(); + post.pending_post_id = `${userId}:${time}`; post.user_id = userId; post.create_at = time; post.root_id = this.state.rootId; post.parent_id = this.state.parentId; - var channel = ChannelStore.get(this.state.channelId); + const channel = ChannelStore.get(this.state.channelId); PostStore.storePendingPost(post); PostStore.storeDraft(channel.id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - client.createPost(post, channel, - function(data) { + Client.createPost(post, channel, + function handlePostSuccess(data) { this.resizePostHolder(); AsyncClient.getPosts(); - var member = ChannelStore.getMember(channel.id); + let member = ChannelStore.getMember(channel.id); member.msg_count = channel.total_msg_count; member.last_viewed_at = Date.now(); ChannelStore.setChannelMember(member); @@ -95,8 +130,8 @@ module.exports = React.createClass({ post: data }); }.bind(this), - function(err) { - var state = {}; + function handlePostError(err) { + let state = {}; if (err.message === 'Invalid RootId parameter') { if ($('#post_deleted').length > 0) { @@ -113,83 +148,83 @@ module.exports = React.createClass({ }.bind(this) ); } - }, - componentDidUpdate: function() { + } + componentDidUpdate() { this.resizePostHolder(); - }, - postMsgKeyPress: function(e) { + } + postMsgKeyPress(e) { if (e.which === 13 && !e.shiftKey && !e.altKey) { e.preventDefault(); - this.refs.textbox.getDOMNode().blur(); + React.findDOMNode(this.refs.textbox).blur(); this.handleSubmit(e); } - var t = Date.now(); + const t = Date.now(); if ((t - this.lastTime) > 5000) { - SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {'parent_id': ''}, state: {}}); + SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}}); this.lastTime = t; } - }, - handleUserInput: function(messageText) { + } + handleUserInput(messageText) { this.resizePostHolder(); this.setState({messageText: messageText}); - var draft = PostStore.getCurrentDraft(); - draft['message'] = messageText; + let draft = PostStore.getCurrentDraft(); + draft.message = messageText; PostStore.storeCurrentDraft(draft); - }, - resizePostHolder: function() { - var height = $(window).height() - $(this.refs.topDiv.getDOMNode()).height() - $('#error_bar').outerHeight() - 50; - $('.post-list-holder-by-time').css('height', height + 'px'); + } + resizePostHolder() { + const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - $('#error_bar').outerHeight() - 50; + $('.post-list-holder-by-time').css('height', `${height}px`); $(window).trigger('resize'); - }, - handleUploadStart: function(clientIds, channelId) { - var draft = PostStore.getDraft(channelId); + } + handleUploadStart(clientIds, channelId) { + let draft = PostStore.getDraft(channelId); - draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds); + draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds); PostStore.storeDraft(channelId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress']}); - }, - handleFileUploadComplete: function(filenames, clientIds, channelId) { - var draft = PostStore.getDraft(channelId); + this.setState({uploadsInProgress: draft.uploadsInProgress}); + } + handleFileUploadComplete(filenames, clientIds, channelId) { + let draft = PostStore.getDraft(channelId); // remove each finished file from uploads - for (var i = 0; i < clientIds.length; i++) { - var index = draft['uploadsInProgress'].indexOf(clientIds[i]); + for (let i = 0; i < clientIds.length; i++) { + const index = draft.uploadsInProgress.indexOf(clientIds[i]); if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); + draft.uploadsInProgress.splice(index, 1); } } - draft['previews'] = draft['previews'].concat(filenames); + draft.previews = draft.previews.concat(filenames); PostStore.storeDraft(channelId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']}); - }, - handleUploadError: function(err, clientId) { + this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); + } + handleUploadError(err, clientId) { if (clientId !== -1) { - var draft = PostStore.getDraft(this.state.channelId); + let draft = PostStore.getDraft(this.state.channelId); - var index = draft['uploadsInProgress'].indexOf(clientId); + const index = draft.uploadsInProgress.indexOf(clientId); if (index !== -1) { - draft['uploadsInProgress'].splice(index, 1); + draft.uploadsInProgress.splice(index, 1); } PostStore.storeDraft(this.state.channelId, draft); - this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err}); + this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); } else { this.setState({serverError: err}); } - }, - removePreview: function(id) { - var previews = this.state.previews; - var uploadsInProgress = this.state.uploadsInProgress; + } + removePreview(id) { + let previews = this.state.previews; + let uploadsInProgress = this.state.uploadsInProgress; // id can either be the path of an uploaded file or the client id of an in progress upload - var index = previews.indexOf(id); + let index = previews.indexOf(id); if (index !== -1) { previews.splice(index, 1); } else { @@ -201,28 +236,28 @@ module.exports = React.createClass({ } } - var draft = PostStore.getCurrentDraft(); - draft['previews'] = previews; - draft['uploadsInProgress'] = uploadsInProgress; + let draft = PostStore.getCurrentDraft(); + draft.previews = previews; + draft.uploadsInProgress = uploadsInProgress; PostStore.storeCurrentDraft(draft); this.setState({previews: previews, uploadsInProgress: uploadsInProgress}); - }, - componentDidMount: function() { - ChannelStore.addChangeListener(this._onChange); + } + componentDidMount() { + ChannelStore.addChangeListener(this.onChange); this.resizePostHolder(); - }, - componentWillUnmount: function() { - ChannelStore.removeChangeListener(this._onChange); - }, - _onChange: function() { - var channelId = ChannelStore.getCurrentId(); + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChange); + } + onChange() { + const channelId = ChannelStore.getCurrentId(); if (this.state.channelId !== channelId) { - var draft = PostStore.getCurrentDraft(); + let draft = PostStore.getCurrentDraft(); - var previews = []; - var messageText = ''; - var uploadsInProgress = []; + let previews = []; + let messageText = ''; + let uploadsInProgress = []; if (draft && draft.previews && draft.message) { previews = draft.previews; messageText = draft.message; @@ -231,33 +266,17 @@ module.exports = React.createClass({ this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress}); } - }, - getInitialState: function() { - PostStore.clearDraftUploads(); - - var draft = PostStore.getCurrentDraft(); - var previews = []; - var messageText = ''; - var uploadsInProgress = []; - if (draft && draft.previews && draft.message) { - previews = draft.previews; - messageText = draft.message; - uploadsInProgress = draft.uploadsInProgress; - } - - return {channelId: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: uploadsInProgress, previews: previews, submitting: false, initialText: messageText}; - }, - getFileCount: function(channelId) { + } + getFileCount(channelId) { if (channelId === this.state.channelId) { return this.state.previews.length + this.state.uploadsInProgress.length; - } else { - var draft = PostStore.getDraft(channelId); - - return draft['previews'].length + draft['uploadsInProgress'].length; } - }, - render: function() { - var serverError = null; + + const draft = PostStore.getDraft(channelId); + return draft.previews.length + draft.uploadsInProgress.length; + } + render() { + let serverError = null; if (this.state.serverError) { serverError = ( <div className='has-error'> @@ -266,12 +285,12 @@ module.exports = React.createClass({ ); } - var postError = null; + let postError = null; if (this.state.postError) { postError = <label className='control-label'>{this.state.postError}</label>; } - var preview = null; + let preview = null; if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) { preview = ( <FilePreview @@ -281,13 +300,18 @@ module.exports = React.createClass({ ); } - var postFooterClassName = 'post-create-footer'; + let postFooterClassName = 'post-create-footer'; if (postError) { postFooterClassName += ' has-error'; } return ( - <form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}> + <form + id='create_post' + ref='topDiv' + role='form' + onSubmit={this.handleSubmit} + > <div className='post-create'> <div className='post-create-body'> <Textbox @@ -311,10 +335,13 @@ module.exports = React.createClass({ {postError} {serverError} {preview} - <MsgTyping channelId={this.state.channelId} parentId=''/> + <MsgTyping + channelId={this.state.channelId} + parentId='' + /> </div> </div> </form> ); } -}); +} diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index 589737271..4efb9cb23 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -1,58 +1,99 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Client =require('../utils/client.jsx'); -var AsyncClient =require('../utils/async_client.jsx'); -var ChannelStore =require('../stores/channel_store.jsx') +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); -module.exports = React.createClass({ - handleDelete: function(e) { - if (this.state.channel_id.length != 26) return; +export default class DeleteChannelModal extends React.Component { + constructor(props) { + super(props); - Client.deleteChannel(this.state.channel_id, - function(data) { + this.handleDelete = this.handleDelete.bind(this); + + this.state = { + title: '', + channelId: '' + }; + } + handleDelete() { + if (this.state.channelId.length !== 26) { + return; + } + + Client.deleteChannel(this.state.channelId, + function handleDeleteSuccess() { AsyncClient.getChannels(true); window.location.href = '/'; - }.bind(this), - function(err) { - AsyncClient.dispatchError(err, "handleDelete"); - }.bind(this) + }, + function handleDeleteError(err) { + AsyncClient.dispatchError(err, 'handleDelete'); + } ); - }, - componentDidMount: function() { - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { var button = $(e.relatedTarget); - self.setState({ title: button.attr('data-title'), channel_id: button.attr('data-channelid') }); - }); - }, - getInitialState: function() { - return { title: "", channel_id: "" }; - }, - render: function() { - - var channelType = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'P' ? "private group" : "channel" + this.setState({ + title: button.attr('data-title'), + channelId: button.attr('data-channelid') + }); + }.bind(this)); + } + render() { + const channel = ChannelStore.getCurrent(); + let channelType = 'channel'; + if (channel && channel.type === 'P') { + channelType = 'private group'; + } return ( - <div className="modal fade" ref="modal" id="delete_channel" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title">Confirm DELETE Channel</h4> - </div> - <div className="modal-body"> - <p> - Are you sure you wish to delete the {this.state.title} {channelType}? - </p> - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button> - </div> + <div + className='modal fade' + ref='modal' + id='delete_channel' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 className='modal-title'>Confirm DELETE Channel</h4> + </div> + <div className='modal-body'> + <p> + Are you sure you wish to delete the {this.state.title} {channelType}? + </p> + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + type='button' + className='btn btn-danger' + data-dismiss='modal' + onClick={this.handleDelete} + > + Delete + </button> + </div> + </div> </div> - </div> </div> ); } -}); +} diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 55d6f509c..075f9c742 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -4,20 +4,28 @@ var Client = require('../utils/client.jsx'); var PostStore = require('../stores/post_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -module.exports = React.createClass({ - handleDelete: function(e) { - Client.deletePost(this.state.channel_id, this.state.post_id, - function(data) { - var selected_list = this.state.selectedList; - if (selected_list && selected_list.order && selected_list.order.length > 0) { - var selected_post = selected_list.posts[selected_list.order[0]]; - if ((selected_post.id === this.state.post_id && this.state.title === "Post") || selected_post.root_id === this.state.post_id) { +export default class DeletePostModal extends React.Component { + constructor(props) { + super(props); + + this.handleDelete = this.handleDelete.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0}; + } + handleDelete() { + Client.deletePost(this.state.channelId, this.state.postId, + function deleteSuccess() { + var selectedList = this.state.selectedList; + if (selectedList && selectedList.order && selectedList.order.length > 0) { + var selectedPost = selectedList.posts[selectedList.order[0]]; + if ((selectedPost.id === this.state.postId && this.state.title === 'Post') || selectedPost.root_id === this.state.postId) { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SEARCH, results: null @@ -27,14 +35,14 @@ module.exports = React.createClass({ type: ActionTypes.RECIEVED_POST_SELECTED, results: null }); - } else if (selected_post.id === this.state.post_id && this.state.title === "Comment") { - if (selected_post.root_id && selected_post.root_id.length > 0 && selected_list.posts[selected_post.root_id]) { - selected_list.order = [selected_post.root_id]; - delete selected_list.posts[selected_post.id]; + } else if (selectedPost.id === this.state.postId && this.state.title === 'Comment') { + if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) { + selectedList.order = [selectedPost.root_id]; + delete selectedList.posts[selectedPost.id]; AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POST_SELECTED, - post_list: selected_list + post_list: selectedList }); AppDispatcher.handleServerAction({ @@ -44,67 +52,97 @@ module.exports = React.createClass({ } } } - PostStore.removePost(this.state.post_id, this.state.channel_id); - AsyncClient.getPosts(this.state.channel_id); + PostStore.removePost(this.state.postId, this.state.channelId); + AsyncClient.getPosts(this.state.channelId); }.bind(this), - function(err) { - AsyncClient.dispatchError(err, "deletePost"); - }.bind(this) + function deleteFailed(err) { + AsyncClient.dispatchError(err, 'deletePost'); + } ); - }, - componentDidMount: function() { - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) { var newState = {}; - if(BrowserStore.getItem('edit_state_transfer')) { + if (BrowserStore.getItem('edit_state_transfer')) { newState = BrowserStore.getItem('edit_state_transfer'); BrowserStore.removeItem('edit_state_transfer'); } else { var button = e.relatedTarget; - newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') }; + newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; } - self.setState(newState); - }); - PostStore.addSelectedPostChangeListener(this._onChange); - }, - componentWillUnmount: function() { - PostStore.removeSelectedPostChangeListener(this._onChange); - }, - _onChange: function() { + this.setState(newState); + }.bind(this)); + PostStore.addSelectedPostChangeListener(this.onListenerChange); + } + componentWillUnmount() { + PostStore.removeSelectedPostChangeListener(this.onListenerChange); + } + onListenerChange() { var newList = PostStore.getSelectedPost(); - if (!utils.areStatesEqual(this.state.selectedList, newList)) { - this.setState({ selectedList: newList }); + if (!Utils.areStatesEqual(this.state.selectedList, newList)) { + this.setState({selectedList: newList}); + } + } + render() { + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var commentWarning = ''; + if (this.state.comments > 0) { + commentWarning = 'This post has ' + this.state.comments + ' comment(s) on it.'; } - }, - getInitialState: function() { - return { title: "", post_id: "", channel_id: "", selectedList: PostStore.getSelectedPost(), comments: 0 }; - }, - render: function() { - var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null; return ( - <div className="modal fade" id="delete_post" ref="modal" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog modal-push-down"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title">Confirm {this.state.title} Delete</h4> + <div + className='modal fade' + id='delete_post' + ref='modal' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog modal-push-down'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 className='modal-title'>Confirm {this.state.title} Delete</h4> </div> - <div className="modal-body"> + <div className='modal-body'> Are you sure you want to delete the {this.state.title.toLowerCase()}? <br/> <br/> - { this.state.comments > 0 ? - "This post has " + this.state.comments + " comment(s) on it." - : "" } + {commentWarning} </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button> + {error} + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + type='button' + className='btn btn-danger' + data-dismiss='modal' + onClick={this.handleDelete} + > + Delete + </button> </div> </div> </div> </div> ); } -}); +} diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 76f0c2c4d..e93bab431 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -1,79 +1,142 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); -module.exports = React.createClass({ - handleEdit: function(e) { +export default class EditChannelModal extends React.Component { + constructor(props) { + super(props); + + this.handleEdit = this.handleEdit.bind(this); + this.handleUserInput = this.handleUserInput.bind(this); + this.handleClose = this.handleClose.bind(this); + + this.state = { + description: '', + title: '', + channelId: '', + serverError: '' + }; + } + handleEdit() { var data = {}; - data["channel_id"] = this.state.channel_id; - if (data["channel_id"].length !== 26) return; - data["channel_description"] = this.state.description.trim(); + data.channel_id = this.state.channelId; + + if (data.channel_id.length !== 26) { + return; + } + + data.channel_description = this.state.description.trim(); Client.updateChannelDesc(data, - function(data) { - this.setState({ server_error: "" }); - AsyncClient.getChannel(this.state.channel_id); - $(this.refs.modal.getDOMNode()).modal('hide'); + function handleUpdateSuccess() { + this.setState({serverError: ''}); + AsyncClient.getChannel(this.state.channelId); + $(React.findDOMNode(this.refs.modal)).modal('hide'); }.bind(this), - function(err) { - if (err.message === "Invalid channel_description parameter") { - this.setState({ server_error: "This description is too long, please enter a shorter one" }); - } - else { - this.setState({ server_error: err.message }); + function handleUpdateError(err) { + if (err.message === 'Invalid channel_description parameter') { + this.setState({serverError: 'This description is too long, please enter a shorter one'}); + } else { + this.setState({serverError: err.message}); } }.bind(this) ); - }, - handleUserInput: function(e) { - this.setState({ description: e.target.value }); - }, - handleClose: function() { - this.setState({description: "", server_error: ""}); - }, - componentDidMount: function() { - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { - var button = e.relatedTarget; - self.setState({ description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), server_error: "" }); - }); - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose) - }, - componentWillUnmount: function() { - $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose) - }, - getInitialState: function() { - return { description: "", title: "", channel_id: "" }; - }, - render: function() { - var server_error = this.state.server_error ? <div className='form-group has-error'><br/><label className='control-label'>{ this.state.server_error }</label></div> : null; + } + handleUserInput(e) { + this.setState({description: e.target.value}); + } + handleClose() { + this.setState({description: '', serverError: ''}); + } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { + const button = e.relatedTarget; + this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); + }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); + } + componentWillUnmount() { + $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + } + render() { + var serverError = null; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>; + } - var editTitle = <h4 className='modal-title' ref='title'>Edit Description</h4>; + var editTitle = ( + <h4 + className='modal-title' + ref='title' + > + Edit Description + </h4> + ); if (this.state.title) { - editTitle = <h4 className='modal-title' ref='title'>Edit Description for <span className='name'>{this.state.title}</span></h4>; + editTitle = ( + <h4 + className='modal-title' + ref='title' + > + Edit Description for <span className='name'>{this.state.title}</span> + </h4> + ); } return ( - <div className="modal fade" ref="modal" id="edit_channel" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - {editTitle} - </div> - <div className="modal-body"> - <textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea> - { server_error } - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button> - </div> + <div + className='modal fade' + ref='modal' + id='edit_channel' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + {editTitle} + </div> + <div className='modal-body'> + <textarea + className='form-control no-resize' + rows='6' + ref='channelDesc' + maxLength='1024' + value={this.state.description} + onChange={this.handleUserInput} + /> + {serverError} + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + type='button' + className='btn btn-primary' + onClick={this.handleEdit} + > + Save + </button> + </div> + </div> </div> - </div> </div> ); } -}); +} diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index 1c5a1ed5e..fef60c715 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -3,13 +3,21 @@ var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -var Constants = require('../utils/constants.jsx'); -var utils = require('../utils/utils.jsx'); var Textbox = require('./textbox.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -module.exports = React.createClass({ - handleEdit: function(e) { +export default class EditPostModal extends React.Component { + constructor() { + super(); + + this.handleEdit = this.handleEdit.bind(this); + this.handleEditInput = this.handleEditInput.bind(this); + this.handleEditKeyPress = this.handleEditKeyPress.bind(this); + this.handleUserInput = this.handleUserInput.bind(this); + + this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''}; + } + handleEdit() { var updatedPost = {}; updatedPost.message = this.state.editText.trim(); @@ -17,8 +25,8 @@ module.exports = React.createClass({ var tempState = this.state; delete tempState.editText; BrowserStore.setItem('edit_state_transfer', tempState); - $("#edit_post").modal('hide'); - $("#delete_post").modal('show'); + $('#edit_post').modal('hide'); + $('#delete_post').modal('show'); return; } @@ -26,79 +34,102 @@ module.exports = React.createClass({ updatedPost.channel_id = this.state.channel_id; Client.updatePost(updatedPost, - function(data) { + function success() { AsyncClient.getPosts(this.state.channel_id); window.scrollTo(0, 0); }.bind(this), - function(err) { - AsyncClient.dispatchError(err, "updatePost"); - }.bind(this) + function error(err) { + AsyncClient.dispatchError(err, 'updatePost'); + } ); - $("#edit_post").modal('hide'); + $('#edit_post').modal('hide'); $(this.state.refocusId).focus(); - }, - handleEditInput: function(editMessage) { + } + handleEditInput(editMessage) { this.setState({editText: editMessage}); - }, - handleEditKeyPress: function(e) { - if (e.which == 13 && !e.shiftKey && !e.altKey) { + } + handleEditKeyPress(e) { + if (e.which === 13 && !e.shiftKey && !e.altKey) { e.preventDefault(); - this.refs.editbox.getDOMNode().blur(); + React.findDOMNode(this.refs.editbox).blur(); this.handleEdit(e); } - }, - handleUserInput: function(e) { - this.setState({ editText: e.target.value }); - }, - componentDidMount: function() { + } + handleUserInput(e) { + this.setState({editText: e.target.value}); + } + componentDidMount() { var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({editText: "", title: "", channel_id: "", post_id: "", comments: 0, refocusId: "", error: ''}); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() { + self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''}); }); - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) { var button = e.relatedTarget; - self.setState({ editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid') }); + self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')}); }); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) { + $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() { self.refs.editbox.resize(); }); - }, - getInitialState: function() { - return { editText: "", title: "", post_id: "", channel_id: "", comments: 0, refocusId: "" }; - }, - render: function() { - var error = this.state.error ? <div className='form-group has-error'><br /><label className='control-label'>{ this.state.error }</label></div> : <div className='form-group'><br /></div>; + } + render() { + var error = (<div className='form-group'><br /></div>); + if (this.state.error) { + error = (<div className='form-group has-error'><br /><label className='control-label'>{this.state.error}</label></div>); + } return ( - <div className="modal fade edit-modal" ref="modal" id="edit_post" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog modal-push-down"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={this.handleEditClose}><span aria-hidden="true">×</span></button> - <h4 className="modal-title">Edit {this.state.title}</h4> - </div> - <div className="edit-modal-body modal-body"> - <Textbox - onUserInput={this.handleEditInput} - onKeyPress={this.handleEditKeyPress} - messageText={this.state.editText} - createMessage="Edit the post..." - id="edit_textbox" - ref="editbox" - /> - { error } - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button> - </div> + <div + className='modal fade edit-modal' + ref='modal' + id='edit_post' + role='dialog' + tabIndex='-1' + aria-hidden='true' > + <div className='modal-dialog modal-push-down'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + onClick={this.handleEditClose}> + <span aria-hidden='true'>×</span> + </button> + <h4 className='modal-title'>Edit {this.state.title}</h4> + </div> + <div className='edit-modal-body modal-body'> + <Textbox + onUserInput={this.handleEditInput} + onKeyPress={this.handleEditKeyPress} + messageText={this.state.editText} + createMessage='Edit the post...' + id='edit_textbox' + ref='editbox' + /> + {error} + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' > + Cancel + </button> + <button + type='button' + className='btn btn-primary' + onClick={this.handleEdit}> + Save + </button> + </div> + </div> </div> - </div> </div> ); } -}); +} diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 678eb9928..f2e91dd98 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -1,35 +1,58 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - handleResend: function() { - window.location.href = window.location.href + "&resend=true" - }, - render: function() { - var title = ""; - var body = ""; - var resend = ""; - if (this.props.isVerified === "true") { - title = config.SiteName + " Email Verified"; - body = <p>Your email has been verified! Click <a href={this.props.teamURL + "?email=" + this.props.userEmail}>here</a> to log in.</p>; +export default class EmailVerify extends React.Component { + constructor(props) { + super(props); + + this.handleResend = this.handleResend.bind(this); + + this.state = {}; + } + handleResend() { + window.location.href = window.location.href + '&resend=true'; + } + render() { + var title = ''; + var body = ''; + var resend = ''; + if (this.props.isVerified === 'true') { + title = config.SiteName + ' Email Verified'; + body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>; } else { - title = config.SiteName + " Email Not Verified"; + title = config.SiteName + ' Email Not Verified'; body = <p>Please verify your email address. Check your inbox for an email.</p>; - resend = <button onClick={this.handleResend} className="btn btn-primary">Resend Email</button> + resend = (<button + onClick={this.handleResend} + className='btn btn-primary' + > + Resend Email + </button>); } return ( - <div className="col-sm-offset-4 col-sm-4"> - <div className="panel panel-default"> - <div className="panel-heading"> - <h3 className="panel-title">{ title }</h3> + <div className='col-sm-offset-4 col-sm-4'> + <div className='panel panel-default'> + <div className='panel-heading'> + <h3 className='panel-title'>{title}</h3> </div> - <div className="panel-body"> - { body } - { resend } + <div className='panel-body'> + {body} + {resend} </div> </div> </div> ); } -}); +} + +EmailVerify.defaultProps = { + isVerified: 'false', + teamURL: '', + userEmail: '' +}; +EmailVerify.propTypes = { + isVerified: React.PropTypes.string, + teamURL: React.PropTypes.string, + userEmail: React.PropTypes.string +}; diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index f7514a009..95f3e572c 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -7,32 +7,40 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -function getStateFromStores() { - var error = ErrorStore.getLastError(); - if (error && error.message !== "There appears to be a problem with your internet connection") { - return { message: error.message }; - } else { - return { message: null }; - } -} +export default class ErrorBar extends React.Component { + constructor() { + super(); -module.exports = React.createClass({ - displayName: 'ErrorBar', + this.onErrorChange = this.onErrorChange.bind(this); + this.handleClose = this.handleClose.bind(this); - componentDidMount: function() { - ErrorStore.addChangeListener(this._onChange); + this.state = this.getStateFromStores(); + if (this.state.message) { + setTimeout(this.handleClose, 10000); + } + } + getStateFromStores() { + var error = ErrorStore.getLastError(); + if (!error || error.message === 'There appears to be a problem with your internet connection') { + return {message: null}; + } + + return {message: error.message}; + } + componentDidMount() { + ErrorStore.addChangeListener(this.onErrorChange); $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); - $(window).resize(function() { + $(window).resize(function onResize() { if (this.state.message) { $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); } }.bind(this)); - }, - componentWillUnmount: function() { - ErrorStore.removeChangeListener(this._onChange); - }, - _onChange: function() { - var newState = getStateFromStores(); + } + componentWillUnmount() { + ErrorStore.removeChangeListener(this.onErrorChange); + } + onErrorChange() { + var newState = this.getStateFromStores(); if (!utils.areStatesEqual(newState, this.state)) { if (newState.message) { setTimeout(this.handleClose, 10000); @@ -40,9 +48,11 @@ module.exports = React.createClass({ this.setState(newState); } - }, - handleClose: function(e) { - if (e) e.preventDefault(); + } + handleClose(e) { + if (e) { + e.preventDefault(); + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ERROR, @@ -50,24 +60,22 @@ module.exports = React.createClass({ }); $('body').css('padding-top', '0'); - }, - getInitialState: function() { - var state = getStateFromStores(); - if (state.message) { - setTimeout(this.handleClose, 10000); - } - return state; - }, - render: function() { + } + render() { if (this.state.message) { return ( - <div className="error-bar"> + <div className='error-bar'> <span>{this.state.message}</span> - <a href="#" className="error-bar__close" onClick={this.handleClose}>×</a> + <a + href='#' + className='error-bar__close' + onClick={this.handleClose}> + × + </a> </div> ); - } else { - return <div/>; } + + return <div/>; } -});
\ No newline at end of file +} diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 45e6c5e28..d07afbc2b 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -5,31 +5,24 @@ var utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - displayName: "FileAttachment", - canSetState: false, - propTypes: { - // a list of file pathes displayed by the parent FileAttachmentList - filename: React.PropTypes.string.isRequired, - // the index of this attachment preview in the parent FileAttachmentList - index: React.PropTypes.number.isRequired, - // the identifier of the modal dialog used to preview files - modalId: React.PropTypes.string.isRequired, - // handler for when the thumbnail is clicked - handleImageClick: React.PropTypes.func - }, - getInitialState: function() { - return {fileSize: -1}; - }, - componentDidMount: function() { +export default class FileAttachment extends React.Component { + constructor(props) { + super(props); + + this.loadFiles = this.loadFiles.bind(this); + + this.canSetState = false; + this.state = {fileSize: -1}; + } + componentDidMount() { this.loadFiles(); - }, - componentDidUpdate: function(prevProps) { + } + componentDidUpdate(prevProps) { if (this.props.filename !== prevProps.filename) { this.loadFiles(); } - }, - loadFiles: function() { + } + loadFiles() { this.canSetState = true; var filename = this.props.filename; @@ -39,91 +32,92 @@ module.exports = React.createClass({ var type = utils.getFileType(fileInfo.ext); // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf("/api/v1/files/get") != -1) { - fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1]; + if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { + fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; } - fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path; - - if (type === "image") { - var self = this; - $('<img/>').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() { - $(this).remove(); - if (name in self.refs) { - var imgDiv = self.refs[name].getDOMNode(); - - $(imgDiv).removeClass('post__load'); - $(imgDiv).addClass('post__image'); - - var width = this.width || $(this).width(); - var height = this.height || $(this).height(); - - if (width < Constants.THUMBNAIL_WIDTH - && height < Constants.THUMBNAIL_HEIGHT) { - $(imgDiv).addClass('small'); - } else { - $(imgDiv).addClass('normal'); + fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; + + if (type === 'image') { + var self = this; // Need this reference since we use the given "this" + $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) { + return function loader() { + $(this).remove(); + if (name in self.refs) { + var imgDiv = React.findDOMNode(self.refs[name]); + + $(imgDiv).removeClass('post__load'); + $(imgDiv).addClass('post__image'); + + var width = this.width || $(this).width(); + var height = this.height || $(this).height(); + + if (width < Constants.THUMBNAIL_WIDTH && + height < Constants.THUMBNAIL_HEIGHT) { + $(imgDiv).addClass('small'); + } else { + $(imgDiv).addClass('normal'); + } + + var re1 = new RegExp(' ', 'g'); + var re2 = new RegExp('\\(', 'g'); + var re3 = new RegExp('\\)', 'g'); + var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)'); } - - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)'); - } - }}(fileInfo.path, filename)); + }; }(fileInfo.path, filename)); } } - }, - componentWillUnmount: function() { + } + componentWillUnmount() { // keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted this.canSetState = false; - }, - shouldComponentUpdate: function(nextProps, nextState) { + } + shouldComponentUpdate(nextProps, nextState) { if (!utils.areStatesEqual(nextProps, this.props)) { return true; } // the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering - if (nextState.fileSize != this.state.fileSize) { + if (nextState.fileSize !== this.state.fileSize) { if (this.refs.fileSize) { // update the UI element to display the file size without re-rendering the whole component - this.refs.fileSize.getDOMNode().innerHTML = utils.fileSizeToString(nextState.fileSize); + React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize); return false; - } else { - // we can't find the element that should hold the file size so we must not have rendered yet - return true; } - } else { + + // we can't find the element that should hold the file size so we must not have rendered yet return true; } - }, - render: function() { + + return true; + } + render() { var filename = this.props.filename; var fileInfo = utils.splitFileLocation(filename); var type = utils.getFileType(fileInfo.ext); var thumbnail; - if (type === "image") { - thumbnail = <div ref={filename} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}/>; + if (type === 'image') { + thumbnail = (<div + ref={filename} + className='post__load' + style={{backgroundImage: 'url(/static/images/load.gif)'}} />); } else { - thumbnail = <div className={"file-icon "+utils.getIconClassName(type)}/>; + thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>; } - var fileSizeString = ""; + var fileSizeString = ''; if (this.state.fileSize < 0) { - var self = this; - Client.getFileInfo( filename, - function(data) { - if (self.canSetState) { - self.setState({fileSize: parseInt(data["size"], 10)}); + function success(data) { + if (this.canSetState) { + this.setState({fileSize: parseInt(data.size, 10)}); } - }, - function(err) { - } + }.bind(this), + function error() {} ); } else { fileSizeString = utils.fileSizeToString(this.state.fileSize); @@ -132,25 +126,51 @@ module.exports = React.createClass({ var filenameString = decodeURIComponent(utils.getFileName(filename)); var trimmedFilename; if (filenameString.length > 35) { - trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "..."; + trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...'; } else { trimmedFilename = filenameString; } return ( - <div className="post-image__column" key={filename}> - <a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick} - data-img-id={this.props.index} data-toggle="modal" data-target={"#" + this.props.modalId }> + <div + className='post-image__column' + key={filename}> + <a className='post-image__thumbnail' + href='#' + onClick={this.props.handleImageClick} + data-img-id={this.props.index} + data-toggle='modal' + data-target={'#' + this.props.modalId} > {thumbnail} </a> - <div className="post-image__details"> - <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div> + <div className='post-image__details'> + <div + data-toggle='tooltip' + title={filenameString} + className='post-image__name' > + {trimmedFilename} + </div> <div> - <span className="post-image__type">{fileInfo.ext.toUpperCase()}</span> - <span className="post-image__size">{fileSizeString}</span> + <span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span> + <span className='post-image__size'>{fileSizeString}</span> </div> </div> </div> ); } -}); +} + +FileAttachment.propTypes = { + + // a list of file pathes displayed by the parent FileAttachmentList + filename: React.PropTypes.string.isRequired, + + // the index of this attachment preview in the parent FileAttachmentList + index: React.PropTypes.number.isRequired, + + // the identifier of the modal dialog used to preview files + modalId: React.PropTypes.string.isRequired, + + // handler for when the thumbnail is clicked + handleImageClick: React.PropTypes.func +}; diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx index df4424d03..33643de73 100644 --- a/web/react/components/file_attachment_list.jsx +++ b/web/react/components/file_attachment_list.jsx @@ -5,33 +5,30 @@ var ViewImageModal = require('./view_image.jsx'); var FileAttachment = require('./file_attachment.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - displayName: "FileAttachmentList", - propTypes: { - // a list of file pathes displayed by this - filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, - // the identifier of the modal dialog used to preview files - modalId: React.PropTypes.string.isRequired, - // the channel that this is part of - channelId: React.PropTypes.string, - // the user that owns the post that this is attached to - userId: React.PropTypes.string - }, - getInitialState: function() { - return {startImgId: 0}; - }, - render: function() { +export default class FileAttachmentList extends React.Component { + constructor(props) { + super(props); + this.state = {startImgId: 0}; + } + render() { var filenames = this.props.filenames; var modalId = this.props.modalId; var postFiles = []; for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) { - postFiles.push(<FileAttachment key={i} filename={filenames[i]} index={i} modalId={modalId} handleImageClick={this.handleImageClick} />); + postFiles.push( + <FileAttachment + key={i} + filename={filenames[i]} + index={i} + modalId={modalId} + handleImageClick={this.handleImageClick} /> + ); } return ( <div> - <div className="post-image__columns"> + <div className='post-image__columns'> {postFiles} </div> <ViewImageModal @@ -42,8 +39,23 @@ module.exports = React.createClass({ filenames={filenames} /> </div> ); - }, - handleImageClick: function(e) { - this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))}); } -}); + handleImageClick(e) { + this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)}); + } +} + +FileAttachmentList.propTypes = { + + // a list of file pathes displayed by this + filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, + + // the identifier of the modal dialog used to preview files + modalId: React.PropTypes.string.isRequired, + + // the channel that this is part of + channelId: React.PropTypes.string, + + // the user that owns the post that this is attached to + userId: React.PropTypes.string +}; diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index d1b2f734a..33382a439 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -1,14 +1,17 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); -var Constants = require('../utils/constants.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - handleRemove: function(e) { +export default class FilePreview extends React.Component { + constructor(props) { + super(props); + + this.handleRemove = this.handleRemove.bind(this); + + this.state = {}; + } + handleRemove(e) { var previewDiv = e.target.parentNode.parentNode; if (previewDiv.hasAttribute('data-filename')) { @@ -16,51 +19,96 @@ module.exports = React.createClass({ } else if (previewDiv.hasAttribute('data-client-id')) { this.props.onRemove(previewDiv.getAttribute('data-client-id')); } - }, - render: function() { + } + render() { var previews = []; - this.props.files.forEach(function(filename) { - + this.props.files.forEach(function setupPreview(fullFilename) { + var filename = fullFilename; var originalFilename = filename; var filenameSplit = filename.split('.'); - var ext = filenameSplit[filenameSplit.length-1]; - var type = utils.getFileType(ext); + var ext = filenameSplit[filenameSplit.length - 1]; + var type = Utils.getFileType(ext); + // This is a temporary patch to fix issue with old files using absolute paths - if (filename.indexOf("/api/v1/files/get") != -1) { - filename = filename.split("/api/v1/files/get")[1]; + + if (filename.indexOf('/api/v1/files/get') !== -1) { + filename = filename.split('/api/v1/files/get')[1]; } - filename = utils.getWindowLocationOrigin() + "/api/v1/files/get" + filename; + filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename; - if (type === "image") { + if (type === 'image') { previews.push( - <div key={filename} className="preview-div" data-filename={originalFilename}> - <img className="preview-img" src={filename}/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={filename} + className='preview-div' + data-filename={originalFilename} + > + <img + className='preview-img' + src={filename} + /> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); } else { previews.push( - <div key={filename} className="preview-div custom-file" data-filename={originalFilename}> - <div className={"file-icon "+utils.getIconClassName(type)}/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={filename} + className='preview-div custom-file' + data-filename={originalFilename} + > + <div className={'file-icon ' + Utils.getIconClassName(type)}/> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); } }.bind(this)); - this.props.uploadsInProgress.forEach(function(clientId) { + this.props.uploadsInProgress.forEach(function addUploadsInProgress(clientId) { previews.push( - <div className="preview-div" data-client-id={clientId}> - <img className="spinner" src="/static/images/load.gif"/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={clientId} + className='preview-div' + data-client-id={clientId} + > + <img + className='spinner' + src='/static/images/load.gif' + /> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); }.bind(this)); return ( - <div className="preview-container"> + <div className='preview-container'> {previews} </div> ); } -}); +} + +FilePreview.defaultProps = { + files: null, + uploadsInProgress: null +}; +FilePreview.propTypes = { + onRemove: React.PropTypes.func.isRequired, + files: React.PropTypes.array, + uploadsInProgress: React.PropTypes.array +}; diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx index f35556371..265924206 100644 --- a/web/react/components/file_upload_overlay.jsx +++ b/web/react/components/file_upload_overlay.jsx @@ -1,12 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - displayName: 'FileUploadOverlay', - propTypes: { - overlayType: React.PropTypes.string - }, - render: function() { +export default class FileUploadOverlay extends React.Component { + render() { var overlayClass = 'file-overlay hidden'; if (this.props.overlayType === 'right') { overlayClass += ' right-file-overlay'; @@ -23,4 +19,8 @@ module.exports = React.createClass({ </div> ); } -}); +} + +FileUploadOverlay.propTypes = { + overlayType: React.PropTypes.string +}; diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index 3b10926f5..fc32d946b 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -2,69 +2,114 @@ // See License.txt for license information. var UserStore = require('../stores/user_store.jsx'); -var ZeroClipboardMixin = require('react-zeroclipboard-mixin'); -ZeroClipboardMixin.ZeroClipboard.config({ - swfPath: '../../static/flash/ZeroClipboard.swf' -}); +export default class GetLinkModal extends React.Component { + constructor(props) { + super(props); -module.exports = React.createClass({ - displayName: 'GetLinkModal', - zeroclipboardElementsSelector: '[data-copy-btn]', - mixins: [ZeroClipboardMixin], - componentDidMount: function() { - var self = this; + this.handleClick = this.handleClick.bind(this); + + this.state = {copiedLink: false}; + } + componentDidMount() { if (this.refs.modal) { - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { - var button = e.relatedTarget; - self.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); - }); - $(this.refs.modal.getDOMNode()).on('hide.bs.modal', function() { - self.setState({copiedLink: false}); - }); + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) { + var button = e.relatedTarget; + this.setState({title: $(button).attr('data-title'), value: $(button).attr('data-value')}); + }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function hide() { + this.setState({copiedLink: false}); + }.bind(this)); + } + } + handleClick() { + var copyTextarea = $(React.findDOMNode(this.refs.textarea)); + copyTextarea.select(); + + try { + var successful = document.execCommand('copy'); + if (successful) { + this.setState({copiedLink: true}); + } else { + this.setState({copiedLink: false}); + } + } catch (err) { + this.setState({copiedLink: false}); } - }, - getInitialState: function() { - return {copiedLink: false}; - }, - handleClick: function() { - this.setState({copiedLink: true}); - }, - render: function() { + } + render() { var currentUser = UserStore.getCurrentUser(); var copyLinkConfirm = null; if (this.state.copiedLink) { - copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className="fa fa-check"></i> Link copied to clipboard.</p>; + copyLinkConfirm = <p className='alert alert-success copy-link-confirm'><i className='fa fa-check'></i> Link copied to clipboard.</p>; } if (currentUser != null) { return ( - <div className='modal fade' ref='modal' id='get_link' tabIndex='-1' role='dialog' aria-hidden='true'> - <div className='modal-dialog'> - <div className='modal-content'> - <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' id='myModalLabel'>{this.state.title} Link</h4> - </div> - <div className='modal-body'> - <p> - Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site. - <br /><br /> - Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}. - </p> - <textarea className='form-control no-resize' readOnly='true' value={this.state.value}></textarea> - </div> - <div className='modal-footer'> - <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button> - <button data-copy-btn='true' type='button' className='btn btn-primary pull-left' onClick={this.handleClick} data-clipboard-text={this.state.value}>Copy Link</button> - {copyLinkConfirm} + <div + className='modal fade' + ref='modal' + id='get_link' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + id='myModalLabel' + > + {this.state.title} Link + </h4> + </div> + <div className='modal-body'> + <p> + Send {strings.Team + 'mates'} the link below for them to sign-up to this {strings.Team} site. + <br /><br /> + Be careful not to share this link publicly, since anyone with the link can join your {strings.Team}. + </p> + <textarea + className='form-control no-resize' + readOnly='true' + ref='textarea' + value={this.state.value} + /> + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Close + </button> + <button + data-copy-btn='true' + type='button' + className='btn btn-primary pull-left' + onClick={this.handleClick} + data-clipboard-text={this.state.value} + > + Copy Link + </button> + {copyLinkConfirm} + </div> </div> - </div> - </div> + </div> </div> ); } return <div/>; } -}); +} diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx index 5905e519b..b0f42ce86 100644 --- a/web/react/components/loading_screen.jsx +++ b/web/react/components/loading_screen.jsx @@ -1,24 +1,31 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - displayName: "LoadingScreen", - propTypes: { - position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']) - }, - getDefaultProps: function() { - return { position: 'relative' }; - }, - render: function() { +export default class LoadingScreen extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { return ( - <div className="loading-screen" style={{position: this.props.position}}> - <div className="loading__content"> + <div + className='loading-screen' + style={{position: this.props.position}} + > + <div className='loading__content'> <h3>Loading</h3> - <div className="round round-1"></div> - <div className="round round-2"></div> - <div className="round round-3"></div> + <div className='round round-1'></div> + <div className='round round-2'></div> + <div className='round round-3'></div> </div> </div> ); } -}); +} + +LoadingScreen.defaultProps = { + position: 'relative' +}; +LoadingScreen.propTypes = { + position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']) +}; diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 28dd64c39..f87e77ff7 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -1,11 +1,11 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var BrowserStore = require('../stores/browser_store.jsx'); -var Constants = require('../utils/constants.jsx'); +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const BrowserStore = require('../stores/browser_store.jsx'); +const Constants = require('../utils/constants.jsx'); export default class Login extends React.Component { constructor(props) { @@ -17,23 +17,23 @@ export default class Login extends React.Component { } handleSubmit(e) { e.preventDefault(); - var state = {}; + let state = {}; - var name = this.props.teamName; + const name = this.props.teamName; if (!name) { state.serverError = 'Bad team name'; this.setState(state); return; } - var email = this.refs.email.getDOMNode().value.trim(); + const email = React.findDOMNode(this.refs.email).value.trim(); if (!email) { state.serverError = 'An email is required'; this.setState(state); return; } - var password = this.refs.password.getDOMNode().value.trim(); + const password = React.findDOMNode(this.refs.password).value.trim(); if (!password) { state.serverError = 'A password is required'; this.setState(state); @@ -49,12 +49,12 @@ export default class Login extends React.Component { state.serverError = ''; this.setState(state); - client.loginByEmail(name, email, password, + Client.loginByEmail(name, email, password, function loggedIn(data) { UserStore.setCurrentUser(data); UserStore.setLastEmail(email); - var redirect = utils.getUrlParameter('redirect'); + const redirect = Utils.getUrlParameter('redirect'); if (redirect) { window.location.href = decodeURIComponent(redirect); } else { @@ -73,31 +73,31 @@ export default class Login extends React.Component { ); } render() { - var serverError; + let serverError; if (this.state.serverError) { serverError = <label className='control-label'>{this.state.serverError}</label>; } - var priorEmail = UserStore.getLastEmail(); + let priorEmail = UserStore.getLastEmail(); - var emailParam = utils.getUrlParameter('email'); + const emailParam = Utils.getUrlParameter('email'); if (emailParam) { priorEmail = decodeURIComponent(emailParam); } - var teamDisplayName = this.props.teamDisplayName; - var teamName = this.props.teamName; + const teamDisplayName = this.props.teamDisplayName; + const teamName = this.props.teamName; - var focusEmail = false; - var focusPassword = false; + let focusEmail = false; + let focusPassword = false; if (priorEmail !== '') { focusPassword = true; } else { focusEmail = true; } - var authServices = JSON.parse(this.props.authServices); + const authServices = JSON.parse(this.props.authServices); - var loginMessage = []; + let loginMessage = []; if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) { loginMessage.push( <a @@ -110,12 +110,12 @@ export default class Login extends React.Component { ); } - var errorClass = ''; + let errorClass = ''; if (serverError) { errorClass = ' has-error'; } - var emailSignup; + let emailSignup; if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { emailSignup = ( <div> @@ -163,7 +163,7 @@ export default class Login extends React.Component { ); } - var forgotPassword; + let forgotPassword; if (emailSignup) { forgotPassword = ( <div className='form-group'> diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx index a5279909b..d244939f5 100644 --- a/web/react/components/member_list_item.jsx +++ b/web/react/components/member_list_item.jsx @@ -1,64 +1,116 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - displayName: 'MemberListItem', - handleInvite: function(e) { +export default class MemberListItem extends React.Component { + constructor(props) { + super(props); + + this.handleInvite = this.handleInvite.bind(this); + this.handleRemove = this.handleRemove.bind(this); + this.handleMakeAdmin = this.handleMakeAdmin.bind(this); + } + handleInvite(e) { e.preventDefault(); this.props.handleInvite(this.props.member.id); - }, - handleRemove: function(e) { + } + handleRemove(e) { e.preventDefault(); this.props.handleRemove(this.props.member.id); - }, - handleMakeAdmin: function(e) { + } + handleMakeAdmin(e) { e.preventDefault(); this.props.handleMakeAdmin(this.props.member.id); - }, - render: function() { - + } + render() { var member = this.props.member; var isAdmin = this.props.isAdmin; - var isMemberAdmin = member.roles.indexOf("admin") > -1; + var isMemberAdmin = member.roles.indexOf('admin') > -1; var timestamp = UserStore.getCurrentUser().update_at; var invite; if (member.invited && this.props.handleInvite) { - invite = <span className="member-role">Added</span>; + invite = <span className='member-role'>Added</span>; } else if (this.props.handleInvite) { - invite = <a onClick={this.handleInvite} className="btn btn-sm btn-primary member-invite"><i className="glyphicon glyphicon-envelope"/> Add</a>; - } else if (isAdmin && !isMemberAdmin && (member.id != UserStore.getCurrentId())) { + invite = ( + <a + onClick={this.handleInvite} + className='btn btn-sm btn-primary member-invite'> + <i className='glyphicon glyphicon-envelope'/> Add + </a> + ); + } else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) { var self = this; + + let makeAdminOption = null; + if (makeAdminOption) { + makeAdminOption = ( + <li role='presentation'> + <a + href='' + role='menuitem' + onClick={self.handleMakeAdmin}>Make Admin + </a> + </li>); + } + + let handleRemoveOption = null; + if (handleRemoveOption) { + handleRemoveOption = ( + <li role='presentation'> + <a + href='' + role='menuitem' + onClick={self.handleRemove}>Remove Member + </a> + </li>); + } + invite = ( - <div className="dropdown member-drop"> - <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> - <span className="text-capitalize">{member.roles || 'Member'} </span> - <span className="caret"></span> + <div className='dropdown member-drop'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_dropdown' + data-toggle='dropdown' + aria-expanded='true' > + <span className='text-capitalize'>{member.roles || 'Member'} </span> + <span className='caret'></span> </a> - <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown"> - { this.props.handleMakeAdmin ? - <li role="presentation"><a href="" role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li> - : null } - { this.props.handleRemove ? - <li role="presentation"><a href="" role="menuitem" onClick={self.handleRemove}>Remove Member</a></li> - : null } + <ul + className='dropdown-menu member-menu' + role='menu' + aria-labelledby='channel_header_dropdown'> + {makeAdminOption} + {handleRemoveOption} </ul> </div> ); } else { - invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>; + invite = <div className='member-role text-capitalize'>{member.roles || 'Member'}<span className='caret hidden'></span></div>; } return ( - <div className="row member-div"> - <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" /> - <span className="member-name">{member.username}</span> - <span className="member-email">{member.email}</span> - { invite } + <div className='row member-div'> + <img + className='post-profile-img pull-left' + src={'/api/v1/users/' + member.id + '/image?time=' + timestamp} + height='36' + width='36' /> + <span className='member-name'>{member.username}</span> + <span className='member-email'>{member.email}</span> + {invite} </div> ); } -}); +} + +MemberListItem.propTypes = { + handleInvite: React.PropTypes.func, + handleRemove: React.PropTypes.func, + handleMakeAdmin: React.PropTypes.func, + member: React.PropTypes.object, + isAdmin: React.PropTypes.bool +}; diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx index cb48e5cc5..064330c8d 100644 --- a/web/react/components/member_list_team.jsx +++ b/web/react/components/member_list_team.jsx @@ -1,122 +1,27 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var ChannelStore = require('../stores/channel_store.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var Client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var utils = require('../utils/utils.jsx'); - -var MemberListTeamItem = React.createClass({ - handleMakeMember: function() { - var data = {}; - data["user_id"] = this.props.user.id; - data["new_roles"] = ""; - - Client.updateRoles(data, - function(data) { - AsyncClient.getProfiles(); - }.bind(this), - function(err) { - this.setState({ server_error: err.message }); - }.bind(this) - ); - }, - handleMakeActive: function() { - Client.updateActive(this.props.user.id, true, - function(data) { - AsyncClient.getProfiles(); - }.bind(this), - function(err) { - this.setState({ server_error: err.message }); - }.bind(this) - ); - }, - handleMakeNotActive: function() { - Client.updateActive(this.props.user.id, false, - function(data) { - AsyncClient.getProfiles(); - }.bind(this), - function(err) { - this.setState({ server_error: err.message }); - }.bind(this) - ); - }, - handleMakeAdmin: function() { - var data = {}; - data["user_id"] = this.props.user.id; - data["new_roles"] = "admin"; - - Client.updateRoles(data, - function(data) { - AsyncClient.getProfiles(); - }.bind(this), - function(err) { - this.setState({ server_error: err.message }); - }.bind(this) - ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null; - var user = this.props.user; - var currentRoles = "Member"; - var timestamp = UserStore.getCurrentUser().update_at; - - if (user.roles.length > 0) { - currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); - } - - var email = user.email.length > 0 ? user.email : ""; - var showMakeMember = user.roles == "admin"; - var showMakeAdmin = user.roles == ""; - var showMakeActive = false; - var showMakeNotActive = true; - - if (user.delete_at > 0) { - currentRoles = "Inactive"; - showMakeMember = false; - showMakeAdmin = false; - showMakeActive = true; - showMakeNotActive = false; - } +const MemberListTeamItem = require('./member_list_team_item.jsx'); + +export default class MemberListTeam extends React.Component { + render() { + const memberList = this.props.users.map(function makeListItem(user) { + return ( + <MemberListTeamItem + key={user.id} + user={user} + /> + ); + }, this); return ( - <div className="row member-div"> - <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" /> - <span className="member-name">{utils.getDisplayName(user)}</span> - <span className="member-email">{email}</span> - <div className="dropdown member-drop"> - <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> - <span>{currentRoles} </span> - <span className="caret"></span> - </a> - <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown"> - { showMakeAdmin ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" } - { showMakeMember ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeMember}>Make Member</a></li> : "" } - { showMakeActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeActive}>Make Active</a></li> : "" } - { showMakeNotActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" } - </ul> - </div> - { server_error } + <div className='member-list-holder'> + {memberList} </div> ); } -}); +} - -module.exports = React.createClass({ - render: function() { - return ( - <div className="member-list-holder"> - { - this.props.users.map(function(user) { - return <MemberListTeamItem key={user.id} user={user} />; - }, this) - } - </div> - ); - } -}); +MemberListTeam.propTypes = { + users: React.PropTypes.arrayOf(React.PropTypes.object).isRequired +}; diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx new file mode 100644 index 000000000..b7e81f843 --- /dev/null +++ b/web/react/components/member_list_team_item.jsx @@ -0,0 +1,203 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const UserStore = require('../stores/user_store.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const Utils = require('../utils/utils.jsx'); + +export default class MemberListTeamItem extends React.Component { + constructor(props) { + super(props); + + this.handleMakeMember = this.handleMakeMember.bind(this); + this.handleMakeActive = this.handleMakeActive.bind(this); + this.handleMakeNotActive = this.handleMakeNotActive.bind(this); + this.handleMakeAdmin = this.handleMakeAdmin.bind(this); + + this.state = {}; + } + handleMakeMember() { + const data = { + user_id: this.props.user.id, + new_roles: '' + }; + + Client.updateRoles(data, + function handleMakeMemberSuccess() { + AsyncClient.getProfiles(); + }, + function handleMakeMemberError(err) { + this.setState({serverError: err.message}); + }.bind(this) + ); + } + handleMakeActive() { + Client.updateActive(this.props.user.id, true, + function handleMakeActiveSuccess() { + AsyncClient.getProfiles(); + }, + function handleMakeActiveError(err) { + this.setState({serverError: err.message}); + }.bind(this) + ); + } + handleMakeNotActive() { + Client.updateActive(this.props.user.id, false, + function handleMakeNotActiveSuccess() { + AsyncClient.getProfiles(); + }, + function handleMakeNotActiveError(err) { + this.setState({serverError: err.message}); + }.bind(this) + ); + } + handleMakeAdmin() { + const data = { + user_id: this.props.user.id, + new_roles: 'admin' + }; + + Client.updateRoles(data, + function handleMakeAdminSuccess() { + AsyncClient.getProfiles(); + }, + function handleMakeAdmitError(err) { + this.setState({serverError: err.message}); + }.bind(this) + ); + } + render() { + let serverError = null; + if (this.state.serverError) { + serverError = ( + <div className='has-error'> + <label className='has-error control-label'>{this.state.serverError}</label> + </div> + ); + } + + const user = this.props.user; + let currentRoles = 'Member'; + const timestamp = UserStore.getCurrentUser().update_at; + + if (user.roles.length > 0) { + currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); + } + + const email = user.email; + let showMakeMember = user.roles === 'admin'; + let showMakeAdmin = user.roles === ''; + let showMakeActive = false; + let showMakeNotActive = true; + + if (user.delete_at > 0) { + currentRoles = 'Inactive'; + showMakeMember = false; + showMakeAdmin = false; + showMakeActive = true; + showMakeNotActive = false; + } + + let makeAdmin = null; + if (showMakeAdmin) { + makeAdmin = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeAdmin} + > + Make Admin + </a> + </li> + ); + } + + let makeMember = null; + if (showMakeMember) { + makeMember = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeMember} + > + Make Member + </a> + </li> + ); + } + + let makeActive = null; + if (showMakeActive) { + makeActive = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeActive} + > + Make Active + </a> + </li> + ); + } + + let makeNotActive = null; + if (showMakeNotActive) { + makeNotActive = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleMakeNotActive} + > + Make Inactive + </a> + </li> + ); + } + + return ( + <div className='row member-div'> + <img + className='post-profile-img pull-left' + src={`/api/v1/users/${user.id}/image?time=${timestamp}`} + height='36' + width='36' + /> + <span className='member-name'>{Utils.getDisplayName(user)}</span> + <span className='member-email'>{email}</span> + <div className='dropdown member-drop'> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_dropdown' + data-toggle='dropdown' + aria-expanded='true' + > + <span>{currentRoles} </span> + <span className='caret'></span> + </a> + <ul + className='dropdown-menu member-menu' + role='menu' + aria-labelledby='channel_header_dropdown' + > + {makeAdmin} + {makeMember} + {makeActive} + {makeNotActive} + </ul> + </div> + {serverError} + </div> + ); + } +} + +MemberListTeamItem.propTypes = { + user: React.PropTypes.object.isRequired +}; diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 114dc183f..72a2a6251 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -1,30 +1,57 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require("../stores/user_store.jsx"); +var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - handleClick: function() { +export default class Mention extends React.Component { + constructor(props) { + super(props); + + this.handleClick = this.handleClick.bind(this); + + this.state = null; + } + handleClick() { this.props.handleClick(this.props.username); - }, - getInitialState: function() { - return null; - }, - render: function() { - var self = this; + } + render() { var icon; var timestamp = UserStore.getCurrentUser().update_at; - if (this.props.id === "allmention" || this.props.id === "channelmention") { - icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; + if (this.props.id === 'allmention' || this.props.id === 'channelmention') { + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } else if (this.props.id != null) { - icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>; + icon = (<span><img + className='mention-img' + src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp} + /> + </span>); } else { - icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } return ( - <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}> - <div className="pull-left">{icon}</div> - <div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div> + <div + className={'mentions-name ' + this.props.isFocused} + id={this.props.id + '_mentions'} + onClick={this.handleClick} + onMouseEnter={this.props.handleMouseEnter} + > + <div className='pull-left'>{icon}</div> + <div className='pull-left mention-align'><span>@{this.props.username}</span><span className='mention-fullname'>{this.props.secondary_text}</span></div> </div> ); } -}); +} + +Mention.defaultProps = { + username: '', + id: '', + isFocused: '', + secondary_text: '' +}; +Mention.propTypes = { + handleClick: React.PropTypes.func.isRequired, + handleMouseEnter: React.PropTypes.func.isRequired, + username: React.PropTypes.string, + id: React.PropTypes.string, + isFocused: React.PropTypes.string, + secondary_text: React.PropTypes.string +}; diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index f562cfb29..afea30161 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -14,54 +14,66 @@ var MAX_HEIGHT_LIST = 292; var MAX_ITEMS_IN_LIST = 25; var ITEM_HEIGHT = 36; -module.exports = React.createClass({ - displayName: 'MentionList', - componentDidMount: function() { +export default class MentionList extends React.Component { + constructor(props) { + super(props); + + this.onListenerChange = this.onListenerChange.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.getSelection = this.getSelection.bind(this); + this.addCurrentMention = this.addCurrentMention.bind(this); + this.addFirstMention = this.addFirstMention.bind(this); + this.isEmpty = this.isEmpty.bind(this); + this.scrollToMention = this.scrollToMention.bind(this); + + this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''}; + } + componentDidMount() { PostStore.addMentionDataChangeListener(this.onListenerChange); - var self = this; - $('.post-right__scroll').scroll(function(){ - if($('.mentions--top').length){ - $('#reply_mention_tab .mentions--top').css({ bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top }); + $('.post-right__scroll').scroll(function onScroll() { + if ($('.mentions--top').length) { + $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top}); } }); $('body').on('keydown.mentionlist', '#' + this.props.id, - function(e) { - if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { + function onMentionListKey(e) { + if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) { e.stopPropagation(); e.preventDefault(); - self.addCurrentMention(); - } else if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { + this.addCurrentMention(); + } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) { e.stopPropagation(); e.preventDefault(); if (e.which === 38) { - if (self.getSelection(self.state.selectedMention - 1)) { - self.setState({selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username}); + if (this.getSelection(this.state.selectedMention - 1)) { + this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username}); } } else if (e.which === 40) { - if (self.getSelection(self.state.selectedMention + 1)) { - self.setState({selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username}); + if (this.getSelection(this.state.selectedMention + 1)) { + this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username}); } } - self.scrollToMention(e.which); + this.scrollToMention(e.which); } - } + }.bind(this) ); - $(document).click(function(e) { - if (!($('#' + self.props.id).is(e.target) || $('#' + self.props.id).has(e.target).length || - ('mentionlist' in self.refs && $(self.refs.mentionlist.getDOMNode()).has(e.target).length))) { - self.setState({mentionText: '-1'}); + $(document).click(function onClick(e) { + if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length || + ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) { + this.setState({mentionText: '-1'}); } - }); - }, - componentWillUnmount: function() { + }.bind(this)); + } + componentWillUnmount() { PostStore.removeMentionDataChangeListener(this.onListenerChange); $('body').off('keydown.mentionlist', '#' + this.props.id); - }, - componentDidUpdate: function() { + } + componentDidUpdate() { if (this.state.mentionText !== '-1') { if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) { var tempSelectedMention = -1; @@ -80,8 +92,8 @@ module.exports = React.createClass({ } else if (this.state.selectedMention !== 0) { this.setState({selectedMention: 0, selectedUsername: ''}); } - }, - onListenerChange: function(id, mentionText) { + } + onListenerChange(id, mentionText) { if (id !== this.props.id) { return; } @@ -92,8 +104,8 @@ module.exports = React.createClass({ } this.setState(newState); - }, - handleClick: function(name) { + } + handleClick(name) { AppDispatcher.handleViewAction({ type: ActionTypes.RECIEVED_ADD_MENTION, id: this.props.id, @@ -101,33 +113,33 @@ module.exports = React.createClass({ }); this.setState({mentionText: '-1'}); - }, - handleMouseEnter: function(listId) { + } + handleMouseEnter(listId) { this.setState({selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username}); - }, - getSelection: function(listId) { + } + getSelection(listId) { if (!this.refs['mention' + listId]) { return false; } return true; - }, - addCurrentMention: function() { + } + addCurrentMention() { if (!this.getSelection(this.state.selectedMention)) { this.addFirstMention(); } else { this.refs['mention' + this.state.selectedMention].handleClick(); } - }, - addFirstMention: function() { + } + addFirstMention() { if (!this.refs.mention0) { return; } this.refs.mention0.handleClick(); - }, - isEmpty: function() { + } + isEmpty() { return (!this.refs.mention0); - }, - scrollToMention: function(keyPressed) { + } + scrollToMention(keyPressed) { var direction; if (keyPressed === 38) { direction = 'up'; @@ -145,12 +157,8 @@ module.exports = React.createClass({ $('#mentionsbox').animate({ scrollTop: scrollAmount }, 75); - }, - getInitialState: function() { - return {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''}; - }, - render: function() { - var self = this; + } + render() { var mentionText = this.state.mentionText; if (mentionText === '-1') { return null; @@ -158,8 +166,10 @@ module.exports = React.createClass({ var profiles = UserStore.getActiveOnlyProfiles(); var users = []; - for (var id in profiles) { - users.push(profiles[id]); + for (let id in profiles) { + if (profiles[id]) { + users.push(profiles[id]); + } } var all = {}; @@ -176,7 +186,7 @@ module.exports = React.createClass({ channel.id = 'channelmention'; users.push(channel); - users.sort(function(a, b) { + users.sort(function sortByUsername(a, b) { if (a.username < b.username) { return -1; } @@ -185,29 +195,34 @@ module.exports = React.createClass({ } return 0; }); - var mentions = {}; + var mentions = []; var index = 0; for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) { if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText, 0) === 0) || (users[i].last_name && users[i].last_name.lastIndexOf(mentionText, 0) === 0) || users[i].username.lastIndexOf(mentionText, 0) === 0) { + let isFocused = ''; + if (this.state.selectedMention === index) { + isFocused = 'mentions-focus'; + } mentions[index] = ( <Mention + key={'mention_key_' + index} ref={'mention' + index} username={users[i].username} secondary_text={Utils.getFullName(users[i])} id={users[i].id} listId={index} - isFocused={this.state.selectedMention === index ? 'mentions-focus' : ''} - handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)} + isFocused={isFocused} + handleMouseEnter={this.handleMouseEnter.bind(this, index)} handleClick={this.handleClick} /> ); index++; } } - var numMentions = Object.keys(mentions).length; + var numMentions = mentions.length; if (numMentions < 1) { return null; @@ -223,11 +238,20 @@ module.exports = React.createClass({ }; return ( - <div className='mentions--top' style={style}> - <div ref='mentionlist' className='mentions-box' id='mentionsbox'> + <div + className='mentions--top' + style={style}> + <div + ref='mentionlist' + className='mentions-box' + id='mentionsbox'> {mentions} </div> </div> ); } -}); +} + +MentionList.propTypes = { + id: React.PropTypes.string +}; diff --git a/web/react/components/message_wrapper.jsx b/web/react/components/message_wrapper.jsx index 5fc88a61b..bce305853 100644 --- a/web/react/components/message_wrapper.jsx +++ b/web/react/components/message_wrapper.jsx @@ -1,17 +1,30 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - render: function() { +export default class MessageWrapper extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { if (this.props.message) { - var inner = utils.textToJsx(this.props.message, this.props.options); + var inner = Utils.textToJsx(this.props.message, this.props.options); return ( <div>{inner}</div> ); - } else { - return <div/> } + + return <div/>; } -}); +} + +MessageWrapper.defaultProps = { + message: null, + options: null +}; +MessageWrapper.propTypes = { + message: React.PropTypes.string, + options: React.PropTypes.object +}; diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 06c373e5d..d6cf4f9d6 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); @@ -13,22 +13,27 @@ var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -function getStateFromStores() { - return { - channel: ChannelStore.getCurrent(), - member: ChannelStore.getCurrentMember(), - users: ChannelStore.getCurrentExtraInfo().members - }; -} +export default class Navbar extends React.Component { + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + this.handleLeave = this.handleLeave.bind(this); + this.createCollapseButtons = this.createCollapseButtons.bind(this); + this.createDropdown = this.createDropdown.bind(this); -module.exports = React.createClass({ - displayName: 'Navbar', - propTypes: { - teamDisplayName: React.PropTypes.string - }, - componentDidMount: function() { - ChannelStore.addChangeListener(this.onListenerChange); - ChannelStore.addExtraInfoChangeListener(this.onListenerChange); + this.state = this.getStateFromStores(); + } + getStateFromStores() { + return { + channel: ChannelStore.getCurrent(), + member: ChannelStore.getCurrentMember(), + users: ChannelStore.getCurrentExtraInfo().members + }; + } + componentDidMount() { + ChannelStore.addChangeListener(this.onChange); + ChannelStore.addExtraInfoChangeListener(this.onChange); $('.inner__wrap').click(this.hideSidebars); $('body').on('click.infopopover', function handlePopoverClick(e) { @@ -36,15 +41,15 @@ module.exports = React.createClass({ $('.info-popover').popover('hide'); } }); - }, - componentWillUnmount: function() { - ChannelStore.removeChangeListener(this.onListenerChange); - }, - handleSubmit: function(e) { + } + componentWillUnmount() { + ChannelStore.removeChangeListener(this.onChange); + } + handleSubmit(e) { e.preventDefault(); - }, - handleLeave: function() { - client.leaveChannel(this.state.channel.id, + } + handleLeave() { + Client.leaveChannel(this.state.channel.id, function success() { AsyncClient.getChannels(true); window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square'; @@ -53,8 +58,8 @@ module.exports = React.createClass({ AsyncClient.dispatchError(err, 'handleLeave'); } ); - }, - hideSidebars: function(e) { + } + hideSidebars(e) { var windowWidth = $(window).outerWidth(); if (windowWidth <= 768) { AppDispatcher.handleServerAction({ @@ -74,32 +79,259 @@ module.exports = React.createClass({ $('.sidebar--menu').removeClass('move--left'); } } - }, - toggleLeftSidebar: function() { + } + toggleLeftSidebar() { $('.inner__wrap').toggleClass('move--right'); $('.sidebar--left').toggleClass('move--right'); - }, - toggleRightSidebar: function() { + } + toggleRightSidebar() { $('.inner__wrap').toggleClass('move--left-small'); $('.sidebar--menu').toggleClass('move--left'); - }, - onListenerChange: function() { - this.setState(getStateFromStores()); + } + onChange() { + this.setState(this.getStateFromStores()); $('#navbar .navbar-brand .description').popover({placement: 'bottom', trigger: 'click', html: true}); - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { + } + createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent) { + if (channel) { + var viewInfoOption = ( + <li role='presentation'> + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_info' + data-channelid={channel.id} + href='#' + > + View Info + </a> + </li> + ); + + var setChannelDescriptionOption = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#edit_channel' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + > + Set Channel Description... + </a> + </li> + ); + + var addMembersOption; + var leaveChannelOption; + if (!isDirect && !ChannelStore.isDefault(channel)) { + addMembersOption = ( + <li role='presentation'> + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_invite' + href='#' + > + Add Members + </a> + </li> + ); + + leaveChannelOption = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + onClick={this.handleLeave} + > + Leave Channel + </a> + </li> + ); + } + + var manageMembersOption; + var renameChannelOption; + var deleteChannelOption; + if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) { + manageMembersOption = ( + <li role='presentation'> + <a + role='menuitem' + data-toggle='modal' + data-target='#channel_members' + href='#' + > + Manage Members + </a> + </li> + ); + + renameChannelOption = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#rename_channel' + data-display={channel.display_name} + data-name={channel.name} + data-channelid={channel.id} + > + Rename Channel... + </a> + </li> + ); + + deleteChannelOption = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#delete_channel' + data-title={channel.display_name} + data-channelid={channel.id} + > + Delete Channel... + </a> + </li> + ); + } + + var notificationPreferenceOption; + if (!isDirect) { + notificationPreferenceOption = ( + <li role='presentation'> + <a + role='menuitem' + href='#' + data-toggle='modal' + data-target='#channel_notifications' + data-title={channel.display_name} + data-channelid={channel.id} + > + Notification Preferences + </a> + </li> + ); + } + + return ( + <div className='navbar-brand'> + <div className='dropdown'> + <div + data-toggle='popover' + data-content={popoverContent} + className='description info-popover' + /> + <a + href='#' + className='dropdown-toggle theme' + type='button' + id='channel_header_dropdown' + data-toggle='dropdown' + aria-expanded='true' + > + <span className='heading'>{channelTitle} </span> + <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span> + </a> + <ul + className='dropdown-menu' + role='menu' + aria-labelledby='channel_header_dropdown' + > + {viewInfoOption} + {addMembersOption} + {manageMembersOption} + {setChannelDescriptionOption} + {notificationPreferenceOption} + {renameChannelOption} + {deleteChannelOption} + {leaveChannelOption} + </ul> + </div> + </div> + ); + } + + return ( + <div className='navbar-brand'> + <a + href='/' + className='heading' + > + {channelTitle} + </a> + </div> + ); + } + createCollapseButtons(currentId) { + var buttons = []; + if (currentId == null) { + buttons.push( + <button + type='button' + className='navbar-toggle' + data-toggle='collapse' + data-target='#navbar-collapse-1' + > + <span className='sr-only'>Toggle sidebar</span> + <span className='icon-bar'></span> + <span className='icon-bar'></span> + <span className='icon-bar'></span> + </button> + ); + } else { + buttons.push( + <button + type='button' + className='navbar-toggle' + data-toggle='collapse' + data-target='#sidebar-nav' + onClick={this.toggleLeftSidebar} + > + <span className='sr-only'>Toggle sidebar</span> + <span className='icon-bar'></span> + <span className='icon-bar'></span> + <span className='icon-bar'></span> + <NotifyCounts /> + </button> + ); + + buttons.push( + <button + type='button' + className='navbar-toggle menu-toggle pull-right' + data-toggle='collapse' + data-target='#sidebar-nav' + onClick={this.toggleRightSidebar} + > + <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> + </button> + ); + } + + return buttons; + } + render() { var currentId = UserStore.getCurrentId(); - var popoverContent = ''; + var channel = this.state.channel; var channelTitle = this.props.teamDisplayName; + var popoverContent; var isAdmin = false; var isDirect = false; - var channel = this.state.channel; if (channel) { - popoverContent = React.renderToString(<MessageWrapper message={channel.description} options={{singleline: true, noMentionHighlight: true}}/>); + popoverContent = React.renderToString( + <MessageWrapper + message={channel.description} + options={{singleline: true, noMentionHighlight: true}} + /> + ); isAdmin = this.state.member.roles.indexOf('admin') > -1; if (channel.type === 'O') { @@ -118,110 +350,46 @@ module.exports = React.createClass({ } if (channel.description.length === 0) { - popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>); + popoverContent = React.renderToString( + <div> + No channel description yet. <br/> + <a + href='#' + data-toggle='modal' + data-desc={channel.description} + data-title={channel.display_name} + data-channelid={channel.id} + data-target='#edit_channel' + > + Click here + </a> to add one.</div> + ); } } - var navbarCollapseButton = null; - if (currentId == null) { - navbarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#navbar-collapse-1'> - <span className='sr-only'>Toggle sidebar</span> - <span className='icon-bar'></span> - <span className='icon-bar'></span> - <span className='icon-bar'></span> - </button>); - } - - var sidebarCollapseButton = null; - if (currentId != null) { - sidebarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleLeftSidebar}> - <span className='sr-only'>Toggle sidebar</span> - <span className='icon-bar'></span> - <span className='icon-bar'></span> - <span className='icon-bar'></span> - <NotifyCounts /> - </button>); - } - - var rightSidebarCollapseButton = null; - if (currentId != null) { - rightSidebarCollapseButton = (<button type='button' className='navbar-toggle menu-toggle pull-right' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleRightSidebar}> - <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> - </button>); - } - - var channelMenuDropdown = null; - if (channel) { - var viewInfoOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li> - - var addMembersOption = null; - if (!isDirect && !ChannelStore.isDefault(channel)) { - addMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>; - } - - var manageMembersOption = null; - if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) { - manageMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>; - } - - var setChannelDescriptionOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>; - - var notificationPreferenceOption = null; - if (!isDirect) { - notificationPreferenceOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>; - } - - var renameChannelOption = null; - if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) { - renameChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>; - } - - var deleteChannelOption = null; - if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) { - deleteChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>; - } + var collapseButtons = this.createCollapseButtons(currentId); - var leaveChannelOption = null; - if (!isDirect && !ChannelStore.isDefault(channel)) { - leaveChannelOption = <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</a></li>; - } - - channelMenuDropdown = (<div className='navbar-brand'> - <div className='dropdown'> - <div data-toggle='popover' data-content={popoverContent} className='description info-popover'></div> - <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'> - <span className='heading'>{channelTitle} </span> - <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span> - </a> - <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'> - {viewInfoOption} - {addMembersOption} - {manageMembersOption} - {setChannelDescriptionOption} - {notificationPreferenceOption} - {renameChannelOption} - {deleteChannelOption} - {leaveChannelOption} - </ul> - </div> - </div>); - } else { - channelMenuDropdown = (<div className='navbar-brand'> - <a href='/' className='heading'>{channelTitle}</a> - </div>); - } + var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent); return ( - <nav className='navbar navbar-default navbar-fixed-top' role='navigation'> + <nav + className='navbar navbar-default navbar-fixed-top' + role='navigation' + > <div className='container-fluid theme'> <div className='navbar-header'> - {navbarCollapseButton} - {sidebarCollapseButton} - {rightSidebarCollapseButton} + {collapseButtons} {channelMenuDropdown} </div> </div> </nav> ); } -}); +} + +Navbar.defaultProps = { + teamDisplayName: '' +}; +Navbar.propTypes = { + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx new file mode 100644 index 000000000..e818a5c92 --- /dev/null +++ b/web/react/components/navbar_dropdown.jsx @@ -0,0 +1,209 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Utils = require('../utils/utils.jsx'); +var client = require('../utils/client.jsx'); +var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); + +var Constants = require('../utils/constants.jsx'); + +function getStateFromStores() { + return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; +} + +export default class NavbarDropdown extends React.Component { + constructor(props) { + super(props); + this.blockToggle = false; + + this.handleLogoutClick = this.handleLogoutClick.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = getStateFromStores(); + } + handleLogoutClick(e) { + e.preventDefault(); + client.logout(); + } + componentDidMount() { + UserStore.addTeamsChangeListener(this.onListenerChange); + TeamStore.addChangeListener(this.onListenerChange); + + $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', function resetDropdown() { + this.blockToggle = true; + setTimeout(function blockTimeout() { + this.blockToggle = false; + }.bind(this), 100); + }.bind(this)); + } + componentWillUnmount() { + UserStore.removeTeamsChangeListener(this.onListenerChange); + TeamStore.removeChangeListener(this.onListenerChange); + + $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + } + onListenerChange() { + var newState = getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); + } + } + render() { + var teamLink = ''; + var inviteLink = ''; + var manageLink = ''; + var currentUser = UserStore.getCurrentUser(); + var isAdmin = false; + var teamSettings = null; + + if (currentUser != null) { + isAdmin = currentUser.roles.indexOf('admin') > -1; + + inviteLink = (<li> + <a + href='#' + data-toggle='modal' + data-target='#invite_member' + > + Invite New Member + </a> + </li>); + + if (this.props.teamType === 'O') { + teamLink = ( + <li> + <a + href='#' + data-toggle='modal' + data-target='#get_link' + data-title='Team Invite' + data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id} + > + Get Team Invite Link + </a> + </li> + ); + } + } + + if (isAdmin) { + manageLink = (<li> + <a + href='#' + data-toggle='modal' + data-target='#team_members' + > + Manage Team + </a> + </li>); + teamSettings = (<li> + <a + href='#' + data-toggle='modal' + data-target='#team_settings' + > + Team Settings + </a> + </li>); + } + + var teams = []; + + teams.push(<li + className='divider' + key='div' + > + </li>); + if (this.state.teams.length > 1 && this.state.currentTeam) { + var curTeamName = this.state.currentTeam.name; + this.state.teams.forEach(function listTeams(teamName) { + if (teamName !== curTeamName) { + teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>); + } + }); + } + teams.push(<li key='newTeam_li'> + <a + key='newTeam_a' + target='_blank' + href={Utils.getWindowLocationOrigin() + '/signup_team'} + > + Create a New Team + </a> + </li>); + + return ( + <ul className='nav navbar-nav navbar-right'> + <li + ref='dropdown' + className='dropdown' + > + <a + href='#' + className='dropdown-toggle' + data-toggle='dropdown' + role='button' + aria-expanded='false' + > + <span + className='dropdown__icon' + dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} + /> + </a> + <ul + className='dropdown-menu' + role='menu' + > + <li> + <a + href='#' + data-toggle='modal' + data-target='#user_settings' + > + Account Settings + </a> + </li> + {teamSettings} + {inviteLink} + {teamLink} + {manageLink} + <li> + <a + href='#' + onClick={this.handleLogoutClick} + > + Logout + </a> + </li> + {teams} + <li className='divider'></li> + <li> + <a + target='_blank' + href={config.HelpLink} + > + Help + </a> + </li> + <li> + <a + target='_blank' + href={config.ReportProblemLink} + > + Report a Problem + </a> + </li> + </ul> + </li> + </ul> + ); + } +} + +NavbarDropdown.defaultProps = { + teamType: '' +}; +NavbarDropdown.propTypes = { + teamType: React.PropTypes.string +}; diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index fc24a7cdc..a02a4c1c0 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -5,17 +5,24 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var asyncClient = require('../utils/async_client.jsx'); var UserStore = require('../stores/user_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); -module.exports = React.createClass({ - displayName: 'NewChannelModal', - handleSubmit: function(e) { +export default class NewChannelModal extends React.Component { + constructor() { + super(); + + this.handleSubmit = this.handleSubmit.bind(this); + this.displayNameKeyUp = this.displayNameKeyUp.bind(this); + this.handleClose = this.handleClose.bind(this); + + this.state = {channelType: ''}; + } + handleSubmit(e) { e.preventDefault(); var channel = {}; var state = {serverError: ''}; - channel.display_name = this.refs.display_name.getDOMNode().value.trim(); + channel.display_name = React.findDOMNode(this.refs.display_name).value.trim(); if (!channel.display_name) { state.displayNameError = 'This field is required'; state.inValid = true; @@ -26,7 +33,7 @@ module.exports = React.createClass({ state.displayNameError = ''; } - channel.name = this.refs.channel_name.getDOMNode().value.trim(); + channel.name = React.findDOMNode(this.refs.channel_name).value.trim(); if (!channel.name) { state.nameError = 'This field is required'; state.inValid = true; @@ -52,54 +59,51 @@ module.exports = React.createClass({ var cu = UserStore.getCurrentUser(); channel.team_id = cu.team_id; - channel.description = this.refs.channel_desc.getDOMNode().value.trim(); + channel.description = React.findDOMNode(this.refs.channel_desc).value.trim(); channel.type = this.state.channelType; client.createChannel(channel, - function(data) { - $(this.refs.modal.getDOMNode()).modal('hide'); + function success(data) { + $(React.findDOMNode(this.refs.modal)).modal('hide'); asyncClient.getChannel(data.id); utils.switchChannel(data); - this.refs.display_name.getDOMNode().value = ''; - this.refs.channel_name.getDOMNode().value = ''; - this.refs.channel_desc.getDOMNode().value = ''; + React.findDOMNode(this.refs.display_name).value = ''; + React.findDOMNode(this.refs.channel_name).value = ''; + React.findDOMNode(this.refs.channel_desc).value = ''; }.bind(this), - function(err) { + function error(err) { state.serverError = err.message; state.inValid = true; this.setState(state); }.bind(this) ); - }, - displayNameKeyUp: function() { - var displayName = this.refs.display_name.getDOMNode().value.trim(); + } + displayNameKeyUp() { + var displayName = React.findDOMNode(this.refs.display_name).value.trim(); var channelName = utils.cleanUpUrlable(displayName); - this.refs.channel_name.getDOMNode().value = channelName; - }, - componentDidMount: function() { + React.findDOMNode(this.refs.channel_name).value = channelName; + } + componentDidMount() { var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) { var button = e.relatedTarget; self.setState({channelType: $(button).attr('data-channeltype')}); }); - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose); - }, - componentWillUnmount: function() { - $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose); - }, - handleClose: function() { - $(this.getDOMNode()).find('.form-control').each(function clearForms() { + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); + } + componentWillUnmount() { + $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + } + handleClose() { + $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { this.value = ''; }); this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false}); - }, - getInitialState: function() { - return {channelType: ''}; - }, - render: function() { + } + render() { var displayNameError = null; var nameError = null; var serverError = null; @@ -124,11 +128,20 @@ module.exports = React.createClass({ } return ( - <div className='modal fade' id='new_channel' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'> + <div + className='modal fade' + id='new_channel' + ref='modal' + tabIndex='-1' + role='dialog' + aria-hidden='true'> <div className='modal-dialog'> <div className='modal-content'> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal'> + <button + type='button' + className='close' + data-dismiss='modal'> <span aria-hidden='true'>×</span> <span className='sr-only'>Cancel</span> </button> @@ -138,23 +151,49 @@ module.exports = React.createClass({ <div className='modal-body'> <div className={displayNameClass}> <label className='control-label'>Display Name</label> - <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' maxLength='22' /> + <input + onKeyUp={this.displayNameKeyUp} + type='text' + ref='display_name' + className='form-control' + placeholder='Enter display name' + maxLength='22' /> {displayNameError} </div> <div className={nameClass}> <label className='control-label'>Handle</label> - <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='22' /> + <input + type='text' + className='form-control' + ref='channel_name' + placeholder="lowercase alphanumeric's only" + maxLength='22' /> {nameError} </div> <div className='form-group'> <label className='control-label'>Description</label> - <textarea className='form-control no-resize' ref='channel_desc' rows='3' placeholder='Description' maxLength='1024'></textarea> + <textarea + className='form-control no-resize' + ref='channel_desc' + rows='3' + placeholder='Description' + maxLength='1024' /> </div> {serverError} </div> <div className='modal-footer'> - <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button> - <button onClick={this.handleSubmit} type='submit' className='btn btn-primary'>Create New {channelTerm}</button> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' > + Cancel + </button> + <button + onClick={this.handleSubmit} + type='submit' + className='btn btn-primary' > + Create New {channelTerm} + </button> </div> </form> </div> @@ -162,4 +201,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx index ebc49882b..0b7c41b62 100644 --- a/web/react/components/notify_counts.jsx +++ b/web/react/components/notify_counts.jsx @@ -23,27 +23,30 @@ function getCountsStateFromStores() { return {count: count}; } -module.exports = React.createClass({ - displayName: 'NotifyCounts', - componentDidMount: function() { +export default class NotifyCounts extends React.Component { + constructor(props) { + super(props); + + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = getCountsStateFromStores(); + } + componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); - }, - onListenerChange: function() { + } + onListenerChange() { var newState = getCountsStateFromStores(); if (!utils.areStatesEqual(newState, this.state)) { this.setState(newState); } - }, - getInitialState: function() { - return getCountsStateFromStores(); - }, - render: function() { + } + render() { if (this.state.count) { return <span className='badge badge-notify'>{this.state.count}</span>; } return null; } -}); +} diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx index b2edea620..399d3b7b9 100644 --- a/web/react/components/password_reset.jsx +++ b/web/react/components/password_reset.jsx @@ -1,143 +1,47 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var UserStore = require('../stores/user_store.jsx'); +var PasswordResetSendLink = require('./password_reset_send_link.jsx'); +var PasswordResetForm = require('./password_reset_form.jsx'); -SendResetPasswordLink = React.createClass({ - handleSendLink: function(e) { - e.preventDefault(); - var state = {}; +export default class PasswordReset extends React.Component { + constructor(props) { + super(props); - var email = this.refs.email.getDOMNode().value.trim(); - if (!email) { - state.error = "Please enter a valid email address." - this.setState(state); - return; - } - - state.error = null; - this.setState(state); - - data = {}; - data['email'] = email; - data['name'] = this.props.teamName; - - client.sendPasswordReset(data, - function(data) { - this.setState({ error: null, update_text: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, more_update_text: "Please check your inbox." }); - $(this.refs.reset_form.getDOMNode()).hide(); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null, more_update_text: null }); - }.bind(this) - ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - var update_text = this.state.update_text ? <div className="reset-form alert alert-success">{this.state.update_text}{this.state.more_update_text}</div> : null; - var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null; - - return ( - <div className="col-sm-12"> - <div className="signup-team__container"> - <h3>Password Reset</h3> - { update_text } - <form onSubmit={this.handleSendLink} ref="reset_form"> - <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}</p> - <div className={error ? 'form-group has-error' : 'form-group'}> - <input type="text" className="form-control" name="email" ref="email" placeholder="Email" /> - </div> - { error } - <button type="submit" className="btn btn-primary">Reset my password</button> - </form> - </div> - </div> - ); + this.state = {}; } -}); - -ResetPassword = React.createClass({ - handlePasswordReset: function(e) { - e.preventDefault(); - var state = {}; - - var password = this.refs.password.getDOMNode().value.trim(); - if (!password || password.length < 5) { - state.error = "Please enter at least 5 characters." - this.setState(state); - return; - } - - state.error = null; - this.setState(state); - - data = {}; - data['new_password'] = password; - data['hash'] = this.props.hash; - data['data'] = this.props.data; - data['name'] = this.props.teamName; - - client.resetPassword(data, - function(data) { - this.setState({ error: null, update_text: "Your password has been updated successfully." }); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null }); - }.bind(this) - ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href={"/" + this.props.teamName + "/login"}>here</a> to log in.</label></div> : null; - var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null; - - return ( - <div className="col-sm-12"> - <div className="signup-team__container"> - <h3>Password Reset</h3> - <form onSubmit={this.handlePasswordReset}> - <p>{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}</p> - <div className={error ? 'form-group has-error' : 'form-group'}> - <input type="password" className="form-control" name="password" ref="password" placeholder="Password" /> - </div> - { error } - <button type="submit" className="btn btn-primary">Change my password</button> - { update_text } - </form> - </div> - </div> - ); - } -}); - -module.exports = React.createClass({ - getInitialState: function() { - return {}; - }, - render: function() { - - if (this.props.isReset === "false") { - return ( - <SendResetPasswordLink - teamDisplayName={this.props.teamDisplayName} - teamName={this.props.teamName} - /> - ); - } else { + render() { + if (this.props.isReset === 'false') { return ( - <ResetPassword + <PasswordResetSendLink teamDisplayName={this.props.teamDisplayName} teamName={this.props.teamName} - hash={this.props.hash} - data={this.props.data} /> ); } + + return ( + <PasswordResetForm + teamDisplayName={this.props.teamDisplayName} + teamName={this.props.teamName} + hash={this.props.hash} + data={this.props.data} + /> + ); } -}); +} + +PasswordReset.defaultProps = { + isReset: '', + teamName: '', + teamDisplayName: '', + hash: '', + data: '' +}; +PasswordReset.propTypes = { + isReset: React.PropTypes.string, + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + hash: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx new file mode 100644 index 000000000..7acd2d1f7 --- /dev/null +++ b/web/react/components/password_reset_form.jsx @@ -0,0 +1,100 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var client = require('../utils/client.jsx'); + +export default class PasswordResetForm extends React.Component { + constructor(props) { + super(props); + + this.handlePasswordReset = this.handlePasswordReset.bind(this); + + this.state = {}; + } + handlePasswordReset(e) { + e.preventDefault(); + var state = {}; + + var password = React.findDOMNode(this.refs.password).value.trim(); + if (!password || password.length < 5) { + state.error = 'Please enter at least 5 characters.'; + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var data = {}; + data.new_password = password; + data.hash = this.props.hash; + data.data = this.props.data; + data.name = this.props.teamName; + + client.resetPassword(data, + function resetSuccess() { + this.setState({error: null, updateText: 'Your password has been updated successfully.'}); + }.bind(this), + function resetFailure(err) { + this.setState({error: err.message, updateText: null}); + }.bind(this) + ); + } + render() { + var updateText = null; + if (this.state.updateText) { + updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>; + } + + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div className='col-sm-12'> + <div className='signup-team__container'> + <h3>Password Reset</h3> + <form onSubmit={this.handlePasswordReset}> + <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + config.SiteName + ' account.'}</p> + <div className={formClass}> + <input + type='password' + className='form-control' + name='password' + ref='password' + placeholder='Password' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + Change my password + </button> + {updateText} + </form> + </div> + </div> + ); + } +} + +PasswordResetForm.defaultProps = { + teamName: '', + teamDisplayName: '', + hash: '', + data: '' +}; +PasswordResetForm.propTypes = { + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + hash: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx new file mode 100644 index 000000000..1e6cc3607 --- /dev/null +++ b/web/react/components/password_reset_send_link.jsx @@ -0,0 +1,98 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var client = require('../utils/client.jsx'); + +export default class PasswordResetSendLink extends React.Component { + constructor(props) { + super(props); + + this.handleSendLink = this.handleSendLink.bind(this); + + this.state = {}; + } + handleSendLink(e) { + e.preventDefault(); + var state = {}; + + var email = React.findDOMNode(this.refs.email).value.trim(); + if (!email) { + state.error = 'Please enter a valid email address.'; + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var data = {}; + data.email = email; + data.name = this.props.teamName; + + client.sendPasswordReset(data, + function passwordResetSent() { + this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'}); + $(React.findDOMNode(this.refs.reset_form)).hide(); + }.bind(this), + function passwordResetFailedToSend(err) { + this.setState({error: err.message, update_text: null, moreUpdateText: null}); + }.bind(this) + ); + } + render() { + var updateText = null; + if (this.state.updateText) { + updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>; + } + + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div className='col-sm-12'> + <div className='signup-team__container'> + <h3>Password Reset</h3> + {updateText} + <form + onSubmit={this.handleSendLink} + ref='reset_form' + > + <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p> + <div className={formClass}> + <input + type='text' + className='form-control' + name='email' + ref='email' + placeholder='Email' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + Reset my password + </button> + </form> + </div> + </div> + ); + } +} + +PasswordResetSendLink.defaultProps = { + teamName: '', + teamDisplayName: '' +}; +PasswordResetSendLink.propTypes = { + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx new file mode 100644 index 000000000..fb9522afb --- /dev/null +++ b/web/react/components/popover_list_members.jsx @@ -0,0 +1,80 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class PopoverListMembers extends React.Component { + componentDidMount() { + const originalLeave = $.fn.popover.Constructor.prototype.leave; + $.fn.popover.Constructor.prototype.leave = function onLeave(obj) { + let selfObj; + if (obj instanceof this.constructor) { + selfObj = obj; + } else { + selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data(`bs.${this.type}`); + } + originalLeave.call(this, obj); + + if (obj.currentTarget && selfObj.$tip) { + selfObj.$tip.one('mouseenter', function onMouseEnter() { + clearTimeout(selfObj.timeout); + selfObj.$tip.one('mouseleave', function onMouseLeave() { + $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj); + }); + }); + } + }; + + $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); + $('body').on('click', function onClick(e) { + if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { + $('#member_popover').popover('hide'); + } + }); + } + render() { + let popoverHtml = ''; + const members = this.props.members; + let count; + if (members.length > 20) { + count = '20+'; + } else { + count = members.length || '-'; + } + + if (members) { + members.sort(function compareByLocal(a, b) { + return a.username.localeCompare(b.username); + }); + + members.forEach(function addMemberElement(m) { + popoverHtml += `<div class='text--nowrap'>${m.username}</div>`; + }); + } + + return ( + <div + id='member_popover' + data-toggle='popover' + data-content={popoverHtml} + data-original-title='Members' + > + <div + id='member_tooltip' + data-placement='left' + data-toggle='tooltip' + title='View Channel Members' + > + {count} + <span + className='glyphicon glyphicon-user' + aria-hidden='true' + /> + </div> + </div> + ); + } +} + +PopoverListMembers.propTypes = { + members: React.PropTypes.array.isRequired, + channelId: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index acc2b51d2..e284f4b6a 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -15,9 +15,17 @@ var utils = require('../utils/utils.jsx'); var PostInfo = require('./post_info.jsx'); -module.exports = React.createClass({ - displayName: 'Post', - handleCommentClick: function(e) { +export default class Post extends React.Component { + constructor(props) { + super(props); + + this.handleCommentClick = this.handleCommentClick.bind(this); + this.forceUpdateInfo = this.forceUpdateInfo.bind(this); + this.retryPost = this.retryPost.bind(this); + + this.state = {}; + } + handleCommentClick(e) { e.preventDefault(); var data = {}; @@ -33,31 +41,31 @@ module.exports = React.createClass({ type: ActionTypes.RECIEVED_SEARCH, results: null }); - }, - forceUpdateInfo: function() { + } + forceUpdateInfo() { this.refs.info.forceUpdate(); this.refs.header.forceUpdate(); - }, - retryPost: function(e) { + } + retryPost(e) { e.preventDefault(); var post = this.props.post; client.createPost(post, post.channel_id, - function(data) { + function success(data) { AsyncClient.getPosts(); var channel = ChannelStore.get(post.channel_id); var member = ChannelStore.getMember(post.channel_id); member.msg_count = channel.total_msg_count; - member.last_viewed_at = (new Date).getTime(); + member.last_viewed_at = utils.getTimestamp(); ChannelStore.setChannelMember(member); AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POST, post: data }); - }.bind(this), - function(err) { + }, + function error() { post.state = Constants.POST_FAILED; PostStore.updatePendingPost(post); this.forceUpdate(); @@ -67,18 +75,15 @@ module.exports = React.createClass({ post.state = Constants.POST_LOADING; PostStore.updatePendingPost(post); this.forceUpdate(); - }, - shouldComponentUpdate: function(nextProps) { + } + shouldComponentUpdate(nextProps) { if (!utils.areStatesEqual(nextProps.post, this.props.post)) { return true; } return false; - }, - getInitialState: function() { - return { }; - }, - render: function() { + } + render() { var post = this.props.post; var parentPost = this.props.parentPost; var posts = this.props.posts; @@ -89,19 +94,27 @@ module.exports = React.createClass({ } var commentCount = 0; - var commentRootId = parentPost ? post.root_id : post.id; + var commentRootId; + if (parentPost) { + commentRootId = post.root_id; + } else { + commentRootId = post.id; + } for (var postId in posts) { - if (posts[postId].root_id == commentRootId) { + if (posts[postId].root_id === commentRootId) { commentCount += 1; } } - var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null; - - var rootUser = this.props.sameRoot ? 'same--root' : 'other--root'; + var rootUser; + if (this.props.sameRoot) { + rootUser = 'same--root'; + } else { + rootUser = 'other--root'; + } var postType = ''; - if (type != 'Post'){ + if (type !== 'Post') { postType = 'post--comment'; } @@ -122,21 +135,60 @@ module.exports = React.createClass({ sameUserClass = 'same--user'; } + var profilePic = null; + if (this.props.hideProfilePic) { + profilePic = ( + <div className='post-profile-img__container'> + <img + className='post-profile-img' + src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} + height='36' + width='36' /> + </div> + ); + } + return ( <div> - <div id={post.id} className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}> - { !this.props.hideProfilePic ? - <div className='post-profile-img__container'> - <img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' /> - </div> - : null } + <div + id={post.id} + className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss} > + {profilePic} <div className='post__content'> - <PostHeader ref='header' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} /> - <PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} /> - <PostInfo ref='info' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply='true' /> + <PostHeader + ref='header' + post={post} + sameRoot={this.props.sameRoot} + commentCount={commentCount} + handleCommentClick={this.handleCommentClick} + isLastComment={this.props.isLastComment} /> + <PostBody + post={post} + sameRoot={this.props.sameRoot} + parentPost={parentPost} + posts={posts} + handleCommentClick={this.handleCommentClick} + retryPost={this.retryPost} /> + <PostInfo + ref='info' + post={post} + sameRoot={this.props.sameRoot} + commentCount={commentCount} + handleCommentClick={this.handleCommentClick} + allowReply='true' /> </div> </div> </div> ); } -}); +} + +Post.propTypes = { + post: React.PropTypes.object, + posts: React.PropTypes.object, + parentPost: React.PropTypes.object, + sameUser: React.PropTypes.bool, + sameRoot: React.PropTypes.bool, + hideProfilePic: React.PropTypes.bool, + isLastComment: React.PropTypes.bool +}; diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index e5ab5b624..88fb9aec8 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -1,95 +1,140 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var FileAttachmentList = require('./file_attachment_list.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var utils = require('../utils/utils.jsx'); -var Constants = require('../utils/constants.jsx'); +const FileAttachmentList = require('./file_attachment_list.jsx'); +const UserStore = require('../stores/user_store.jsx'); +const Utils = require('../utils/utils.jsx'); +const Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - componentWillReceiveProps: function(nextProps) { - var linkData = utils.extractLinks(nextProps.post.message); +export default class PostBody extends React.Component { + constructor(props) { + super(props); + + const linkData = Utils.extractLinks(this.props.post.message); + this.state = {links: linkData.links, message: linkData.text}; + } + componentWillReceiveProps(nextProps) { + const linkData = Utils.extractLinks(nextProps.post.message); this.setState({links: linkData.links, message: linkData.text}); - }, - getInitialState: function() { - var linkData = utils.extractLinks(this.props.post.message); - return {links: linkData.links, message: linkData.text}; - }, - render: function() { - var post = this.props.post; - var filenames = this.props.post.filenames; - var parentPost = this.props.parentPost; - var inner = utils.textToJsx(this.state.message); + } + render() { + const post = this.props.post; + const filenames = this.props.post.filenames; + const parentPost = this.props.parentPost; + const inner = Utils.textToJsx(this.state.message); - var comment = ''; - var reply = ''; - var postClass = ''; + let comment = ''; + let postClass = ''; if (parentPost) { - var profile = UserStore.getProfile(parentPost.user_id); - var apostrophe = ''; - var name = '...'; + const profile = UserStore.getProfile(parentPost.user_id); + + let apostrophe = ''; + let name = '...'; if (profile != null) { if (profile.username.slice(-1) === 's') { apostrophe = '\''; } else { apostrophe = '\'s'; } - name = <a className='theme' onClick={function searchName() { utils.searchForTerm(profile.username); }}>{profile.username}</a>; + name = ( + <a + className='theme' + onClick={Utils.searchForTerm.bind(null, profile.username)} + > + {profile.username} + </a> + ); } - var message = ''; + let message = ''; if (parentPost.message) { - message = utils.replaceHtmlEntities(parentPost.message); + message = Utils.replaceHtmlEntities(parentPost.message); } else if (parentPost.filenames.length) { message = parentPost.filenames[0].split('/').pop(); if (parentPost.filenames.length === 2) { message += ' plus 1 other file'; } else if (parentPost.filenames.length > 2) { - message += ' plus ' + (parentPost.filenames.length - 1) + ' other files'; + message += ` plus ${parentPost.filenames.length - 1} other files`; } } comment = ( <p className='post-link'> - <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span> + <span> + Commented on {name}{apostrophe} message: + <a + className='theme' + onClick={this.props.handleCommentClick} + > + {message} + </a> + </span> </p> ); postClass += ' post-comment'; } - var loading; + let loading; if (post.state === Constants.POST_FAILED) { postClass += ' post-fail'; - loading = <a className='theme post-retry pull-right' href='#' onClick={this.props.retryPost}>Retry</a>; + loading = ( + <a + className='theme post-retry pull-right' + href='#' + onClick={this.props.retryPost} + > + Retry + </a> + ); } else if (post.state === Constants.POST_LOADING) { postClass += ' post-waiting'; - loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>; + loading = ( + <img + className='post-loading-gif pull-right' + src='/static/images/load.gif' + /> + ); } - var embed; + let embed; if (filenames.length === 0 && this.state.links) { - embed = utils.getEmbed(this.state.links[0]); + embed = Utils.getEmbed(this.state.links[0]); } - var fileAttachmentHolder = ''; + let fileAttachmentHolder = ''; if (filenames && filenames.length > 0) { - fileAttachmentHolder = (<FileAttachmentList - filenames={filenames} - modalId={'view_image_modal_' + post.id} - channelId={post.channel_id} - userId={post.user_id} />); + fileAttachmentHolder = ( + <FileAttachmentList + filenames={filenames} + modalId={`view_image_modal_${post.id}`} + channelId={post.channel_id} + userId={post.user_id} + /> + ); } return ( <div className='post-body'> {comment} - <p key={post.id + '_message'} className={postClass}>{loading}<span>{inner}</span></p> + <p + key={`${post.id}_message`} + className={postClass} + > + {loading}<span>{inner}</span> + </p> {fileAttachmentHolder} {embed} </div> ); } -}); +} + +PostBody.propTypes = { + post: React.PropTypes.object.isRequired, + parentPost: React.PropTypes.object, + retryPost: React.PropTypes.func.isRequired, + handleCommentClick: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index 83b007bad..d284a9d1b 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -3,34 +3,61 @@ var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - getInitialState: function() { - return { }; - }, - render: function() { - var currentUser = UserStore.getCurrentUser() +export default class PostDeletedModal extends React.Component { + constructor(props) { + super(props); + + this.state = {}; + } + render() { + var currentUser = UserStore.getCurrentUser(); if (currentUser != null) { return ( - <div className="modal fade" ref="modal" id="post_deleted" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" id="myModalLabel">Comment could not be posted</h4> - </div> - <div className="modal-body"> - <p>Someone deleted the message on which you tried to post a comment.</p> + <div + className='modal fade' + ref='modal' + id='post_deleted' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + id='myModalLabel' + > + Comment could not be posted + </h4> + </div> + <div className='modal-body'> + <p>Someone deleted the message on which you tried to post a comment.</p> + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-primary' + data-dismiss='modal' + > + Okay + </button> + </div> </div> - <div className="modal-footer"> - <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button> - </div> - </div> - </div> + </div> </div> ); - } else { - return <div/>; } + + return <div/>; } -}); +} diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx index 129db6d14..9dc525e03 100644 --- a/web/react/components/post_header.jsx +++ b/web/react/components/post_header.jsx @@ -1,23 +1,42 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserProfile = require( './user_profile.jsx' ); +var UserProfile = require('./user_profile.jsx'); var PostInfo = require('./post_info.jsx'); -module.exports = React.createClass({ - getInitialState: function() { - return { }; - }, - render: function() { +export default class PostHeader extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { var post = this.props.post; return ( - <ul className="post-header post-header-post"> - <li className="post-header-col post-header__name"><strong><UserProfile userId={post.user_id} /></strong></li> - <li className="post-info--hidden"> - <PostInfo post={post} commentCount={this.props.commentCount} handleCommentClick={this.props.handleCommentClick} allowReply="true" isLastComment={this.props.isLastComment} /> + <ul className='post-header post-header-post'> + <li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li> + <li className='post-info--hidden'> + <PostInfo + post={post} + commentCount={this.props.commentCount} + handleCommentClick={this.props.handleCommentClick} + allowReply='true' + isLastComment={this.props.isLastComment} + /> </li> </ul> ); } -}); +} + +PostHeader.defaultProps = { + post: null, + commentCount: 0, + isLastComment: false +}; +PostHeader.propTypes = { + post: React.PropTypes.object, + commentCount: React.PropTypes.number, + isLastComment: React.PropTypes.bool, + handleCommentClick: React.PropTypes.func +}; diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 2fe6dd96b..37958b649 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -1,147 +1,217 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); -var utils = require('../utils/utils.jsx'); -var Client = require('../utils/client.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); -var Constants = require('../utils/constants.jsx'); +export default class RenameChannelModal extends React.Component { + constructor(props) { + super(props); -module.exports = React.createClass({ - handleSubmit: function(e) { + this.handleSubmit = this.handleSubmit.bind(this); + this.onNameChange = this.onNameChange.bind(this); + this.onDisplayNameChange = this.onDisplayNameChange.bind(this); + this.displayNameKeyUp = this.displayNameKeyUp.bind(this); + this.handleClose = this.handleClose.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + displayName: '', + channelName: '', + channelId: '', + serverError: '', + nameError: '', + displayNameError: '', + invalid: false + }; + } + handleSubmit(e) { e.preventDefault(); - if (this.state.channel_id.length !== 26) return; + if (this.state.channelId.length !== 26) { + return; + } - var channel = ChannelStore.get(this.state.channel_id); - var oldName = channel.name - var oldDisplayName = channel.display_name - var state = { server_error: "" }; + let channel = ChannelStore.get(this.state.channelId); + const oldName = channel.name; + const oldDisplayName = channel.displayName; + let state = {serverError: ''}; - channel.display_name = this.state.display_name.trim(); + channel.display_name = this.state.displayName.trim(); if (!channel.display_name) { - state.display_name_error = "This field is required"; - state.inValid = true; - } - else if (channel.display_name.length > 22) { - state.display_name_error = "This field must be less than 22 characters"; - state.inValid = true; - } - else { - state.display_name_error = ""; + state.displayNameError = 'This field is required'; + state.invalid = true; + } else if (channel.display_name.length > 22) { + state.displayNameError = 'This field must be less than 22 characters'; + state.invalid = true; + } else { + state.displayNameError = ''; } - channel.name = this.state.channel_name.trim(); + channel.name = this.state.channelName.trim(); if (!channel.name) { - state.name_error = "This field is required"; - state.inValid = true; - } - else if(channel.name.length > 22){ - state.name_error = "This field must be less than 22 characters"; - state.inValid = true; - } - else { - var cleaned_name = utils.cleanUpUrlable(channel.name); - if (cleaned_name != channel.name) { - state.name_error = "Must be lowercase alphanumeric characters"; - state.inValid = true; - } - else { - state.name_error = ""; + state.nameError = 'This field is required'; + state.invalid = true; + } else if (channel.name.length > 22) { + state.nameError = 'This field must be less than 22 characters'; + state.invalid = true; + } else { + let cleanedName = Utils.cleanUpUrlable(channel.name); + if (cleanedName !== channel.name) { + state.nameError = 'Must be lowercase alphanumeric characters'; + state.invalid = true; + } else { + state.nameError = ''; } } this.setState(state); - if (state.inValid) - return; - - if (oldName == channel.name && oldDisplayName == channel.display_name) + if (state.invalid || (oldName === channel.name && oldDisplayName === channel.display_name)) { return; + } Client.updateChannel(channel, - function(data, text, req) { - $(this.refs.modal.getDOMNode()).modal('hide'); + function handleUpdateSuccess() { + $(React.findDOMNode(this.refs.modal)).modal('hide'); AsyncClient.getChannel(channel.id); - utils.updateTabTitle(channel.display_name); - utils.updateAddressBar(channel.name); + Utils.updateTabTitle(channel.display_name); + Utils.updateAddressBar(channel.name); - this.refs.display_name.getDOMNode().value = ""; - this.refs.channel_name.getDOMNode().value = ""; + React.findDOMNode(this.refs.displayName).value = ''; + React.findDOMNode(this.refs.channelName).value = ''; }.bind(this), - function(err) { - state.server_error = err.message; - state.inValid = true; + function handleUpdateError(err) { + state.serverError = err.message; + state.invalid = true; this.setState(state); }.bind(this) ); - }, - onNameChange: function() { - this.setState({ channel_name: this.refs.channel_name.getDOMNode().value }) - }, - onDisplayNameChange: function() { - this.setState({ display_name: this.refs.display_name.getDOMNode().value }) - }, - displayNameKeyUp: function(e) { - var display_name = this.refs.display_name.getDOMNode().value.trim(); - var channel_name = utils.cleanUpUrlable(display_name); - this.refs.channel_name.getDOMNode().value = channel_name; - this.setState({ channel_name: channel_name }) - }, - handleClose: function() { - this.setState({display_name: "", channel_name: "", display_name_error: "", server_error: "", name_error: ""}); - }, - componentDidMount: function() { - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { - var button = $(e.relatedTarget); - self.setState({ display_name: button.attr('data-display'), channel_name: button.attr('data-name'), channel_id: button.attr('data-channelid') }); - }); - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose); - }, - componentWillUnmount: function() { - $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose); - }, - getInitialState: function() { - return { display_name: "", channel_name: "", channel_id: "" }; - }, - render: function() { - - var display_name_error = this.state.display_name_error ? <label className='control-label'>{ this.state.display_name_error }</label> : null; - var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null; - var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null; + } + onNameChange() { + this.setState({channelName: React.findDOMNode(this.refs.channelName).value}); + } + onDisplayNameChange() { + this.setState({displayName: React.findDOMNode(this.refs.displayName).value}); + } + displayNameKeyUp() { + const displayName = React.findDOMNode(this.refs.displayName).value.trim(); + const channelName = Utils.cleanUpUrlable(displayName); + React.findDOMNode(this.refs.channelName).value = channelName; + this.setState({channelName: channelName}); + } + handleClose() { + this.state = { + displayName: '', + channelName: '', + channelId: '', + serverError: '', + nameError: '', + displayNameError: '', + invalid: false + }; + } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) { + const button = $(e.relatedTarget); + this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')}); + }.bind(this)); + $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); + } + componentWillUnmount() { + $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + } + render() { + let displayNameError = null; + let displayNameClass = 'form-group'; + if (this.state.displayNameError) { + displayNameError = <label className='control-label'>{this.state.displayNameError}</label>; + displayNameClass += ' has-error'; + } + + let nameError = null; + let nameClass = 'form-group'; + if (this.state.nameError) { + nameError = <label className='control-label'>{this.state.nameError}</label>; + nameClass += ' has-error'; + } + + let serverError = null; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } return ( - <div className="modal fade" ref="modal" id="rename_channel" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal"> - <span aria-hidden="true">×</span> - <span className="sr-only">Close</span> + <div + className='modal fade' + ref='modal' + id='rename_channel' + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + > + <span aria-hidden='true'>×</span> + <span className='sr-only'>Close</span> </button> - <h4 className="modal-title">Rename Channel</h4> + <h4 className='modal-title'>Rename Channel</h4> </div> - <form role="form"> - <div className="modal-body"> - <div className={ this.state.display_name_error ? "form-group has-error" : "form-group" }> + <form role='form'> + <div className='modal-body'> + <div className={displayNameClass}> <label className='control-label'>Display Name</label> - <input onKeyUp={this.displayNameKeyUp} onChange={this.onDisplayNameChange} type="text" ref="display_name" className="form-control" placeholder="Enter display name" value={this.state.display_name} maxLength="64" /> - { display_name_error } + <input + onKeyUp={this.displayNameKeyUp} + onChange={this.onDisplayNameChange} + type='text' + ref='displayName' + className='form-control' + placeholder='Enter display name' + value={this.state.displayName} + maxLength='64' + /> + {displayNameError} </div> - <div className={ this.state.name_error ? "form-group has-error" : "form-group" }> + <div className={nameClass}> <label className='control-label'>Handle</label> - <input onChange={this.onNameChange} type="text" className="form-control" ref="channel_name" placeholder="lowercase alphanumeric's only" value={this.state.channel_name} maxLength="64" /> - { name_error } + <input + onChange={this.onNameChange} + type='text' + className='form-control' + ref='channelName' + placeholder='lowercase alphanumeric's only' + value={this.state.channelName} + maxLength='64' + /> + {nameError} </div> - { server_error } + {serverError} </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button onClick={this.handleSubmit} type="submit" className="btn btn-primary">Save</button> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + onClick={this.handleSubmit} + type='submit' + className='btn btn-primary' + > + Save + </button> </div> </form> </div> @@ -149,4 +219,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 7df2fed9e..e74ab7f13 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -6,10 +6,10 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserProfile = require('./user_profile.jsx'); var UserStore = require('../stores/user_store.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); var FileAttachmentList = require('./file_attachment_list.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var ActionTypes = Constants.ActionTypes; @@ -25,7 +25,7 @@ export default class RhsComment extends React.Component { e.preventDefault(); var post = this.props.post; - client.createPost(post, post.channel_id, + Client.createPost(post, post.channel_id, function success(data) { AsyncClient.getPosts(post.channel_id); @@ -52,7 +52,7 @@ export default class RhsComment extends React.Component { this.forceUpdate(); } shouldComponentUpdate(nextProps) { - if (!utils.areStatesEqual(nextProps.post, this.props.post)) { + if (!Utils.areStatesEqual(nextProps.post, this.props.post)) { return true; } @@ -73,7 +73,7 @@ export default class RhsComment extends React.Component { type = 'Comment'; } - var message = utils.textToJsx(post.message); + var message = Utils.textToJsx(post.message); var timestamp = UserStore.getCurrentUser().update_at; var loading; @@ -182,7 +182,7 @@ export default class RhsComment extends React.Component { </li> <li className='post-header-col'> <time className='post-right-comment-time'> - {utils.displayCommentDateTime(post.create_at)} + {Utils.displayCommentDateTime(post.create_at)} </time> </li> <li className='post-header-col post-header__reply'> diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx index 4cf4231e9..5156ec4d7 100644 --- a/web/react/components/rhs_header_post.jsx +++ b/web/react/components/rhs_header_post.jsx @@ -1,9 +1,9 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; export default class RhsHeaderPost extends React.Component { constructor(props) { @@ -43,7 +43,7 @@ export default class RhsHeaderPost extends React.Component { }); } render() { - var back; + let back; if (this.props.fromSearch) { back = ( <a diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index b11b39e9e..8da8231a2 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -1,7 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var PostStore = require('../stores/post_store.jsx'); @@ -10,36 +9,47 @@ var utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -function getSearchTermStateFromStores() { - var term = PostStore.getSearchTerm() || ''; - return { - search_term: term - }; -} +export default class SearchBar extends React.Component { + constructor() { + super(); + this.mounted = false; -module.exports = React.createClass({ - displayName: 'SearchBar', - componentDidMount: function() { - PostStore.addSearchTermChangeListener(this._onChange); - }, - componentWillUnmount: function() { - PostStore.removeSearchTermChangeListener(this._onChange); - }, - _onChange: function(doSearch, isMentionSearch) { - if (this.isMounted()) { - var newState = getSearchTermStateFromStores(); + this.onListenerChange = this.onListenerChange.bind(this); + this.handleUserInput = this.handleUserInput.bind(this); + this.performSearch = this.performSearch.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = this.getSearchTermStateFromStores(); + } + getSearchTermStateFromStores() { + var term = PostStore.getSearchTerm() || ''; + return { + searchTerm: term + }; + } + componentDidMount() { + PostStore.addSearchTermChangeListener(this.onListenerChange); + this.mounted = true; + } + componentWillUnmount() { + PostStore.removeSearchTermChangeListener(this.onListenerChange); + this.mounted = false; + } + onListenerChange(doSearch, isMentionSearch) { + if (this.mounted) { + var newState = this.getSearchTermStateFromStores(); if (!utils.areStatesEqual(newState, this.state)) { this.setState(newState); } if (doSearch) { - this.performSearch(newState.search_term, isMentionSearch); + this.performSearch(newState.searchTerm, isMentionSearch); } } - }, - clearFocus: function(e) { + } + clearFocus() { $('.search-bar__container').removeClass('focused'); - }, - handleClose: function(e) { + } + handleClose(e) { e.preventDefault(); AppDispatcher.handleServerAction({ @@ -58,23 +68,23 @@ module.exports = React.createClass({ type: ActionTypes.RECIEVED_POST_SELECTED, results: null }); - }, - handleUserInput: function(e) { + } + handleUserInput(e) { var term = e.target.value; PostStore.storeSearchTerm(term); PostStore.emitSearchTermChange(false); - this.setState({ search_term: term }); - }, - handleUserFocus: function(e) { + this.setState({searchTerm: term}); + } + handleUserFocus(e) { e.target.select(); $('.search-bar__container').addClass('focused'); - }, - performSearch: function(terms, isMentionSearch) { + } + performSearch(terms, isMentionSearch) { if (terms.length) { this.setState({isSearching: true}); client.search( terms, - function(data) { + function success(data) { this.setState({isSearching: false}); if (utils.isMobile()) { React.findDOMNode(this.refs.search).value = ''; @@ -86,38 +96,50 @@ module.exports = React.createClass({ is_mention_search: isMentionSearch }); }.bind(this), - function(err) { + function error(err) { this.setState({isSearching: false}); - AsyncClient.dispatchError(err, "search"); + AsyncClient.dispatchError(err, 'search'); }.bind(this) ); } - }, - handleSubmit: function(e) { + } + handleSubmit(e) { e.preventDefault(); - this.performSearch(this.state.search_term.trim()); - }, - getInitialState: function() { - return getSearchTermStateFromStores(); - }, - render: function() { + this.performSearch(this.state.searchTerm.trim()); + } + render() { + var isSearching = null; + if (this.state.isSearching) { + isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>; + } return ( <div> - <div className="sidebar__collapse" onClick={this.handleClose}><span className="fa fa-angle-left"></span></div> - <span onClick={this.clearFocus} className="search__clear">Cancel</span> - <form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}> - <span className="glyphicon glyphicon-search sidebar__search-icon"></span> + <div + className='sidebar__collapse' + onClick={this.handleClose} > + <span className='fa fa-angle-left'></span> + </div> + <span + className='search__clear' + onClick={this.clearFocus}> + Cancel + </span> + <form + role='form' + className='search__form relative-div' + onSubmit={this.handleSubmit}> + <span className='glyphicon glyphicon-search sidebar__search-icon'></span> <input - type="text" - ref="search" - className="form-control search-bar" - placeholder="Search" - value={this.state.search_term} + type='text' + ref='search' + className='form-control search-bar' + placeholder='Search' + value={this.state.searchTerm} onFocus={this.handleUserFocus} onChange={this.handleUserInput} /> - {this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null} + {isSearching} </form> </div> ); } -}); +} diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx index b978cdb0c..e67e458af 100644 --- a/web/react/components/setting_item_max.jsx +++ b/web/react/components/setting_item_max.jsx @@ -1,33 +1,68 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - render: function() { - var clientError = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null; - var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null; - var extraInfo = this.props.extraInfo ? this.props.extraInfo : null; +export default class SettingItemMax extends React.Component { + render() { + var clientError = null; + if (this.props.client_error) { + clientError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.client_error}</label></div>); + } + + var serverError = null; + if (this.props.server_error) { + serverError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.server_error}</label></div>); + } + + var extraInfo = null; + if (this.props.extraInfo) { + extraInfo = this.props.extraInfo; + } + + var submit = ''; + if (this.props.submit) { + submit = (<a + className='btn btn-sm btn-primary' + href='#' + onClick={this.props.submit}> + Submit</a>); + } var inputs = this.props.inputs; return ( - <ul className="section-max form-horizontal"> - <li className="col-sm-12 section-title">{this.props.title}</li> - <li className="col-sm-9 col-sm-offset-3"> - <ul className="setting-list"> - <li className="setting-list-item"> + <ul className='section-max form-horizontal'> + <li className='col-sm-12 section-title'>{this.props.title}</li> + <li className='col-sm-9 col-sm-offset-3'> + <ul className='setting-list'> + <li className='setting-list-item'> {inputs} {extraInfo} </li> - <li className="setting-list-item"> + <li className='setting-list-item'> <hr /> - { server_error } - { clientError } - { this.props.submit ? <a className="btn btn-sm btn-primary" href="#" onClick={this.props.submit}>Submit</a> : "" } - <a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a> + {serverError} + {clientError} + {submit} + <a + className='btn btn-sm theme' + href='#' + onClick={this.props.updateSection} > + Cancel + </a> </li> </ul> </li> </ul> ); } -}); +} + +SettingItemMax.propTypes = { + inputs: React.PropTypes.array, + client_error: React.PropTypes.string, + server_error: React.PropTypes.string, + extraInfo: React.PropTypes.element, + updateSection: React.PropTypes.func, + submit: React.PropTypes.func, + title: React.PropTypes.string +}; diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx index 3c87e416e..098729a4f 100644 --- a/web/react/components/setting_item_min.jsx +++ b/web/react/components/setting_item_min.jsx @@ -1,19 +1,23 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - displayName: 'SettingsItemMin', - propTypes: { - title: React.PropTypes.string, - disableOpen: React.PropTypes.bool, - updateSection: React.PropTypes.func, - describe: React.PropTypes.string - }, - render: function() { - var editButton = ''; +export default class SettingItemMin extends React.Component { + render() { + let editButton = null; if (!this.props.disableOpen) { - editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>; + editButton = ( + <li className='col-sm-2 section-edit'> + <a + className='section-edit theme' + href='#' + onClick={this.props.updateSection} + > + Edit + </a> + </li> + ); } + return ( <ul className='section-min'> <li className='col-sm-10 section-title'>{this.props.title}</li> @@ -22,4 +26,11 @@ module.exports = React.createClass({ </ul> ); } -}); +} + +SettingItemMin.propTypes = { + title: React.PropTypes.string, + disableOpen: React.PropTypes.bool, + updateSection: React.PropTypes.func, + describe: React.PropTypes.string +}; diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx index d8091ec28..e5cbd6e92 100644 --- a/web/react/components/settings_sidebar.jsx +++ b/web/react/components/settings_sidebar.jsx @@ -1,24 +1,56 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); +export default class SettingsSidebar extends React.Component { + constructor(props) { + super(props); -module.exports = React.createClass({ - displayName:'SettingsSidebar', - updateTab: function(tab) { - this.props.updateTab(tab); + this.handleClick = this.handleClick.bind(this); + } + handleClick(tab) { + this.props.updateTab(tab.name); $('.settings-modal').addClass('display--content'); - }, - render: function() { - var self = this; + } + render() { + let tabList = this.props.tabs.map(function makeTab(tab) { + let key = `${tab.name}_li`; + let className = ''; + if (this.props.activeTab === tab.name) { + className = 'active'; + } + + return ( + <li + key={key} + className={className} + > + <a + href='#' + onClick={this.handleClick.bind(null, tab)} + > + <i className={tab.icon} /> + {tab.uiName} + </a> + </li> + ); + }.bind(this)); + return ( - <div className=""> - <ul className="nav nav-pills nav-stacked"> - {this.props.tabs.map(function(tab) { - return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li> - })} + <div> + <ul className='nav nav-pills nav-stacked'> + {tabList} </ul> </div> ); } -}); +} + +SettingsSidebar.propTypes = { + tabs: React.PropTypes.arrayOf(React.PropTypes.shape({ + name: React.PropTypes.string.isRequired, + uiName: React.PropTypes.string.isRequired, + icon: React.PropTypes.string.isRequired + })).isRequired, + activeTab: React.PropTypes.string, + updateTab: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 5b74165f3..ef23f5bc2 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -8,127 +8,137 @@ var SocketStore = require('../stores/socket_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var SidebarHeader = require('./sidebar_header.jsx'); var SearchBox = require('./search_bar.jsx'); var Constants = require('../utils/constants.jsx'); -function getStateFromStores() { - var members = ChannelStore.getAllMembers(); - var teamMemberMap = UserStore.getActiveOnlyProfiles(); - var currentId = ChannelStore.getCurrentId(); +export default class Sidebar extends React.Component { + constructor(props) { + super(props); - var teammates = []; - for (var id in teamMemberMap) { - if (id === UserStore.getCurrentId()) { - continue; - } - teammates.push(teamMemberMap[id]); - } + this.badgesActive = false; + this.firstUnreadChannel = null; + this.lastUnreadChannel = null; - // Create lists of all read and unread direct channels - var showDirectChannels = []; - var readDirectChannels = []; - for (var i = 0; i < teammates.length; i++) { - var teammate = teammates[i]; + this.onChange = this.onChange.bind(this); + this.onScroll = this.onScroll.bind(this); + this.onResize = this.onResize.bind(this); + this.updateUnreadIndicators = this.updateUnreadIndicators.bind(this); + this.createChannelElement = this.createChannelElement.bind(this); - if (teammate.id === UserStore.getCurrentId()) { - continue; + this.state = this.getStateFromStores(); + this.state.loadingDMChannel = -1; + } + getStateFromStores() { + var members = ChannelStore.getAllMembers(); + var teamMemberMap = UserStore.getActiveOnlyProfiles(); + var currentId = ChannelStore.getCurrentId(); + + var teammates = []; + for (var id in teamMemberMap) { + if (id === UserStore.getCurrentId()) { + continue; + } + teammates.push(teamMemberMap[id]); } - var channelName = ''; - if (teammate.id > UserStore.getCurrentId()) { - channelName = UserStore.getCurrentId() + '__' + teammate.id; - } else { - channelName = teammate.id + '__' + UserStore.getCurrentId(); - } + // Create lists of all read and unread direct channels + var showDirectChannels = []; + var readDirectChannels = []; + for (var i = 0; i < teammates.length; i++) { + var teammate = teammates[i]; - var channel = ChannelStore.getByName(channelName); + if (teammate.id === UserStore.getCurrentId()) { + continue; + } - if (channel != null) { - channel.display_name = teammate.username; - channel.teammate_username = teammate.username; + var channelName = ''; + if (teammate.id > UserStore.getCurrentId()) { + channelName = UserStore.getCurrentId() + '__' + teammate.id; + } else { + channelName = teammate.id + '__' + UserStore.getCurrentId(); + } - channel.status = UserStore.getStatus(teammate.id); + var channel = ChannelStore.getByName(channelName); - var channelMember = members[channel.id]; - var msgCount = channel.total_msg_count - channelMember.msg_count; - if (msgCount > 0) { - showDirectChannels.push(channel); - } else if (currentId === channel.id) { - showDirectChannels.push(channel); + if (channel != null) { + channel.display_name = teammate.username; + channel.teammate_username = teammate.username; + + channel.status = UserStore.getStatus(teammate.id); + + var channelMember = members[channel.id]; + var msgCount = channel.total_msg_count - channelMember.msg_count; + if (msgCount > 0) { + showDirectChannels.push(channel); + } else if (currentId === channel.id) { + showDirectChannels.push(channel); + } else { + readDirectChannels.push(channel); + } } else { - readDirectChannels.push(channel); + var tempChannel = {}; + tempChannel.fake = true; + tempChannel.name = channelName; + tempChannel.display_name = teammate.username; + tempChannel.teammate_username = teammate.username; + tempChannel.status = UserStore.getStatus(teammate.id); + tempChannel.last_post_at = 0; + tempChannel.total_msg_count = 0; + tempChannel.type = 'D'; + readDirectChannels.push(tempChannel); } - } else { - var tempChannel = {}; - tempChannel.fake = true; - tempChannel.name = channelName; - tempChannel.display_name = teammate.username; - tempChannel.teammate_username = teammate.username; - tempChannel.status = UserStore.getStatus(teammate.id); - tempChannel.last_post_at = 0; - tempChannel.total_msg_count = 0; - tempChannel.type = 'D'; - readDirectChannels.push(tempChannel); } - } - // If we don't have MAX_DMS unread channels, sort the read list by last_post_at - if (showDirectChannels.length < Constants.MAX_DMS) { - readDirectChannels.sort(function sortByLastPost(a, b) { - // sort by last_post_at first - if (a.last_post_at > b.last_post_at) { - return -1; - } - if (a.last_post_at < b.last_post_at) { - return 1; - } + // If we don't have MAX_DMS unread channels, sort the read list by last_post_at + if (showDirectChannels.length < Constants.MAX_DMS) { + readDirectChannels.sort(function sortByLastPost(a, b) { + // sort by last_post_at first + if (a.last_post_at > b.last_post_at) { + return -1; + } + if (a.last_post_at < b.last_post_at) { + return 1; + } - // if last_post_at is equal, sort by name - if (a.display_name < b.display_name) { - return -1; - } - if (a.display_name > b.display_name) { - return 1; + // if last_post_at is equal, sort by name + if (a.display_name < b.display_name) { + return -1; + } + if (a.display_name > b.display_name) { + return 1; + } + return 0; + }); + + var index = 0; + while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) { + showDirectChannels.push(readDirectChannels[index]); + index++; } - return 0; - }); + readDirectChannels = readDirectChannels.slice(index); - var index = 0; - while (showDirectChannels.length < Constants.MAX_DMS && index < readDirectChannels.length) { - showDirectChannels.push(readDirectChannels[index]); - index++; + showDirectChannels.sort(function directSort(a, b) { + if (a.display_name < b.display_name) { + return -1; + } + if (a.display_name > b.display_name) { + return 1; + } + return 0; + }); } - readDirectChannels = readDirectChannels.slice(index); - showDirectChannels.sort(function directSort(a, b) { - if (a.display_name < b.display_name) { - return -1; - } - if (a.display_name > b.display_name) { - return 1; - } - return 0; - }); + return { + activeId: currentId, + channels: ChannelStore.getAll(), + members: members, + showDirectChannels: showDirectChannels, + hideDirectChannels: readDirectChannels + }; } - - return { - activeId: currentId, - channels: ChannelStore.getAll(), - members: members, - showDirectChannels: showDirectChannels, - hideDirectChannels: readDirectChannels - }; -} - -module.exports = React.createClass({ - displayName: 'Sidebar', - propTypes: { - teamType: React.PropTypes.string, - teamDisplayName: React.PropTypes.string - }, - componentDidMount: function() { + componentDidMount() { ChannelStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onChange); @@ -140,12 +150,12 @@ module.exports = React.createClass({ this.updateUnreadIndicators(); $(window).on('resize', this.onResize); - }, - componentDidUpdate: function() { + } + componentDidUpdate() { this.updateTitle(); this.updateUnreadIndicators(); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { $(window).off('resize', this.onResize); ChannelStore.removeChangeListener(this.onChange); @@ -153,14 +163,14 @@ module.exports = React.createClass({ UserStore.removeStatusesChangeListener(this.onChange); TeamStore.removeChangeListener(this.onChange); SocketStore.removeChangeListener(this.onSocketChange); - }, - onChange: function() { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { + } + onChange() { + var newState = this.getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { this.setState(newState); } - }, - onSocketChange: function(msg) { + } + onSocketChange(msg) { if (msg.action === 'posted') { if (ChannelStore.getCurrentId() === msg.channel_id) { if (window.isActive) { @@ -208,17 +218,17 @@ module.exports = React.createClass({ if (notifyText.length === 0) { if (msgProps.image) { - utils.notifyMe(title, username + ' uploaded an image', channel); + Utils.notifyMe(title, username + ' uploaded an image', channel); } else if (msgProps.otherFile) { - utils.notifyMe(title, username + ' uploaded a file', channel); + Utils.notifyMe(title, username + ' uploaded a file', channel); } else { - utils.notifyMe(title, username + ' did something new', channel); + Utils.notifyMe(title, username + ' did something new', channel); } } else { - utils.notifyMe(title, username + ' wrote: ' + notifyText, channel); + Utils.notifyMe(title, username + ' wrote: ' + notifyText, channel); } if (!user.notify_props || user.notify_props.desktop_sound === 'true') { - utils.ding(); + Utils.ding(); } } } else if (msg.action === 'viewed') { @@ -243,186 +253,196 @@ module.exports = React.createClass({ } } } - }, - updateTitle: function() { + } + updateTitle() { var channel = ChannelStore.getCurrent(); if (channel) { if (channel.type === 'D') { - var teammateUsername = utils.getDirectTeammate(channel.id).username; + var teammateUsername = Utils.getDirectTeammate(channel.id).username; document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-')); } else { document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-')); } } - }, - onScroll: function() { + } + onScroll() { this.updateUnreadIndicators(); - }, - onResize: function() { + } + onResize() { this.updateUnreadIndicators(); - }, - updateUnreadIndicators: function() { - var container = $(this.refs.container.getDOMNode()); + } + updateUnreadIndicators() { + var container = $(React.findDOMNode(this.refs.container)); if (this.firstUnreadChannel) { - var firstUnreadElement = $(this.refs[this.firstUnreadChannel].getDOMNode()); + var firstUnreadElement = $(React.findDOMNode(this.refs[this.firstUnreadChannel])); if (firstUnreadElement.position().top + firstUnreadElement.height() < 0) { - $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'initial'); + $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'initial'); } else { - $(this.refs.topUnreadIndicator.getDOMNode()).css('display', 'none'); + $(React.findDOMNode(this.refs.topUnreadIndicator)).css('display', 'none'); } } if (this.lastUnreadChannel) { - var lastUnreadElement = $(this.refs[this.lastUnreadChannel].getDOMNode()); + var lastUnreadElement = $(React.findDOMNode(this.refs[this.lastUnreadChannel])); if (lastUnreadElement.position().top > container.height()) { - $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'initial'); + $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'initial'); } else { - $(this.refs.bottomUnreadIndicator.getDOMNode()).css('display', 'none'); + $(React.findDOMNode(this.refs.bottomUnreadIndicator)).css('display', 'none'); } } - }, - getInitialState: function() { - var newState = getStateFromStores(); - newState.loadingDMChannel = -1; - - return newState; - }, - render: function() { + } + createChannelElement(channel, index) { var members = this.state.members; var activeId = this.state.activeId; - var badgesActive = false; + var channelMember = members[channel.id]; + var msgCount; - // keep track of the first and last unread channels so we can use them to set the unread indicators - var self = this; - this.firstUnreadChannel = null; - this.lastUnreadChannel = null; - - function createChannelElement(channel, index) { - var channelMember = members[channel.id]; - var msgCount; - - var linkClass = ''; - if (channel.id === activeId) { - linkClass = 'active'; - } + var linkClass = ''; + if (channel.id === activeId) { + linkClass = 'active'; + } - var unread = false; - if (channelMember) { - msgCount = channel.total_msg_count - channelMember.msg_count; - unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0; - } + var unread = false; + if (channelMember) { + msgCount = channel.total_msg_count - channelMember.msg_count; + unread = (msgCount > 0 && channelMember.notify_level !== 'quiet') || channelMember.mention_count > 0; + } - var titleClass = ''; - if (unread) { - titleClass = 'unread-title'; + var titleClass = ''; + if (unread) { + titleClass = 'unread-title'; - if (!self.firstUnreadChannel) { - self.firstUnreadChannel = channel.name; - } - self.lastUnreadChannel = channel.name; - } - - var badge = null; - if (channelMember) { - if (channel.type === 'D') { - // direct message channels show badges for any number of unread posts - msgCount = channel.total_msg_count - channelMember.msg_count; - if (msgCount > 0) { - badge = <span className='badge pull-right small'>{msgCount}</span>; - badgesActive = true; - } - } else if (channelMember.mention_count > 0) { - // public and private channels only show badges for mentions - badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>; - badgesActive = true; - } - } else if (self.state.loadingDMChannel === index && channel.type === 'D') { - badge = <img className='channel-loading-gif pull-right' src='/static/images/load.gif'/>; + if (!this.firstUnreadChannel) { + this.firstUnreadChannel = channel.name; } + this.lastUnreadChannel = channel.name; + } - // set up status icon for direct message channels - var status = null; + var badge = null; + if (channelMember) { if (channel.type === 'D') { - var statusIcon = ''; - if (channel.status === 'online') { - statusIcon = Constants.ONLINE_ICON_SVG; - } else if (channel.status === 'away') { - statusIcon = Constants.ONLINE_ICON_SVG; - } else { - statusIcon = Constants.OFFLINE_ICON_SVG; + // direct message channels show badges for any number of unread posts + msgCount = channel.total_msg_count - channelMember.msg_count; + if (msgCount > 0) { + badge = <span className='badge pull-right small'>{msgCount}</span>; + this.badgesActive = true; } - status = <span className='status' dangerouslySetInnerHTML={{__html: statusIcon}} />; + } else if (channelMember.mention_count > 0) { + // public and private channels only show badges for mentions + badge = <span className='badge pull-right small'>{channelMember.mention_count}</span>; + this.badgesActive = true; } + } else if (this.state.loadingDMChannel === index && channel.type === 'D') { + badge = ( + <img + className='channel-loading-gif pull-right' + src='/static/images/load.gif' + /> + ); + } - // set up click handler to switch channels (or create a new channel for non-existant ones) - var handleClick = null; - var href = '#'; - var teamURL = TeamStore.getCurrentTeamUrl(); + // set up status icon for direct message channels + var status = null; + if (channel.type === 'D') { + var statusIcon = ''; + if (channel.status === 'online') { + statusIcon = Constants.ONLINE_ICON_SVG; + } else if (channel.status === 'away') { + statusIcon = Constants.ONLINE_ICON_SVG; + } else { + statusIcon = Constants.OFFLINE_ICON_SVG; + } + status = ( + <span + className='status' + dangerouslySetInnerHTML={{__html: statusIcon}} + /> + ); + } - if (!channel.fake) { + // set up click handler to switch channels (or create a new channel for non-existant ones) + var handleClick = null; + var href = '#'; + var teamURL = TeamStore.getCurrentTeamUrl(); + + if (!channel.fake) { + handleClick = function clickHandler(e) { + e.preventDefault(); + Utils.switchChannel(channel); + }; + } else if (channel.fake && teamURL) { + // 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(); - utils.switchChannel(channel); + this.setState({loadingDMChannel: index}); + + Client.createDirectChannel(channel, otherUserId, + function success(data) { + this.setState({loadingDMChannel: -1}); + AsyncClient.getChannel(data.id); + Utils.switchChannel(data); + }, + function error() { + this.setState({loadingDMChannel: -1}); + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; + } + ); }; - } else if (channel.fake && teamURL) { - // It's a direct message channel that doesn't exist yet so let's create it now - var otherUserId = utils.getUserIdFromChannelName(channel); - - if (self.state.loadingDMChannel === -1) { - handleClick = function clickHandler(e) { - e.preventDefault(); - self.setState({loadingDMChannel: index}); - - Client.createDirectChannel(channel, otherUserId, - function success(data) { - self.setState({loadingDMChannel: -1}); - AsyncClient.getChannel(data.id); - utils.switchChannel(data); - }, - function error() { - self.setState({loadingDMChannel: -1}); - window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; - } - ); - }; - } } - - return ( - <li key={channel.name} ref={channel.name} className={linkClass}> - <a className={'sidebar-channel ' + titleClass} href={href} onClick={handleClick}> - {status} - {channel.display_name} - {badge} - </a> - </li> - ); } + return ( + <li + key={channel.name} + ref={channel.name} + className={linkClass} + > + <a + className={'sidebar-channel ' + titleClass} + href={href} + onClick={handleClick} + > + {status} + {channel.display_name} + {badge} + </a> + </li> + ); + } + render() { + this.badgesActive = false; + + // keep track of the first and last unread channels so we can use them to set the unread indicators + this.firstUnreadChannel = null; + this.lastUnreadChannel = null; + // create elements for all 3 types of channels var channelItems = this.state.channels.filter( function filterPublicChannels(channel) { return channel.type === 'O'; } - ).map(createChannelElement); + ).map(this.createChannelElement); var privateChannelItems = this.state.channels.filter( function filterPrivateChannels(channel) { return channel.type === 'P'; } - ).map(createChannelElement); + ).map(this.createChannelElement); - var directMessageItems = this.state.showDirectChannels.map(createChannelElement); + var directMessageItems = this.state.showDirectChannels.map(this.createChannelElement); // update the favicon to show if there are any notifications var link = document.createElement('link'); link.type = 'image/x-icon'; link.rel = 'shortcut icon'; link.id = 'favicon'; - if (badgesActive) { + if (this.badgesActive) { link.href = '/static/images/redfavicon.ico'; } else { link.href = '/static/images/favicon.ico'; @@ -438,7 +458,13 @@ module.exports = React.createClass({ if (this.state.hideDirectChannels.length > 0) { directMessageMore = ( <li> - <a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}> + <a + href='#' + data-toggle='modal' + className='nav-more' + data-target='#more_direct_channels' + data-channels={JSON.stringify(this.state.hideDirectChannels)} + > {'More (' + this.state.hideDirectChannels.length + ')'} </a> </li> @@ -447,21 +473,76 @@ module.exports = React.createClass({ return ( <div> - <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} /> + <SidebarHeader + teamDisplayName={this.props.teamDisplayName} + teamType={this.props.teamType} + /> <SearchBox /> - <div ref='topUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-top' style={{display: 'none'}}>Unread post(s) above</div> - <div ref='bottomUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' style={{display: 'none'}}>Unread post(s) below</div> + <div + ref='topUnreadIndicator' + className='nav-pills__unread-indicator nav-pills__unread-indicator-top' + style={{display: 'none'}} + > + Unread post(s) above + </div> + <div + ref='bottomUnreadIndicator' + className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' + style={{display: 'none'}} + > + Unread post(s) below + </div> - <div ref='container' className='nav-pills__container' onScroll={this.onScroll}> + <div + ref='container' + className='nav-pills__container' + onScroll={this.onScroll} + > <ul className='nav nav-pills nav-stacked'> - <li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li> + <li> + <h4> + Channels + <a + className='add-channel-btn' + href='#' + data-toggle='modal' + data-target='#new_channel' + data-channeltype='O' + > + + + </a> + </h4> + </li> {channelItems} - <li><a href='#' data-toggle='modal' className='nav-more' data-target='#more_channels' data-channeltype='O'>More...</a></li> + <li> + <a + href='#' + data-toggle='modal' + className='nav-more' + data-target='#more_channels' + data-channeltype='O' + > + More... + </a> + </li> </ul> <ul className='nav nav-pills nav-stacked'> - <li><h4>Private Groups<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='P'>+</a></h4></li> + <li> + <h4> + Private Groups + <a + className='add-channel-btn' + href='#' + data-toggle='modal' + data-target='#new_channel' + data-channeltype='P' + > + + + </a> + </h4> + </li> {privateChannelItems} </ul> <ul className='nav nav-pills nav-stacked'> @@ -473,4 +554,13 @@ module.exports = React.createClass({ </div> ); } -}); +} + +Sidebar.defaultProps = { + teamType: '', + teamDisplayName: '' +}; +Sidebar.propTypes = { + teamType: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index af65b7e1d..6e219cc6c 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -1,133 +1,25 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +var NavbarDropdown = require('./navbar_dropdown.jsx'); var UserStore = require('../stores/user_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); -var Constants = require('../utils/constants.jsx'); +export default class SidebarHeader extends React.Component { + constructor(props) { + super(props); -function getStateFromStores() { - return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; -} - -var NavbarDropdown = React.createClass({ - handleLogoutClick: function(e) { - e.preventDefault(); - client.logout(); - }, - blockToggle: false, - componentDidMount: function() { - UserStore.addTeamsChangeListener(this.onListenerChange); - TeamStore.addChangeListener(this.onListenerChange); - - var self = this; - $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function() { - self.blockToggle = true; - setTimeout(function() { - self.blockToggle = false; - }, 100); - }); - }, - componentWillUnmount: function() { - UserStore.removeTeamsChangeListener(this.onListenerChange); - TeamStore.removeChangeListener(this.onListenerChange); - - $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown'); - }, - onListenerChange: function() { - if (this.isMounted()) { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } - } - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { - var teamLink = ''; - var inviteLink = ''; - var manageLink = ''; - var currentUser = UserStore.getCurrentUser(); - var isAdmin = false; - var teamSettings = null; - - if (currentUser != null) { - isAdmin = currentUser.roles.indexOf('admin') > -1; - - inviteLink = (<li> <a href='#' data-toggle='modal' data-target='#invite_member'>Invite New Member</a> </li>); - - if (this.props.teamType === 'O') { - teamLink = ( - <li> - <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}>Get Team Invite Link</a> - </li> - ); - } - } - - if (isAdmin) { - manageLink = (<li> <a href='#' data-toggle='modal' data-target='#team_members'>Manage Team</a> </li>); - teamSettings = (<li> <a href='#' data-toggle='modal' data-target='#team_settings'>Team Settings</a> </li>); - } + this.toggleDropdown = this.toggleDropdown.bind(this); - var teams = []; - - teams.push(<li className='divider' key='div'></li>); - if (this.state.teams.length > 1 && this.state.currentTeam) { - var curTeamName = this.state.currentTeam.name; - this.state.teams.forEach(function(teamName) { - if (teamName !== curTeamName) { - teams.push(<li key={teamName}><a href={utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>); - } - }); - } - teams.push(<li key='newTeam_li'><a key='newTeam_a' target="_blank" href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>); - - return ( - <ul className='nav navbar-nav navbar-right'> - <li ref='dropdown' className='dropdown'> - <a href='#' className='dropdown-toggle' data-toggle='dropdown' role='button' aria-expanded='false'> - <span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> - </a> - <ul className='dropdown-menu' role='menu'> - <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li> - {teamSettings} - {inviteLink} - {teamLink} - {manageLink} - <li><a href='#' onClick={this.handleLogoutClick}>Logout</a></li> - {teams} - <li className='divider'></li> - <li><a target='_blank' href={config.HelpLink}>Help</a></li> - <li><a target='_blank' href={config.ReportProblemLink}>Report a Problem</a></li> - </ul> - </li> - </ul> - ); + this.state = {}; } -}); - -module.exports = React.createClass({ - displayName: 'SidebarHeader', - getDefaultProps: function() { - return { - teamDisplayName: config.SiteName - }; - }, - - toggleDropdown: function() { + toggleDropdown() { if (this.refs.dropdown.blockToggle) { this.refs.dropdown.blockToggle = false; return; } $('.team__header').find('.dropdown-toggle').dropdown('toggle'); - }, - - render: function() { + } + render() { var me = UserStore.getCurrentUser(); var profilePicture = null; @@ -136,20 +28,38 @@ module.exports = React.createClass({ } if (me.last_picture_update) { - profilePicture = (<img className='user__picture' src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} />); + profilePicture = (<img + className='user__picture' + src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + />); } return ( <div className='team__header theme'> - <a href='#' onClick={this.toggleDropdown}> + <a + href='#' + onClick={this.toggleDropdown} + > {profilePicture} <div className='header__info'> <div className='user__name'>{'@' + me.username}</div> - <div className='team__name'>{this.props.teamDisplayName }</div> + <div className='team__name'>{this.props.teamDisplayName}</div> </div> </a> - <NavbarDropdown ref='dropdown' teamType={this.props.teamType} /> + <NavbarDropdown + ref='dropdown' + teamType={this.props.teamType} + /> </div> ); } -}); +} + +SidebarHeader.defaultProps = { + teamDisplayName: config.SiteName, + teamType: '' +}; +SidebarHeader.propTypes = { + teamDisplayName: React.PropTypes.string, + teamType: React.PropTypes.string +}; diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index df75e3adf..9aeda6626 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -4,47 +4,49 @@ var SearchResults = require('./search_results.jsx'); var RhsThread = require('./rhs_thread.jsx'); var PostStore = require('../stores/post_store.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -function getStateFromStores(from_search) { - return { search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch() }; +function getStateFromStores() { + return {search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch()}; } -module.exports = React.createClass({ - componentDidMount: function() { - PostStore.addSearchChangeListener(this._onSearchChange); - PostStore.addSelectedPostChangeListener(this._onSelectedChange); - }, - componentWillUnmount: function() { - PostStore.removeSearchChangeListener(this._onSearchChange); - PostStore.removeSelectedPostChangeListener(this._onSelectedChange); - }, - _onSelectedChange: function(from_search) { - if (this.isMounted()) { - var newState = getStateFromStores(from_search); - newState.from_search = from_search; - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } +export default class SidebarRight extends React.Component { + constructor(props) { + super(props); + + this.onSelectedChange = this.onSelectedChange.bind(this); + this.onSearchChange = this.onSearchChange.bind(this); + this.resize = this.resize.bind(this); + + this.state = getStateFromStores(); + } + componentDidMount() { + PostStore.addSearchChangeListener(this.onSearchChange); + PostStore.addSelectedPostChangeListener(this.onSelectedChange); + } + componentWillUnmount() { + PostStore.removeSearchChangeListener(this.onSearchChange); + PostStore.removeSelectedPostChangeListener(this.onSelectedChange); + } + onSelectedChange(fromSearch) { + var newState = getStateFromStores(fromSearch); + newState.from_search = fromSearch; + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); } - }, - _onSearchChange: function() { - if (this.isMounted()) { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } + } + onSearchChange() { + var newState = getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); } - }, - resize: function() { + } + resize() { var postHolder = $('.post-list-holder-by-time'); postHolder[0].scrollTop = postHolder[0].scrollHeight - 224; - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { - if (! (this.state.search_visible || this.state.post_right_visible)) { + } + render() { + if (!(this.state.search_visible || this.state.post_right_visible)) { $('.inner__wrap').removeClass('move--left').removeClass('move--right'); $('.sidebar--right').removeClass('move--left'); this.resize(); @@ -58,25 +60,27 @@ module.exports = React.createClass({ $('.sidebar--right').addClass('move--left'); $('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); this.resize(); - setTimeout(function(){ - $('.sidebar__overlay').fadeOut("200", function(){ + setTimeout(function overlayTimer() { + $('.sidebar__overlay').fadeOut('200', function fadeOverlay() { $(this).remove(); }); - },500) + }, 500); - var content = ""; + var content = ''; if (this.state.search_visible) { content = <SearchResults isMentionSearch={this.state.is_mention_search} />; - } - else if (this.state.post_right_visible) { - content = <RhsThread fromSearch={this.state.from_search} isMentionSearch={this.state.is_mention_search} />; + } else if (this.state.post_right_visible) { + content = (<RhsThread + fromSearch={this.state.from_search} + isMentionSearch={this.state.is_mention_search} + />); } return ( - <div className="sidebar-right-container"> - { content } + <div className='sidebar-right-container'> + {content} </div> ); } -}); +} diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index 13640b1e5..bf08e6508 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -1,10 +1,10 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var ChoosePage = require('./team_signup_choose_auth.jsx'); -var EmailSignUpPage = require('./team_signup_with_email.jsx'); -var SSOSignupPage = require('./team_signup_with_sso.jsx'); -var Constants = require('../utils/constants.jsx'); +const ChoosePage = require('./team_signup_choose_auth.jsx'); +const EmailSignUpPage = require('./team_signup_with_email.jsx'); +const SSOSignupPage = require('./team_signup_with_sso.jsx'); +const Constants = require('../utils/constants.jsx'); export default class TeamSignUp extends React.Component { constructor(props) { @@ -30,14 +30,14 @@ export default class TeamSignUp extends React.Component { return <EmailSignUpPage />; } else if (this.state.page === 'service' && this.state.service !== '') { return <SSOSignupPage service={this.state.service} />; - } else { - return ( - <ChoosePage - services={this.props.services} - updatePage={this.updatePage} - /> - ); } + + return ( + <ChoosePage + services={this.props.services} + updatePage={this.updatePage} + /> + ); } } diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 756aae638..1d45548da 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -10,69 +10,99 @@ var UsernamePage = require('./team_signup_username_page.jsx'); var PasswordPage = require('./team_signup_password_page.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -module.exports = React.createClass({ - displayName: 'SignupTeamComplete', - propTypes: { - hash: React.PropTypes.string, - email: React.PropTypes.string, - data: React.PropTypes.string - }, - updateParent: function(state, skipSet) { +export default class SignupTeamComplete extends React.Component { + constructor(props) { + super(props); + + this.updateParent = this.updateParent.bind(this); + + var initialState = BrowserStore.getGlobalItem(props.hash); + + if (!initialState) { + initialState = {}; + initialState.wizard = 'welcome'; + initialState.team = {}; + initialState.team.email = this.props.email; + initialState.team.allowed_domains = ''; + initialState.invites = []; + initialState.invites.push(''); + initialState.invites.push(''); + initialState.invites.push(''); + initialState.user = {}; + initialState.hash = this.props.hash; + initialState.data = this.props.data; + } + + this.state = initialState; + } + updateParent(state, skipSet) { BrowserStore.setGlobalItem(this.props.hash, state); if (!skipSet) { this.setState(state); } - }, - getInitialState: function() { - var props = BrowserStore.getGlobalItem(this.props.hash); - - if (!props) { - props = {}; - props.wizard = 'welcome'; - props.team = {}; - props.team.email = this.props.email; - props.team.allowed_domains = ''; - props.invites = []; - props.invites.push(''); - props.invites.push(''); - props.invites.push(''); - props.user = {}; - props.hash = this.props.hash; - props.data = this.props.data; - } - - return props; - }, - render: function() { + } + render() { if (this.state.wizard === 'welcome') { - return <WelcomePage state={this.state} updateParent={this.updateParent} />; + return (<WelcomePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'team_display_name') { - return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />; + return (<TeamDisplayNamePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'team_url') { - return <TeamURLPage state={this.state} updateParent={this.updateParent} />; + return (<TeamURLPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'allowed_domains') { - return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />; + return (<AllowedDomainsPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'send_invites') { - return <SendInivtesPage state={this.state} updateParent={this.updateParent} />; + return (<SendInivtesPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'username') { - return <UsernamePage state={this.state} updateParent={this.updateParent} />; + return (<UsernamePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'password') { - return <PasswordPage state={this.state} updateParent={this.updateParent} />; + return (<PasswordPage + state={this.state} + updateParent={this.updateParent} + />); } return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>); } -}); +} + +SignupTeamComplete.defaultProps = { + hash: '', + email: '', + data: '' +}; +SignupTeamComplete.propTypes = { + hash: React.PropTypes.string, + email: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 2080cc191..67e85d4de 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -7,11 +7,31 @@ var UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - handleSubmit: function(e) { +export default class SignupUserComplete extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + var initialState = BrowserStore.getGlobalItem(this.props.hash); + + if (!initialState) { + initialState = {}; + initialState.wizard = 'welcome'; + initialState.user = {}; + initialState.user.team_id = this.props.teamId; + initialState.user.email = this.props.email; + initialState.hash = this.props.hash; + initialState.data = this.props.data; + initialState.original_email = this.props.email; + } + + this.state = initialState; + } + handleSubmit(e) { e.preventDefault(); - this.state.user.username = this.refs.name.getDOMNode().value.trim(); + this.state.user.username = React.findDOMNode(this.refs.name).value.trim(); if (!this.state.user.username) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; @@ -31,14 +51,14 @@ module.exports = React.createClass({ return; } - this.state.user.email = this.refs.email.getDOMNode().value.trim(); + this.state.user.email = React.findDOMNode(this.refs.email).value.trim(); if (!this.state.user.email) { this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); return; } - this.state.user.password = this.refs.password.getDOMNode().value.trim(); - if (!this.state.user.password || this.state.user.password .length < 5) { + this.state.user.password = React.findDOMNode(this.refs.password).value.trim(); + if (!this.state.user.password || this.state.user.password .length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; } @@ -48,11 +68,11 @@ module.exports = React.createClass({ this.state.user.allow_marketing = true; client.createUser(this.state.user, this.state.data, this.state.hash, - function(data) { + function createUserSuccess() { client.track('signup', 'signup_user_02_complete'); client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password, - function(data) { + function emailLoginSuccess(data) { UserStore.setLastEmail(this.state.user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) { @@ -60,7 +80,7 @@ module.exports = React.createClass({ } window.location.href = '/'; }.bind(this), - function(err) { + function emailLoginFailure(err) { if (err.message === 'Login failed because email address has not been verified') { window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { @@ -69,28 +89,12 @@ module.exports = React.createClass({ }.bind(this) ); }.bind(this), - function(err) { + function createUserFailure(err) { this.setState({serverError: err.message}); }.bind(this) ); - }, - getInitialState: function() { - var state = BrowserStore.getGlobalItem(this.props.hash); - - if (!state) { - state = {}; - state.wizard = 'welcome'; - state.user = {}; - state.user.team_id = this.props.teamId; - state.user.email = this.props.email; - state.hash = this.props.hash; - state.data = this.props.data; - state.original_email = this.props.email; - } - - return state; - }, - render: function() { + } + render() { client.track('signup', 'signup_user_01_welcome'); if (this.state.wizard === 'finished') { @@ -134,16 +138,24 @@ module.exports = React.createClass({ yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>; } - var emailContainerStyle = "margin--extra"; + var emailContainerStyle = 'margin--extra'; if (this.state.original_email) { - emailContainerStyle = "hidden"; + emailContainerStyle = 'hidden'; } var email = ( <div className={emailContainerStyle}> <h5><strong>What's your email address?</strong></h5> <div className={emailDivStyle}> - <input type='email' ref='email' className='form-control' defaultValue={this.state.user.email} placeholder='' maxLength='128' autoFocus={true} /> + <input + type='email' + ref='email' + className='form-control' + defaultValue={this.state.user.email} + placeholder='' + maxLength='128' + autoFocus={true} + /> {emailError} </div> </div> @@ -155,7 +167,10 @@ module.exports = React.createClass({ var signupMessage = []; if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) { signupMessage.push( - <a className='btn btn-custom-login gitlab' href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}> + <a + className='btn btn-custom-login gitlab' + href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search} + > <span className='icon' /> <span>with GitLab</span> </a> @@ -172,7 +187,13 @@ module.exports = React.createClass({ <div className='margin--extra'> <h5><strong>Choose your username</strong></h5> <div className={nameDivStyle}> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' /> + <input + type='text' + ref='name' + className='form-control' + placeholder='' + maxLength='128' + /> {nameError} <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p> </div> @@ -180,12 +201,26 @@ module.exports = React.createClass({ <div className='margin--extra'> <h5><strong>Choose your password</strong></h5> <div className={passwordDivStyle}> - <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + <input + type='password' + ref='password' + className='form-control' + placeholder='' + maxLength='128' + /> {passwordError} </div> </div> </div> - <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p> + <p className='margin--extra'> + <button + type='submit' + onClick={this.handleSubmit} + className='btn-primary btn' + > + Create Account + </button> + </p> </div> ); } @@ -209,7 +244,10 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h5 className='margin--less'>Welcome to:</h5> <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2> <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> @@ -222,6 +260,23 @@ module.exports = React.createClass({ </div> ); } -}); - +} +SignupUserComplete.defaultProps = { + teamName: '', + hash: '', + teamId: '', + email: '', + data: null, + authServices: '', + teamDisplayName: '' +}; +SignupUserComplete.propTypes = { + teamName: React.PropTypes.string, + hash: React.PropTypes.string, + teamId: React.PropTypes.string, + email: React.PropTypes.string, + data: React.PropTypes.string, + authServices: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx index 4f8f0b2cf..e5398332e 100644 --- a/web/react/components/team_feature_tab.jsx +++ b/web/react/components/team_feature_tab.jsx @@ -4,65 +4,65 @@ var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -module.exports = React.createClass({ - displayName: 'Feature Tab', - propTypes: { - updateSection: React.PropTypes.func.isRequired, - team: React.PropTypes.object.isRequired, - activeSection: React.PropTypes.string.isRequired - }, - submitValetFeature: function() { +export default class FeatureTab extends React.Component { + constructor(props) { + super(props); + + this.submitValetFeature = this.submitValetFeature.bind(this); + this.handleValetRadio = this.handleValetRadio.bind(this); + this.onUpdateSection = this.onUpdateSection.bind(this); + + this.state = {}; + var team = this.props.team; + + if (team && team.allow_valet) { + this.state.allowValet = 'true'; + } else { + this.state.allowValet = 'false'; + } + } + componentWillReceiveProps(newProps) { + var team = newProps.team; + + var allowValet = 'false'; + if (team && team.allow_valet) { + allowValet = 'true'; + } + + this.setState({allowValet: allowValet}); + } + submitValetFeature() { var data = {}; data.allow_valet = this.state.allowValet; - client.updateValetFeature(data, - function() { + Client.updateValetFeature(data, + function success() { this.props.updateSection(''); AsyncClient.getMyTeam(); }.bind(this), - function(err) { + function fail(err) { var state = this.getInitialState(); state.serverError = err; this.setState(state); }.bind(this) ); - }, - handleValetRadio: function(val) { + } + handleValetRadio(val) { this.setState({allowValet: val}); - this.refs.wrapper.getDOMNode().focus(); - }, - componentWillReceiveProps: function(newProps) { - var team = newProps.team; - - var allowValet = 'false'; - if (team && team.allow_valet) { - allowValet = 'true'; - } - - this.setState({allowValet: allowValet}); - }, - getInitialState: function() { - var team = this.props.team; - - var allowValet = 'false'; - if (team && team.allow_valet) { - allowValet = 'true'; - } - - return {allowValet: allowValet}; - }, - onUpdateSection: function(e) { + React.findDOMNode(this.refs.wrapper).focus(); + } + onUpdateSection(e) { e.preventDefault(); if (this.props.activeSection === 'valet') { this.props.updateSection(''); } else { this.props.updateSection('valet'); } - }, - render: function() { + } + render() { var clientError = null; var serverError = null; if (this.state.clientError) { @@ -73,7 +73,6 @@ module.exports = React.createClass({ } var valetSection; - var self = this; if (this.props.activeSection === 'valet') { var valetActive = [false, false]; @@ -92,7 +91,7 @@ module.exports = React.createClass({ <input type='radio' checked={valetActive[0]} - onChange={self.handleValetRadio.bind(this, 'true')} + onChange={this.handleValetRadio.bind(this, 'true')} > On </input> @@ -104,7 +103,7 @@ module.exports = React.createClass({ <input type='radio' checked={valetActive[1]} - onChange={self.handleValetRadio.bind(this, 'false')} + onChange={this.handleValetRadio.bind(this, 'false')} > Off </input> @@ -145,10 +144,25 @@ module.exports = React.createClass({ return ( <div> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' ref='title'><i className='modal-back'></i>Advanced Features</h4> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <i className='modal-back'></i>Advanced Features + </h4> </div> - <div ref='wrapper' className='user-settings'> + <div + ref='wrapper' + className='user-settings' + > <h3 className='tab-header'>Advanced Features</h3> <div className='divider-dark first'/> {valetSection} @@ -157,4 +171,14 @@ module.exports = React.createClass({ </div> ); } -}); +} + +FeatureTab.defaultProps = { + team: {}, + activeSection: '' +}; +FeatureTab.propTypes = { + updateSection: React.PropTypes.func.isRequired, + team: React.PropTypes.object.isRequired, + activeSection: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx index fd2a22731..2966a8a9a 100644 --- a/web/react/components/team_general_tab.jsx +++ b/web/react/components/team_general_tab.jsx @@ -1,11 +1,11 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var SettingItemMin = require('./setting_item_min.jsx'); -var SettingItemMax = require('./setting_item_max.jsx'); +const SettingItemMin = require('./setting_item_min.jsx'); +const SettingItemMax = require('./setting_item_max.jsx'); -var client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const Utils = require('../utils/utils.jsx'); export default class GeneralTab extends React.Component { constructor(props) { @@ -21,10 +21,10 @@ export default class GeneralTab extends React.Component { handleNameSubmit(e) { e.preventDefault(); - var state = {serverError: '', clientError: ''}; - var valid = true; + let state = {serverError: '', clientError: ''}; + let valid = true; - var name = this.state.name.trim(); + const name = this.state.name.trim(); if (!name) { state.clientError = 'This field is required'; valid = false; @@ -41,10 +41,10 @@ export default class GeneralTab extends React.Component { return; } - var data = {}; + let data = {}; data.new_name = name; - client.updateTeamDisplayName(data, + Client.updateTeamDisplayName(data, function nameChangeSuccess() { this.props.updateSection(''); $('#team_settings').modal('hide'); @@ -84,8 +84,8 @@ export default class GeneralTab extends React.Component { this.setState({name: e.target.value}); } render() { - var clientError = null; - var serverError = null; + let clientError = null; + let serverError = null; if (this.state.clientError) { clientError = this.state.clientError; } @@ -93,18 +93,21 @@ export default class GeneralTab extends React.Component { serverError = this.state.serverError; } - var nameSection; + let nameSection; if (this.props.activeSection === 'name') { let inputs = []; - let teamNameLabel = utils.toTitleCase(strings.Team) + ' Name'; - if (utils.isMobile()) { + let teamNameLabel = Utils.toTitleCase(strings.Team) + ' Name'; + if (Utils.isMobile()) { teamNameLabel = ''; } inputs.push( - <div key='teamNameSetting' className='form-group'> + <div + key='teamNameSetting' + className='form-group' + > <label className='col-sm-5 control-label'>{teamNameLabel}</label> <div className='col-sm-7'> <input @@ -119,7 +122,7 @@ export default class GeneralTab extends React.Component { nameSection = ( <SettingItemMax - title={utils.toTitleCase(strings.Team) + ' Name'} + title={`${Utils.toTitleCase(strings.Team)} Name`} inputs={inputs} submit={this.handleNameSubmit} server_error={serverError} @@ -132,7 +135,7 @@ export default class GeneralTab extends React.Component { nameSection = ( <SettingItemMin - title={utils.toTitleCase(strings.Team) + ' Name'} + title={`${Utils.toTitleCase(strings.Team)} Name`} describe={describe} updateSection={this.onUpdateSection} /> diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx index 1a79eef1d..53855fe1c 100644 --- a/web/react/components/team_settings.jsx +++ b/web/react/components/team_settings.jsx @@ -5,66 +5,83 @@ var TeamStore = require('../stores/team_store.jsx'); var ImportTab = require('./team_import_tab.jsx'); var FeatureTab = require('./team_feature_tab.jsx'); var GeneralTab = require('./team_general_tab.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - displayName: 'Team Settings', - propTypes: { - activeTab: React.PropTypes.string.isRequired, - activeSection: React.PropTypes.string.isRequired, - updateSection: React.PropTypes.func.isRequired, - teamDisplayName: React.PropTypes.string.isRequired - }, - componentDidMount: function() { +export default class TeamSettings extends React.Component { + constructor(props) { + super(props); + + this.onChange = this.onChange.bind(this); + + this.state = {team: TeamStore.getCurrent()}; + } + componentDidMount() { TeamStore.addChangeListener(this.onChange); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { TeamStore.removeChangeListener(this.onChange); - }, - onChange: function() { + } + onChange() { var team = TeamStore.getCurrent(); - if (!utils.areStatesEqual(this.state.team, team)) { + if (!Utils.areStatesEqual(this.state.team, team)) { this.setState({team: team}); } - }, - getInitialState: function() { - return {team: TeamStore.getCurrent()}; - }, - render: function() { + } + render() { var result; switch (this.props.activeTab) { - case 'general': - result = ( - <div> - <GeneralTab - team={this.state.team} - activeSection={this.props.activeSection} - updateSection={this.props.updateSection} - teamDisplayName={this.props.teamDisplayName} - /> - </div> - ); - break; - case 'feature': - result = ( - <div> - <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} /> - </div> - ); - break; - case 'import': - result = ( - <div> - <ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} /> - </div> - ); - break; - default: - result = ( - <div/> - ); - break; + case 'general': + result = ( + <div> + <GeneralTab + team={this.state.team} + activeSection={this.props.activeSection} + updateSection={this.props.updateSection} + teamDisplayName={this.props.teamDisplayName} + /> + </div> + ); + break; + case 'feature': + result = ( + <div> + <FeatureTab + team={this.state.team} + activeSection={this.props.activeSection} + updateSection={this.props.updateSection} + /> + </div> + ); + break; + case 'import': + result = ( + <div> + <ImportTab + team={this.state.team} + activeSection={this.props.activeSection} + updateSection={this.props.updateSection} + /> + </div> + ); + break; + default: + result = ( + <div/> + ); + break; } return result; } -}); +} + +TeamSettings.defaultProps = { + activeTab: '', + activeSection: '', + teamDisplayName: '' +}; +TeamSettings.propTypes = { + activeTab: React.PropTypes.string.isRequired, + activeSection: React.PropTypes.string.isRequired, + updateSection: React.PropTypes.func.isRequired, + teamDisplayName: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx index 7e65e8cab..668bf76cf 100644 --- a/web/react/components/team_settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -1,70 +1,96 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var SettingsSidebar = require('./settings_sidebar.jsx'); -var TeamSettings = require('./team_settings.jsx'); +const SettingsSidebar = require('./settings_sidebar.jsx'); +const TeamSettings = require('./team_settings.jsx'); -module.exports = React.createClass({ - displayName: 'Team Settings Modal', - propTypes: { - teamDisplayName: React.PropTypes.string.isRequired - }, - componentDidMount: function() { - $('body').on('click', '.modal-back', function onClick() { +export default class TeamSettingsModal extends React.Component { + constructor(props) { + super(props); + + this.updateTab = this.updateTab.bind(this); + this.updateSection = this.updateSection.bind(this); + + this.state = { + activeTab: 'general', + activeSection: '' + }; + } + componentDidMount() { + $('body').on('click', '.modal-back', function handleBackClick() { $(this).closest('.modal-dialog').removeClass('display--content'); }); - $('body').on('click', '.modal-header .close', function onClick() { + $('body').on('click', '.modal-header .close', function handleCloseClick() { setTimeout(function removeContent() { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); }); - }, - updateTab: function(tab) { + } + updateTab(tab) { this.setState({activeTab: tab, activeSection: ''}); - }, - updateSection: function(section) { + } + updateSection(section) { this.setState({activeSection: section}); - }, - getInitialState: function() { - return {activeTab: 'general', activeSection: ''}; - }, - render: function() { - var tabs = []; + } + render() { + let tabs = []; tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'}); tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'}); return ( - <div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'> - <div className='modal-dialog settings-modal'> - <div className='modal-content'> - <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' ref='title'>Team Settings</h4> - </div> - <div className='modal-body'> - <div className='settings-table'> - <div className='settings-links'> - <SettingsSidebar - tabs={tabs} - activeTab={this.state.activeTab} - updateTab={this.updateTab} - /> + <div + className='modal fade' + ref='modal' + id='team_settings' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog settings-modal'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + Team Settings + </h4> </div> - <div className='settings-content minimize-settings'> - <TeamSettings - activeTab={this.state.activeTab} - activeSection={this.state.activeSection} - updateSection={this.updateSection} - teamDisplayName={this.props.teamDisplayName} - /> + <div className='modal-body'> + <div className='settings-table'> + <div className='settings-links'> + <SettingsSidebar + tabs={tabs} + activeTab={this.state.activeTab} + updateTab={this.updateTab} + /> + </div> + <div className='settings-content minimize-settings'> + <TeamSettings + activeTab={this.state.activeTab} + activeSection={this.state.activeSection} + updateSection={this.updateSection} + teamDisplayName={this.props.teamDisplayName} + /> + </div> + </div> </div> </div> - </div> </div> - </div> </div> ); } -}); +} +TeamSettingsModal.propTypes = { + teamDisplayName: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/team_signup_allowed_domains_page.jsx b/web/react/components/team_signup_allowed_domains_page.jsx index 90c7ff668..aee5afd23 100644 --- a/web/react/components/team_signup_allowed_domains_page.jsx +++ b/web/react/components/team_signup_allowed_domains_page.jsx @@ -1,31 +1,34 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupAllowedDomainsPage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupAllowedDomainsPage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + + this.state = {}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'team_url'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - if (this.refs.open_network.getDOMNode().checked) { + if (React.findDOMNode(this.refs.open_network).checked) { this.props.state.wizard = 'send_invites'; this.props.state.team.type = 'O'; this.props.updateParent(this.props.state); return; } - if (this.refs.allow.getDOMNode().checked) { - var name = this.refs.name.getDOMNode().value.trim(); + if (React.findDOMNode(this.refs.allow).checked) { + var name = React.findDOMNode(this.refs.name).value.trim(); var domainRegex = /^\w+\.\w+$/; if (!name) { this.setState({nameError: 'This field is required'}); @@ -46,12 +49,9 @@ module.exports = React.createClass({ this.props.state.team.type = 'I'; this.props.updateParent(this.props.state); } - }, - getInitialState: function() { - return {}; - }, - render: function() { - client.track('signup', 'signup_team_04_allow_domains'); + } + render() { + Client.track('signup', 'signup_team_04_allow_domains'); var nameError = null; var nameDivClass = 'form-group'; @@ -63,11 +63,21 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h2>Email Domain</h2> <p> <div className='checkbox'> - <label><input type='checkbox' ref='allow' defaultChecked={true} />{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}</label> + <label> + <input + type='checkbox' + ref='allow' + defaultChecked={true} + /> + {' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'} + </label> </div> </p> <p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p> @@ -77,7 +87,16 @@ module.exports = React.createClass({ <div className='col-sm-9'> <div className='input-group'> <span className='input-group-addon'>@</span> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/> + <input + type='text' + ref='name' + className='form-control' + placeholder='' + maxLength='128' + defaultValue={this.props.state.team.allowed_domains} + autoFocus={true} + onFocus={this.handleFocus} + /> </div> </div> </div> @@ -86,13 +105,38 @@ module.exports = React.createClass({ <p>To allow signups from multiple domains, separate each with a comma.</p> <p> <div className='checkbox'> - <label><input type='checkbox' ref='open_network' defaultChecked={this.props.state.team.type === 'O'} /> Allow anyone to signup to this domain without an invitation.</label> + <label> + <input + type='checkbox' + ref='open_network' + defaultChecked={this.props.state.team.type === 'O'} + /> Allow anyone to signup to this domain without an invitation.</label> </div> </p> - <button type='button' className='btn btn-default' onClick={this.submitBack}><i className='glyphicon glyphicon-chevron-left'></i> Back</button> - <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <button + type='button' + className='btn btn-default' + onClick={this.submitBack} + > + <i className='glyphicon glyphicon-chevron-left'></i> Back + </button> + <button + type='submit' + className='btn-primary btn' + onClick={this.submitNext} + > + Next<i className='glyphicon glyphicon-chevron-right'></i> + </button> </form> </div> ); } -}); +} + +TeamSignupAllowedDomainsPage.defaultProps = { + state: {} +}; +TeamSignupAllowedDomainsPage.propTypes = { + state: React.PropTypes.object, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx index b5e93de1b..de756f4d5 100644 --- a/web/react/components/team_signup_display_name_page.jsx +++ b/web/react/components/team_signup_display_name_page.jsx @@ -4,21 +4,24 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupDisplayNamePage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupDisplayNamePage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + + this.state = {}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'welcome'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - var displayName = this.refs.name.getDOMNode().value.trim(); + var displayName = React.findDOMNode(this.refs.name).value.trim(); if (!displayName) { this.setState({nameError: 'This field is required'}); return; @@ -28,15 +31,12 @@ module.exports = React.createClass({ this.props.state.team.display_name = displayName; this.props.state.team.name = utils.cleanUpUrlable(displayName); this.props.updateParent(this.props.state); - }, - getInitialState: function() { - return {}; - }, - handleFocus: function(e) { + } + handleFocus(e) { e.preventDefault(); e.currentTarget.select(); - }, - render: function() { + } + render() { client.track('signup', 'signup_team_02_name'); var nameError = null; @@ -49,24 +49,48 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' /> <h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2> <div className={nameDivClass}> <div className='row'> <div className='col-sm-9'> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} /> + <input + type='text' + ref='name' + className='form-control' + placeholder='' + maxLength='128' + defaultValue={this.props.state.team.display_name} + autoFocus={true} + onFocus={this.handleFocus} /> </div> </div> {nameError} </div> <div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div> - <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <button + type='submit' + className='btn btn-primary margin--extra' + onClick={this.submitNext} > + Next<i className='glyphicon glyphicon-chevron-right'></i> + </button> <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> + <a + href='#' + onClick={this.submitBack}> + Back to previous step + </a> </div> </form> </div> ); } -}); +} + +TeamSignupDisplayNamePage.propTypes = { + state: React.PropTypes.object, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx index 11cd17e74..10bb2d69e 100644 --- a/web/react/components/team_signup_email_item.jsx +++ b/web/react/components/team_signup_email_item.jsx @@ -1,28 +1,28 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); - -module.exports = React.createClass({ - displayName: 'TeamSignupEmailItem', - propTypes: { - focus: React.PropTypes.bool, - email: React.PropTypes.string - }, - getInitialState: function() { - return {}; - }, - getValue: function() { - return this.refs.email.getDOMNode().value.trim(); - }, - validate: function(teamEmail) { - var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); +const Utils = require('../utils/utils.jsx'); + +export default class TeamSignupEmailItem extends React.Component { + constructor(props) { + super(props); + + this.getValue = this.getValue.bind(this); + this.validate = this.validate.bind(this); + + this.state = {}; + } + getValue() { + return React.findDOMNode(this.refs.email).value.trim(); + } + validate(teamEmail) { + const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); if (!email) { return true; } - if (!utils.isEmail(email)) { + if (!Utils.isEmail(email)) { this.state.emailError = 'Please enter a valid email address'; this.setState(this.state); return false; @@ -31,13 +31,14 @@ module.exports = React.createClass({ this.setState(this.state); return false; } + this.state.emailError = ''; this.setState(this.state); return true; - }, - render: function() { - var emailError = null; - var emailDivClass = 'form-group'; + } + render() { + let emailError = null; + let emailDivClass = 'form-group'; if (this.state.emailError) { emailError = <label className='control-label'>{this.state.emailError}</label>; emailDivClass += ' has-error'; @@ -45,9 +46,22 @@ module.exports = React.createClass({ return ( <div className={emailDivClass}> - <input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' /> + <input + autoFocus={this.props.focus} + type='email' + ref='email' + className='form-control' + placeholder='Email Address' + defaultValue={this.props.email} + maxLength='128' + /> {emailError} </div> ); } -}); +} + +TeamSignupEmailItem.propTypes = { + focus: React.PropTypes.bool, + email: React.PropTypes.string +}; diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index 6b21915f6..f36ec1119 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -1,24 +1,28 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); +var BrowserStore = require('../stores/browser_store.jsx'); +var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupPasswordPage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupPasswordPage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + + this.state = {}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'username'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - var password = this.refs.password.getDOMNode().value.trim(); + var password = React.findDOMNode(this.refs.password).value.trim(); if (!password || password.length < 5) { this.setState({passwordError: 'Please enter at least 5 characters'}); return; @@ -31,17 +35,16 @@ module.exports = React.createClass({ teamSignup.user.allow_marketing = true; delete teamSignup.wizard; - client.createTeamFromSignup(teamSignup, + Client.createTeamFromSignup(teamSignup, function success() { - client.track('signup', 'signup_team_08_complete'); + Client.track('signup', 'signup_team_08_complete'); var props = this.props; - - client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password, - function(data) { + Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password, + function loginSuccess() { UserStore.setLastEmail(teamSignup.team.email); - UserStore.setCurrentUser(teamSignup.user); + UserStore.setCurrentUser(data); if (this.props.hash > 0) { BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); } @@ -52,7 +55,7 @@ module.exports = React.createClass({ window.location.href = '/'; }.bind(this), - function(err) { + function loginFail(err) { if (err.message === 'Login failed because email address has not been verified') { window.location.href = '/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name); } else { @@ -67,12 +70,9 @@ module.exports = React.createClass({ $('#finish-button').button('reset'); }.bind(this) ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - client.track('signup', 'signup_team_07_password'); + } + render() { + Client.track('signup', 'signup_team_07_password'); var passwordError = null; var passwordDivStyle = 'form-group'; @@ -89,7 +89,10 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h2 className='margin--less'>Your password</h2> <h5 className='color--light'>Select a password that you'll use to login with your email address:</h5> <div className='inner__content margin--extra'> @@ -99,7 +102,14 @@ module.exports = React.createClass({ <div className='row'> <div className='col-sm-11'> <h5><strong>Choose your password</strong></h5> - <input autoFocus={true} type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + <input + autoFocus={true} + type='password' + ref='password' + className='form-control' + placeholder='' + maxLength='128' + /> <div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div> </div> </div> @@ -108,14 +118,37 @@ module.exports = React.createClass({ </div> </div> <div className='form-group'> - <button type='submit' className='btn btn-primary margin--extra' id='finish-button' data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button> + <button + type='submit' + className='btn btn-primary margin--extra' + id='finish-button' + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} + onClick={this.submitNext} + > + Finish + </button> </div> <p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p> <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> + <a + href='#' + onClick={this.submitBack} + > + Back to previous step + </a> </div> </form> </div> ); } -}); +} + +TeamSignupPasswordPage.defaultProps = { + state: {}, + hash: '' +}; +TeamSignupPasswordPage.propTypes = { + state: React.PropTypes.object, + hash: React.PropTypes.string, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 646a742ba..6d9b0ec03 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -2,9 +2,9 @@ // See License.txt for license information. var EmailItem = require('./team_signup_email_item.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var ConfigStore = require('../stores/config_store.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); export default class TeamSignupSendInvitesPage extends React.Component { constructor(props) { @@ -71,7 +71,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } keySubmit(e) { if (e && e.keyCode === 13) { - this.submitNext(e) + this.submitNext(e); } } componentWillMount() { @@ -92,7 +92,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } } render() { - client.track('signup', 'signup_team_05_send_invites'); + Client.track('signup', 'signup_team_05_send_invites'); var content = null; var bottomContent = null; @@ -165,7 +165,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2> + <h2>{'Invite ' + Utils.toTitleCase(strings.Team) + ' Members'}</h2> {content} <div className='form-group'> <button @@ -190,6 +190,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { ); } } + TeamSignupSendInvitesPage.propTypes = { state: React.PropTypes.object.isRequired, updateParent: React.PropTypes.func.isRequired diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index beef725e2..2ea6c3680 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -1,33 +1,37 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var constants = require('../utils/constants.jsx'); - -module.exports = React.createClass({ - displayName: 'TeamSignupURLPage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); +const Constants = require('../utils/constants.jsx'); + +export default class TeamSignupUrlPage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + this.handleFocus = this.handleFocus.bind(this); + + this.state = {nameError: ''}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'team_display_name'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - var name = this.refs.name.getDOMNode().value.trim(); + const name = React.findDOMNode(this.refs.name).value.trim(); if (!name) { this.setState({nameError: 'This field is required'}); return; } - var cleanedName = utils.cleanUpUrlable(name); + const cleanedName = Utils.cleanUpUrlable(name); - var urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g; + const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g; if (cleanedName !== name || !urlRegex.test(name)) { this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."}); return; @@ -36,14 +40,14 @@ module.exports = React.createClass({ return; } - for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) { - if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) { + for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) { + if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) { this.setState({nameError: 'This team name is unavailable'}); return; } } - client.findTeamByName(name, + Client.findTeamByName(name, function success(data) { if (!data) { if (config.AllowSignupDomainsWizard) { @@ -65,55 +69,88 @@ module.exports = React.createClass({ this.setState(this.state); }.bind(this) ); - }, - getInitialState: function() { - return {}; - }, - handleFocus: function(e) { + } + handleFocus(e) { e.preventDefault(); e.currentTarget.select(); - }, - render: function() { + } + render() { $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); - client.track('signup', 'signup_team_03_url'); + Client.track('signup', 'signup_team_03_url'); - var nameError = null; - var nameDivClass = 'form-group'; + let nameError = null; + let nameDivClass = 'form-group'; if (this.state.nameError) { nameError = <label className='control-label'>{this.state.nameError}</label>; nameDivClass += ' has-error'; } + const title = `${Utils.getWindowLocationOrigin()}/`; + return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{utils.toTitleCase(strings.Team) + ' URL'}</h2> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> + <h2>{`${Utils.toTitleCase(strings.Team)} URL`}</h2> <div className={nameDivClass}> <div className='row'> <div className='col-sm-11'> <div className='input-group input-group--limit'> - <span data-toggle='tooltip' title={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/> + <span + data-toggle='tooltip' + title={title} + className='input-group-addon' + > + {title} + </span> + <input + type='text' + ref='name' + className='form-control' + placeholder='' + maxLength='128' + defaultValue={this.props.state.team.name} + autoFocus={true} + onFocus={this.handleFocus} + /> </div> </div> </div> {nameError} </div> - <p>{'Choose the web address of your new ' + strings.Team + ':'}</p> + <p>{`Choose the web address of your new ${strings.Team}:`}</p> <ul className='color--light'> <li>Short and memorable is best</li> <li>Use lowercase letters, numbers and dashes</li> <li>Must start with a letter and can't end in a dash</li> </ul> - <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <button + type='submit' + className='btn btn-primary margin--extra' + onClick={this.submitNext} + > + Next<i className='glyphicon glyphicon-chevron-right'></i> + </button> <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> + <a + href='#' + onClick={this.submitBack} + > + Back to previous step + </a> </div> </form> </div> ); } -}); +} + +TeamSignupUrlPage.propTypes = { + state: React.PropTypes.object, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index 56882e6a1..8efcd87e1 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -1,26 +1,29 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +var Utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupUsernamePage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupUsernamePage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + + this.state = {}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'send_invites'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - var name = this.refs.name.getDOMNode().value.trim(); + var name = React.findDOMNode(this.refs.name).value.trim(); - var usernameError = utils.isValidUsername(name); + var usernameError = Utils.isValidUsername(name); if (usernameError === 'Cannot use a reserved word as a username.') { this.setState({nameError: 'This username is reserved, please choose a new one.'}); return; @@ -32,12 +35,9 @@ module.exports = React.createClass({ this.props.state.wizard = 'password'; this.props.state.user.username = name; this.props.updateParent(this.props.state); - }, - getInitialState: function() { - return {}; - }, - render: function() { - client.track('signup', 'signup_team_06_username'); + } + render() { + Client.track('signup', 'signup_team_06_username'); var nameError = null; var nameDivClass = 'form-group'; @@ -49,7 +49,10 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h2 className='margin--less'>Your username</h2> <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5> <div className='inner__content margin--extra'> @@ -57,19 +60,47 @@ module.exports = React.createClass({ <div className='row'> <div className='col-sm-11'> <h5><strong>Choose your username</strong></h5> - <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' /> + <input + autoFocus={true} + type='text' + ref='name' + className='form-control' + placeholder='' + defaultValue={this.props.state.user.username} + maxLength='128' + /> <div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div> </div> </div> {nameError} </div> </div> - <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <button + type='submit' + className='btn btn-primary margin--extra' + onClick={this.submitNext} + > + Next + <i className='glyphicon glyphicon-chevron-right'></i> + </button> <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> + <a + href='#' + onClick={this.submitBack} + > + Back to previous step + </a> </div> </form> </div> ); } -}); +} + +TeamSignupUsernamePage.defaultProps = { + state: null +}; +TeamSignupUsernamePage.propTypes = { + state: React.PropTypes.object, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index f0c680bd8..e742cba2f 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -1,17 +1,22 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +var Utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupWelcomePage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitNext: function(e) { +export default class TeamSignupWelcomePage extends React.Component { + constructor(props) { + super(props); + + this.submitNext = this.submitNext.bind(this); + this.handleDiffEmail = this.handleDiffEmail.bind(this); + this.handleDiffSubmit = this.handleDiffSubmit.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + + this.state = {useDiff: false}; + } + submitNext(e) { if (!BrowserStore.isLocalStorageSupported()) { this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'}); return; @@ -19,18 +24,18 @@ module.exports = React.createClass({ e.preventDefault(); this.props.state.wizard = 'team_display_name'; this.props.updateParent(this.props.state); - }, - handleDiffEmail: function(e) { + } + handleDiffEmail(e) { e.preventDefault(); this.setState({useDiff: true}); - }, - handleDiffSubmit: function(e) { + } + handleDiffSubmit(e) { e.preventDefault(); var state = {useDiff: true, serverError: ''}; - var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); - if (!email || !utils.isEmail(email)) { + var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + if (!email || !Utils.isEmail(email)) { state.emailError = 'Please enter a valid email address'; this.setState(state); return; @@ -41,7 +46,7 @@ module.exports = React.createClass({ } state.emailError = ''; - client.signupTeam(email, + Client.signupTeam(email, function success(data) { if (data.follow_link) { window.location.href = data.follow_link; @@ -56,23 +61,20 @@ module.exports = React.createClass({ this.setState(this.state); }.bind(this) ); - }, - getInitialState: function() { - return {useDiff: false}; - }, - handleKeyPress: function(event) { + } + handleKeyPress(event) { if (event.keyCode === 13) { this.submitNext(event); } - }, - componentWillMount: function() { + } + componentWillMount() { document.addEventListener('keyup', this.handleKeyPress, false); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { document.removeEventListener('keyup', this.handleKeyPress, false); - }, - render: function() { - client.track('signup', 'signup_team_01_welcome'); + } + render() { + Client.track('signup', 'signup_team_01_welcome'); var storageError = null; if (this.state.storageError) { @@ -105,7 +107,10 @@ module.exports = React.createClass({ return ( <div> <p> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h3 className='sub-heading'>Welcome to:</h3> <h1 className='margin--top-none'>{config.SiteName}</h1> </p> @@ -121,7 +126,14 @@ module.exports = React.createClass({ You can add other administrators later. </p> <div className='form-group'> - <button className='btn-primary btn form-group' type='submit' onClick={this.submitNext}><i className='glyphicon glyphicon-ok'></i>Yes, this address is correct</button> + <button + className='btn-primary btn form-group' + type='submit' + onClick={this.submitNext} + > + <i className='glyphicon glyphicon-ok'></i> + Yes, this address is correct + </button> {storageError} </div> <hr /> @@ -129,16 +141,42 @@ module.exports = React.createClass({ <div className={emailDivClass}> <div className='row'> <div className='col-sm-9'> - <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' /> + <input + type='email' + ref='email' + className='form-control' + placeholder='Email Address' + maxLength='128' + /> </div> </div> {emailError} </div> {serverError} - <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit}>Use this instead</button> + <button + className='btn btn-md btn-primary' + type='button' + onClick={this.handleDiffSubmit} + > + Use this instead + </button> </div> - <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a> + <a + href='#' + onClick={this.handleDiffEmail} + className={differentEmailLinkClass} + > + Use a different email + </a> </div> ); } -}); +} + +TeamSignupWelcomePage.defaultProps = { + state: {} +}; +TeamSignupWelcomePage.propTypes = { + updateParent: React.PropTypes.func.isRequired, + state: React.PropTypes.object +}; diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx index c7204880f..c0bbb7da9 100644 --- a/web/react/components/team_signup_with_email.jsx +++ b/web/react/components/team_signup_with_email.jsx @@ -1,8 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); +const Utils = require('../utils/utils.jsx'); +const Client = require('../utils/client.jsx'); export default class EmailSignUpPage extends React.Component { constructor() { @@ -14,11 +14,11 @@ export default class EmailSignUpPage extends React.Component { } handleSubmit(e) { e.preventDefault(); - var team = {}; - var state = {serverError: ''}; + let team = {}; + let state = {serverError: ''}; - team.email = this.refs.email.getDOMNode().value.trim().toLowerCase(); - if (!team.email || !utils.isEmail(team.email)) { + team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + if (!team.email || !Utils.isEmail(team.email)) { state.emailError = 'Please enter a valid email address'; state.inValid = true; } else { @@ -30,12 +30,12 @@ export default class EmailSignUpPage extends React.Component { return; } - client.signupTeam(team.email, + Client.signupTeam(team.email, function success(data) { if (data.follow_link) { window.location.href = data.follow_link; } else { - window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email); + window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`; } }, function fail(err) { @@ -69,7 +69,7 @@ export default class EmailSignUpPage extends React.Component { </button> </div> <div className='form-group margin--extra-2x'> - <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + <span><a href='/find_team'>{`Find my ${strings.Team}`}</a></span> </div> </form> ); diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index efd2dd810..0408a262d 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -1,66 +1,93 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var PostStore = require('../stores/post_store.jsx'); -var CommandList = require('./command_list.jsx'); -var ErrorStore = require('../stores/error_store.jsx'); -var AsyncClient = require('../utils/async_client.jsx'); - -var utils = require('../utils/utils.jsx'); -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; +const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +const PostStore = require('../stores/post_store.jsx'); +const CommandList = require('./command_list.jsx'); +const ErrorStore = require('../stores/error_store.jsx'); +const AsyncClient = require('../utils/async_client.jsx'); + +const Utils = require('../utils/utils.jsx'); +const Constants = require('../utils/constants.jsx'); +const ActionTypes = Constants.ActionTypes; + +export default class Textbox extends React.Component { + constructor(props) { + super(props); + + this.getStateFromStores = this.getStateFromStores.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + this.onRecievedError = this.onRecievedError.bind(this); + this.onTimerInterrupt = this.onTimerInterrupt.bind(this); + this.updateMentionTab = this.updateMentionTab.bind(this); + this.handleChange = this.handleChange.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleBackspace = this.handleBackspace.bind(this); + this.checkForNewMention = this.checkForNewMention.bind(this); + this.addMention = this.addMention.bind(this); + this.addCommand = this.addCommand.bind(this); + this.resize = this.resize.bind(this); + this.handleFocus = this.handleFocus.bind(this); + this.handleBlur = this.handleBlur.bind(this); + this.handlePaste = this.handlePaste.bind(this); + + this.state = { + mentionText: '-1', + mentions: [], + connection: '', + timerInterrupt: null + }; + + this.caret = -1; + this.addedMention = false; + this.doProcessMentions = false; + this.mentions = []; + } + getStateFromStores() { + const error = ErrorStore.getLastError(); -function getStateFromStores() { - var error = ErrorStore.getLastError(); + if (error) { + return {message: error.message}; + } - if (error) { - return {message: error.message}; + return {message: null}; } - return {message: null}; -} - -module.exports = React.createClass({ - displayName: 'Textbox', - caret: -1, - addedMention: false, - doProcessMentions: false, - mentions: [], - componentDidMount: function() { + componentDidMount() { PostStore.addAddMentionListener(this.onListenerChange); ErrorStore.addChangeListener(this.onRecievedError); this.resize(); this.updateMentionTab(null); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { PostStore.removeAddMentionListener(this.onListenerChange); ErrorStore.removeChangeListener(this.onRecievedError); - }, - onListenerChange: function(id, username) { + } + onListenerChange(id, username) { if (id === this.props.id) { this.addMention(username); } - }, - onRecievedError: function() { - var errorState = getStateFromStores(); + } + onRecievedError() { + const errorState = this.getStateFromStores(); - if (this.state.timerInterrupt != null) { + if (this.state.timerInterrupt !== null) { window.clearInterval(this.state.timerInterrupt); this.setState({timerInterrupt: null}); } if (errorState.message === 'There appears to be a problem with your internet connection') { this.setState({connection: 'bad-connection'}); - var timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000); + const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000); this.setState({timerInterrupt: timerInterrupt}); } else { this.setState({connection: ''}); } - }, - onTimerInterrupt: function() { - //Since these should only happen when you have no connection and slightly briefly after any - //performance hit should not matter + } + onTimerInterrupt() { + // Since these should only happen when you have no connection and slightly briefly after any + // performance hit should not matter if (this.state.connection === 'bad-connection') { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ERROR, @@ -72,10 +99,10 @@ module.exports = React.createClass({ window.clearInterval(this.state.timerInterrupt); this.setState({timerInterrupt: null}); - }, - componentDidUpdate: function() { + } + componentDidUpdate() { if (this.caret >= 0) { - utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret); + Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret); this.caret = -1; } if (this.doProcessMentions) { @@ -83,40 +110,35 @@ module.exports = React.createClass({ this.doProcessMentions = false; } this.resize(); - }, - componentWillReceiveProps: function(nextProps) { + } + componentWillReceiveProps(nextProps) { if (!this.addedMention) { this.checkForNewMention(nextProps.messageText); } - var text = this.refs.message.getDOMNode().value; + const text = React.findDOMNode(this.refs.message).value; if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) { this.doProcessMentions = true; } this.addedMention = false; this.refs.commands.getSuggestedCommands(nextProps.messageText); this.resize(); - }, - getInitialState: function() { - return {mentionText: '-1', mentions: [], connection: '', timerInterrupt: null}; - }, - updateMentionTab: function(mentionText) { - var self = this; - + } + updateMentionTab(mentionText) { // using setTimeout so dispatch isn't called during an in progress dispatch - setTimeout(function() { + setTimeout(function updateMentionTabAfterTimeout() { AppDispatcher.handleViewAction({ type: ActionTypes.RECIEVED_MENTION_DATA, - id: self.props.id, + id: this.props.id, mention_text: mentionText }); - }, 1); - }, - handleChange: function() { - this.props.onUserInput(this.refs.message.getDOMNode().value); + }.bind(this), 1); + } + handleChange() { + this.props.onUserInput(React.findDOMNode(this.refs.message).value); this.resize(); - }, - handleKeyPress: function(e) { - var text = this.refs.message.getDOMNode().value; + } + handleKeyPress(e) { + const text = React.findDOMNode(this.refs.message).value; if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) { this.refs.commands.addFirstCommand(); @@ -125,10 +147,10 @@ module.exports = React.createClass({ } if (!this.doProcessMentions) { - var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); - var preText = text.substring(0, caret); - var lastSpace = preText.lastIndexOf(' '); - var lastAt = preText.lastIndexOf('@'); + const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const preText = text.substring(0, caret); + const lastSpace = preText.lastIndexOf(' '); + const lastAt = preText.lastIndexOf('@'); if (caret > lastAt && lastSpace < lastAt) { this.doProcessMentions = true; @@ -136,18 +158,18 @@ module.exports = React.createClass({ } this.props.onKeyPress(e); - }, - handleKeyDown: function(e) { - if (utils.getSelectedText(this.refs.message.getDOMNode()) !== '') { + } + handleKeyDown(e) { + if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') { this.doProcessMentions = true; } if (e.keyCode === 8) { this.handleBackspace(e); } - }, - handleBackspace: function() { - var text = this.refs.message.getDOMNode().value; + } + handleBackspace() { + const text = React.findDOMNode(this.refs.message).value; if (text.indexOf('/') === 0) { this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1)); } @@ -156,21 +178,21 @@ module.exports = React.createClass({ return; } - var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); - var preText = text.substring(0, caret); - var lastSpace = preText.lastIndexOf(' '); - var lastAt = preText.lastIndexOf('@'); + const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); + const preText = text.substring(0, caret); + const lastSpace = preText.lastIndexOf(' '); + const lastAt = preText.lastIndexOf('@'); if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) { this.doProcessMentions = true; } - }, - checkForNewMention: function(text) { - var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); + } + checkForNewMention(text) { + const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); - var preText = text.substring(0, caret); + const preText = text.substring(0, caret); - var atIndex = preText.lastIndexOf('@'); + const atIndex = preText.lastIndexOf('@'); // The @ character not typed, so nothing to do. if (atIndex === -1) { @@ -178,8 +200,8 @@ module.exports = React.createClass({ return; } - var lastCharSpace = preText.lastIndexOf(String.fromCharCode(160)); - var lastSpace = preText.lastIndexOf(' '); + const lastCharSpace = preText.lastIndexOf(String.fromCharCode(160)); + const lastSpace = preText.lastIndexOf(' '); // If there is a space after the last @, nothing to do. if (lastSpace > atIndex || lastCharSpace > atIndex) { @@ -188,43 +210,43 @@ module.exports = React.createClass({ } // Get the name typed so far. - var name = preText.substring(atIndex + 1, preText.length).toLowerCase(); + const name = preText.substring(atIndex + 1, preText.length).toLowerCase(); this.updateMentionTab(name); - }, - addMention: function(name) { - var caret = utils.getCaretPosition(this.refs.message.getDOMNode()); + } + addMention(name) { + const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); - var text = this.props.messageText; + const text = this.props.messageText; - var preText = text.substring(0, caret); + const preText = text.substring(0, caret); - var atIndex = preText.lastIndexOf('@'); + const atIndex = preText.lastIndexOf('@'); // The @ character not typed, so nothing to do. if (atIndex === -1) { return; } - var prefix = text.substring(0, atIndex); - var suffix = text.substring(caret, text.length); + const prefix = text.substring(0, atIndex); + const suffix = text.substring(caret, text.length); this.caret = prefix.length + name.length + 2; this.addedMention = true; this.doProcessMentions = true; - this.props.onUserInput(prefix + '@' + name + ' ' + suffix); - }, - addCommand: function(cmd) { - var elm = this.refs.message.getDOMNode(); + this.props.onUserInput(`${prefix}@${name} ${suffix}`); + } + addCommand(cmd) { + const elm = React.findDOMNode(this.refs.message); elm.value = cmd; this.handleChange(); - }, - resize: function() { - var e = this.refs.message.getDOMNode(); - var w = this.refs.wrapper.getDOMNode(); + } + resize() { + const e = React.findDOMNode(this.refs.message); + const w = React.findDOMNode(this.refs.wrapper); - var lht = parseInt($(e).css('lineHeight'), 10); - var lines = e.scrollHeight / lht; - var mod = 15; + const lht = parseInt($(e).css('lineHeight'), 10); + const lines = e.scrollHeight / lht; + let mod = 15; if (lines < 2.5 || this.props.messageText === '') { mod = 30; @@ -237,28 +259,62 @@ module.exports = React.createClass({ $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167); $(w).css({height: 'auto'}).height(167); } - }, - handleFocus: function() { - var elm = this.refs.message.getDOMNode(); + } + handleFocus() { + const elm = React.findDOMNode(this.refs.message); if (elm.title === elm.value) { elm.value = ''; } - }, - handleBlur: function() { - var elm = this.refs.message.getDOMNode(); + } + handleBlur() { + const elm = React.findDOMNode(this.refs.message); if (elm.value === '') { elm.value = elm.title; } - }, - handlePaste: function() { + } + handlePaste() { this.doProcessMentions = true; - }, - render: function() { + } + render() { return ( - <div ref='wrapper' className='textarea-wrapper'> - <CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} /> - <textarea id={this.props.id} ref='message' className={'form-control custom-textarea ' + this.state.connection} spellCheck='true' autoComplete='off' autoCorrect='off' rows='1' maxLength={Constants.MAX_POST_LEN} placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} /> + <div + ref='wrapper' + className='textarea-wrapper' + > + <CommandList + ref='commands' + addCommand={this.addCommand} + channelId={this.props.channelId} + /> + <textarea + id={this.props.id} + ref='message' + className={`form-control custom-textarea ${this.state.connection}`} + spellCheck='true' + autoComplete='off' + autoCorrect='off' + rows='1' + maxLength={Constants.MAX_POST_LEN} + placeholder={this.props.createMessage} + value={this.props.messageText} + onInput={this.handleChange} + onChange={this.handleChange} + onKeyPress={this.handleKeyPress} + onKeyDown={this.handleKeyDown} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + onPaste={this.handlePaste} + /> </div> ); } -}); +} + +Textbox.propTypes = { + id: React.PropTypes.string.isRequired, + channelId: React.PropTypes.string, + messageText: React.PropTypes.string.isRequired, + onUserInput: React.PropTypes.func.isRequired, + onKeyPress: React.PropTypes.func.isRequired, + createMessage: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 5c4d26a23..0d10166f3 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -1,20 +1,9 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var UserStore = require('../stores/user_store.jsx'); -function getStateFromStores(userId) { - var profile = UserStore.getProfile(userId); - - if (profile == null) { - return { profile: { id: "0", username: "..."} }; - } else { - return { profile: profile }; - } -} - var id = 0; function nextId() { @@ -22,49 +11,76 @@ function nextId() { return id; } +export default class UserProfile extends React.Component { + constructor(props) { + super(props); + + this.uniqueId = nextId(); + + this.state = this.getStateFromStores(this.props.userId); + } + getStateFromStores(userId) { + var profile = UserStore.getProfile(userId); + + if (profile == null) { + return {profile: {id: '0', username: '...'}}; + } -module.exports = React.createClass({ - uniqueId: null, - componentDidMount: function() { - UserStore.addChangeListener(this._onChange); - $("#profile_" + this.uniqueId).popover({placement : 'right', container: 'body', trigger: 'hover', html: true, delay: { "show": 200, "hide": 100 }}); - $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} ); - }, - componentWillUnmount: function() { - UserStore.removeChangeListener(this._onChange); - }, - _onChange: function(id) { - if (id == this.props.userId) { - var newState = getStateFromStores(this.props.userId); - if (!utils.areStatesEqual(newState, this.state)) { + return {profile: profile}; + } + componentDidMount() { + UserStore.addChangeListener(this.onChange); + $('#profile_' + this.uniqueId).popover({placement: 'right', container: 'body', trigger: 'hover', html: true, delay: {show: 200, hide: 100}}); + $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'}); + } + componentWillUnmount() { + UserStore.removeChangeListener(this.onChange); + } + onChange(userId) { + if (userId === this.props.userId) { + var newState = this.getStateFromStores(this.props.userId); + if (!Utils.areStatesEqual(newState, this.state)) { this.setState(newState); } } - }, - componentWillReceiveProps: function(nextProps) { - if (this.props.userId != nextProps.userId) { - this.setState(getStateFromStores(nextProps.userId)); + } + componentWillReceiveProps(nextProps) { + if (this.props.userId !== nextProps.userId) { + this.setState(this.getStateFromStores(nextProps.userId)); + } + } + render() { + var name = this.state.profile.username; + if (this.props.overwriteName) { + name = this.props.overwriteName; } - }, - getInitialState: function() { - this.uniqueId = nextId(); - return getStateFromStores(this.props.userId); - }, - render: function() { - var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username; - - var data_content = "<img class='user-popover__image' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />"; + var dataContent = '<img class="user-popover__image" src="/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '" height="128" width="128" />'; if (!config.ShowEmail) { - data_content += "<div class='text-nowrap'>Email not shared</div>"; + dataContent += '<div class="text-nowrap">Email not shared</div>'; } else { - data_content += "<div data-toggle='tooltip' title= '" + this.state.profile.email + "'><a href='mailto:" + this.state.profile.email + "' class='text-nowrap text-lowercase user-popover__email'>" + this.state.profile.email + "</a></div>"; + dataContent += '<div data-toggle="tooltip" title="' + this.state.profile.email + '"><a href="mailto:' + this.state.profile.email + '" class="text-nowrap text-lowercase user-popover__email">' + this.state.profile.email + '</a></div>'; } return ( - <div className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} > - { name } + <div + className='user-popover' + id={'profile_' + this.uniqueId} + data-toggle='popover' + data-content={dataContent} + data-original-title={this.state.profile.username} + > + {name} </div> ); } -}); +} + +UserProfile.defaultProps = { + userId: '', + overwriteName: '' +}; +UserProfile.propTypes = { + userId: React.PropTypes.string, + overwriteName: React.PropTypes.string +}; diff --git a/web/react/components/user_settings_appearance.jsx b/web/react/components/user_settings_appearance.jsx index ba2d97ea8..878ec42fc 100644 --- a/web/react/components/user_settings_appearance.jsx +++ b/web/react/components/user_settings_appearance.jsx @@ -4,100 +4,133 @@ var UserStore = require('../stores/user_store.jsx'); var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); -var client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - submitTheme: function(e) { +export default class UserSettingsAppearance extends React.Component { + constructor(props) { + super(props); + + this.submitTheme = this.submitTheme.bind(this); + this.updateTheme = this.updateTheme.bind(this); + this.handleClose = this.handleClose.bind(this); + + this.state = this.getStateFromStores(); + } + getStateFromStores() { + var user = UserStore.getCurrentUser(); + var theme = '#2389d7'; + if (config.ThemeColors != null) { + theme = config.ThemeColors[0]; + } + if (user.props && user.props.theme) { + theme = user.props.theme; + } + + return {theme: theme.toLowerCase()}; + } + submitTheme(e) { e.preventDefault(); var user = UserStore.getCurrentUser(); - if (!user.props) user.props = {}; + if (!user.props) { + user.props = {}; + } user.props.theme = this.state.theme; - client.updateUser(user, - function(data) { - this.props.updateSection(""); + Client.updateUser(user, + function success() { + this.props.updateSection(''); window.location.reload(); }.bind(this), - function(err) { - state = this.getInitialState(); - state.server_error = err; + function fail(err) { + var state = this.getStateFromStores(); + state.serverError = err; this.setState(state); }.bind(this) ); - }, - updateTheme: function(e) { - var hex = utils.rgb2hex(e.target.style.backgroundColor); - this.setState({ theme: hex.toLowerCase() }); - }, - handleClose: function() { - this.setState({server_error: null}); + } + updateTheme(e) { + var hex = Utils.rgb2hex(e.target.style.backgroundColor); + this.setState({theme: hex.toLowerCase()}); + } + handleClose() { + this.setState({serverError: null}); this.props.updateTab('general'); - }, - componentDidMount: function() { - if (this.props.activeSection === "theme") { - $(this.refs[this.state.theme].getDOMNode()).addClass('active-border'); + } + componentDidMount() { + if (this.props.activeSection === 'theme') { + $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } $('#user_settings').on('hidden.bs.modal', this.handleClose); - }, - componentDidUpdate: function() { - if (this.props.activeSection === "theme") { + } + componentDidUpdate() { + if (this.props.activeSection === 'theme') { $('.color-btn').removeClass('active-border'); - $(this.refs[this.state.theme].getDOMNode()).addClass('active-border'); + $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); } - }, - componentWillUnmount: function() { + } + componentWillUnmount() { $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); - }, - getInitialState: function() { - var user = UserStore.getCurrentUser(); - var theme = config.ThemeColors != null ? config.ThemeColors[0] : "#2389d7"; - if (user.props && user.props.theme) { - theme = user.props.theme; + } + render() { + var serverError; + if (this.state.serverError) { + serverError = this.state.serverError; } - return { theme: theme.toLowerCase() }; - }, - render: function() { - var server_error = this.state.server_error ? this.state.server_error : null; - var themeSection; var self = this; if (config.ThemeColors != null) { if (this.props.activeSection === 'theme') { - var theme_buttons = []; + var themeButtons = []; for (var i = 0; i < config.ThemeColors.length; i++) { - theme_buttons.push(<button ref={config.ThemeColors[i]} type="button" className="btn btn-lg color-btn" style={{backgroundColor: config.ThemeColors[i]}} onClick={this.updateTheme} />); + themeButtons.push( + <button + ref={config.ThemeColors[i]} + type='button' + className='btn btn-lg color-btn' + style={{backgroundColor: config.ThemeColors[i]}} + onClick={this.updateTheme} + /> + ); } var inputs = []; inputs.push( - <li className="setting-list-item"> - <div className="btn-group" data-toggle="buttons-radio"> - { theme_buttons } + <li className='setting-list-item'> + <div + className='btn-group' + data-toggle='buttons-radio' + > + {themeButtons} </div> </li> ); themeSection = ( <SettingItemMax - title="Theme Color" + title='Theme Color' inputs={inputs} submit={this.submitTheme} - server_error={server_error} - updateSection={function(e){self.props.updateSection("");e.preventDefault;}} + serverError={serverError} + updateSection={function updateSection(e) { + self.props.updateSection(''); + e.preventDefault(); + }} /> ); } else { themeSection = ( <SettingItemMin - title="Theme Color" + title='Theme Color' describe={this.state.theme} - updateSection={function(){self.props.updateSection("theme");}} + updateSection={function updateSection() { + self.props.updateSection('theme'); + }} /> ); } @@ -105,17 +138,38 @@ module.exports = React.createClass({ return ( <div> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" ref="title"><i className="modal-back"></i>Appearance Settings</h4> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <i className='modal-back'></i>Appearance Settings + </h4> </div> - <div className="user-settings"> - <h3 className="tab-header">Appearance Settings</h3> - <div className="divider-dark first"/> + <div className='user-settings'> + <h3 className='tab-header'>Appearance Settings</h3> + <div className='divider-dark first'/> {themeSection} - <div className="divider-dark"/> + <div className='divider-dark'/> </div> </div> ); } -}); +} + +UserSettingsAppearance.defaultProps = { + activeSection: '' +}; +UserSettingsAppearance.propTypes = { + activeSection: React.PropTypes.string, + updateSection: React.PropTypes.func, + updateTab: React.PropTypes.func +}; diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index ddd2fb607..ead7ac1d5 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -194,7 +194,7 @@ export default class UserSettingsGeneralTab extends React.Component { this.props.updateSection(section); } handleClose() { - $(this.getDOMNode()).find('.form-control').each(function clearForms() { + $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { this.value = ''; }); @@ -230,7 +230,6 @@ export default class UserSettingsGeneralTab extends React.Component { } var nameSection; - var self = this; var inputs = []; if (this.props.activeSection === 'name') { @@ -276,9 +275,9 @@ export default class UserSettingsGeneralTab extends React.Component { server_error={serverError} client_error={clientError} updateSection={function clearSection(e) { - self.updateSection(''); + this.updateSection(''); e.preventDefault(); - }} + }.bind(this)} /> ); } else { @@ -297,8 +296,8 @@ export default class UserSettingsGeneralTab extends React.Component { title='Full Name' describe={fullName} updateSection={function updateNameSection() { - self.updateSection('name'); - }} + this.updateSection('name'); + }.bind(this)} /> ); } @@ -335,9 +334,9 @@ export default class UserSettingsGeneralTab extends React.Component { server_error={serverError} client_error={clientError} updateSection={function clearSection(e) { - self.updateSection(''); + this.updateSection(''); e.preventDefault(); - }} + }.bind(this)} /> ); } else { @@ -346,8 +345,8 @@ export default class UserSettingsGeneralTab extends React.Component { title='Nickname' describe={UserStore.getCurrentUser().nickname} updateSection={function updateNicknameSection() { - self.updateSection('nickname'); - }} + this.updateSection('nickname'); + }.bind(this)} /> ); } @@ -384,9 +383,9 @@ export default class UserSettingsGeneralTab extends React.Component { server_error={serverError} client_error={clientError} updateSection={function clearSection(e) { - self.updateSection(''); + this.updateSection(''); e.preventDefault(); - }} + }.bind(this)} /> ); } else { @@ -395,8 +394,8 @@ export default class UserSettingsGeneralTab extends React.Component { title='Username' describe={UserStore.getCurrentUser().username} updateSection={function updateUsernameSection() { - self.updateSection('username'); - }} + this.updateSection('username'); + }.bind(this)} /> ); } @@ -433,9 +432,9 @@ export default class UserSettingsGeneralTab extends React.Component { server_error={serverError} client_error={emailError} updateSection={function clearSection(e) { - self.updateSection(''); + this.updateSection(''); e.preventDefault(); - }} + }.bind(this)} /> ); } else { @@ -444,8 +443,8 @@ export default class UserSettingsGeneralTab extends React.Component { title='Email' describe={UserStore.getCurrentUser().email} updateSection={function updateEmailSection() { - self.updateSection('email'); - }} + this.updateSection('email'); + }.bind(this)} /> ); } @@ -460,9 +459,9 @@ export default class UserSettingsGeneralTab extends React.Component { server_error={serverError} client_error={clientError} updateSection={function clearSection(e) { - self.updateSection(''); + this.updateSection(''); e.preventDefault(); - }} + }.bind(this)} picture={this.state.picture} pictureChange={this.updatePicture} submitActive={this.submitActive} @@ -479,8 +478,8 @@ export default class UserSettingsGeneralTab extends React.Component { title='Profile Picture' describe={minMessage} updateSection={function updatePictureSection() { - self.updateSection('picture'); - }} + this.updateSection('picture'); + }.bind(this)} /> ); } diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx index f5a555951..7ec75e000 100644 --- a/web/react/components/user_settings_modal.jsx +++ b/web/react/components/user_settings_modal.jsx @@ -4,51 +4,75 @@ var SettingsSidebar = require('./settings_sidebar.jsx'); var UserSettings = require('./user_settings.jsx'); -module.exports = React.createClass({ - componentDidMount: function() { - $('body').on('click', '.modal-back', function(){ +export default class UserSettingsModal extends React.Component { + constructor(props) { + super(props); + + this.updateTab = this.updateTab.bind(this); + this.updateSection = this.updateSection.bind(this); + + this.state = {active_tab: 'general', active_section: ''}; + } + componentDidMount() { + $('body').on('click', '.modal-back', function changeDisplay() { $(this).closest('.modal-dialog').removeClass('display--content'); }); - $('body').on('click', '.modal-header .close', function(){ - setTimeout(function() { + $('body').on('click', '.modal-header .close', function closeModal() { + setTimeout(function finishClose() { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); }); - }, - updateTab: function(tab) { - this.setState({ active_tab: tab }); - }, - updateSection: function(section) { - this.setState({ active_section: section }); - }, - getInitialState: function() { - return { active_tab: "general", active_section: "" }; - }, - render: function() { + } + updateTab(tab) { + this.setState({active_tab: tab}); + } + updateSection(section) { + this.setState({active_section: section}); + } + render() { var tabs = []; - tabs.push({name: "general", uiName: "General", icon: "glyphicon glyphicon-cog"}); - tabs.push({name: "security", uiName: "Security", icon: "glyphicon glyphicon-lock"}); - tabs.push({name: "notifications", uiName: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"}); - tabs.push({name: "appearance", uiName: "Appearance", icon: "glyphicon glyphicon-wrench"}); + tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); + tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'}); + tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'}); + tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'}); return ( - <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog settings-modal"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" ref="title">Account Settings</h4> + <div + className='modal fade' + ref='modal' + id='user_settings' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog settings-modal'> + <div className='modal-content'> + <div className='modal-header'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + Account Settings + </h4> </div> - <div className="modal-body"> - <div className="settings-table"> - <div className="settings-links"> + <div className='modal-body'> + <div className='settings-table'> + <div className='settings-links'> <SettingsSidebar tabs={tabs} activeTab={this.state.active_tab} updateTab={this.updateTab} /> </div> - <div className="settings-content minimize-settings"> + <div className='settings-content minimize-settings'> <UserSettings activeTab={this.state.active_tab} activeSection={this.state.active_section} @@ -63,4 +87,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx index ba0bda78e..b97f29e29 100644 --- a/web/react/components/user_settings_notifications.jsx +++ b/web/react/components/user_settings_notifications.jsx @@ -163,15 +163,15 @@ export default class NotificationsTab extends React.Component { } handleNotifyRadio(notifyLevel) { this.setState({notifyLevel: notifyLevel}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } handleEmailRadio(enableEmail) { this.setState({enableEmail: enableEmail}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } handleSoundRadio(enableSound) { this.setState({enableSound: enableSound}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } updateUsernameKey(val) { this.setState({usernameKey: val}); @@ -189,10 +189,10 @@ export default class NotificationsTab extends React.Component { this.setState({channelKey: val}); } updateCustomMentionKeys() { - var checked = this.refs.customcheck.getDOMNode().checked; + var checked = React.findDOMNode(this.refs.customcheck).checked; if (checked) { - var text = this.refs.custommentions.getDOMNode().value; + var text = React.findDOMNode(this.refs.custommentions).value; // remove all spaces and split string into individual keys this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true}); @@ -201,7 +201,7 @@ export default class NotificationsTab extends React.Component { } } onCustomChange() { - this.refs.customcheck.getDOMNode().checked = true; + React.findDOMNode(this.refs.customcheck).checked = true; this.updateCustomMentionKeys(); } render() { @@ -210,8 +210,6 @@ export default class NotificationsTab extends React.Component { serverError = this.state.serverError; } - var self = this; - var user = this.props.user; var desktopSection; @@ -229,12 +227,12 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationLevelOption'> <div className='radio'> <label> <input type='radio' checked={notifyActive[0]} - onChange={self.handleNotifyRadio.bind(this, 'all')} + onChange={this.handleNotifyRadio.bind(this, 'all')} > For all activity </input> @@ -246,7 +244,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={notifyActive[1]} - onChange={self.handleNotifyRadio.bind(this, 'mention')} + onChange={this.handleNotifyRadio.bind(this, 'mention')} > Only for mentions and private messages </input> @@ -258,7 +256,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={notifyActive[2]} - onChange={self.handleNotifyRadio.bind(this, 'none')} + onChange={this.handleNotifyRadio.bind(this, 'none')} > Never </input> @@ -268,15 +266,15 @@ export default class NotificationsTab extends React.Component { ); handleUpdateDesktopSection = function updateDesktopSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); let extraInfo = ( <div className='setting-list__hint'> These settings will override the global notification settings for the <b>{this.state.curChannel}</b> channel </div> - ) + ); desktopSection = ( <SettingItemMax @@ -299,8 +297,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateDesktopSection = function updateDesktopSection() { - self.props.updateSection('desktop'); - }; + this.props.updateSection('desktop'); + }.bind(this); desktopSection = ( <SettingItemMin @@ -324,13 +322,13 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationSoundOptions'> <div className='radio'> <label> <input type='radio' checked={soundActive[0]} - onChange={self.handleSoundRadio.bind(this, 'true')} + onChange={this.handleSoundRadio.bind(this, 'true')} > On </input> @@ -342,7 +340,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={soundActive[1]} - onChange={self.handleSoundRadio.bind(this, 'false')} + onChange={this.handleSoundRadio.bind(this, 'false')} > Off </input> @@ -353,9 +351,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateSoundSection = function updateSoundSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); soundSection = ( <SettingItemMax @@ -377,8 +375,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateSoundSection = function updateSoundSection() { - self.props.updateSection('sound'); - }; + this.props.updateSection('sound'); + }.bind(this); soundSection = ( <SettingItemMin @@ -403,13 +401,13 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationEmailOptions'> <div className='radio'> <label> <input type='radio' checked={emailActive[0]} - onChange={self.handleEmailRadio.bind(this, 'true')} + onChange={this.handleEmailRadio.bind(this, 'true')} > On </input> @@ -421,7 +419,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={emailActive[1]} - onChange={self.handleEmailRadio.bind(this, 'false')} + onChange={this.handleEmailRadio.bind(this, 'false')} > Off </input> @@ -433,9 +431,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateEmailSection = function updateEmailSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); emailSection = ( <SettingItemMax @@ -455,8 +453,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateEmailSection = function updateEmailSection() { - self.props.updateSection('email'); - }; + this.props.updateSection('email'); + }.bind(this); emailSection = ( <SettingItemMin @@ -480,10 +478,10 @@ export default class NotificationsTab extends React.Component { if (user.first_name) { handleUpdateFirstNameKey = function handleFirstNameKeyChange(e) { - self.updateFirstNameKey(e.target.checked); - }; + this.updateFirstNameKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationFirstNameOption'> <div className='checkbox'> <label> <input @@ -500,10 +498,10 @@ export default class NotificationsTab extends React.Component { } handleUpdateUsernameKey = function handleUsernameKeyChange(e) { - self.updateUsernameKey(e.target.checked); - }; + this.updateUsernameKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationUsernameOption'> <div className='checkbox'> <label> <input @@ -519,10 +517,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateMentionKey = function handleMentionKeyChange(e) { - self.updateMentionKey(e.target.checked); - }; + this.updateMentionKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationMentionOption'> <div className='checkbox'> <label> <input @@ -538,10 +536,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateAllKey = function handleAllKeyChange(e) { - self.updateAllKey(e.target.checked); - }; + this.updateAllKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationAllOption'> <div className='checkbox'> <label> <input @@ -557,10 +555,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateChannelKey = function handleChannelKeyChange(e) { - self.updateChannelKey(e.target.checked); - }; + this.updateChannelKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationChannelOption'> <div className='checkbox'> <label> <input @@ -576,7 +574,7 @@ export default class NotificationsTab extends React.Component { ); inputs.push( - <div> + <div key='userNotificationCustomOption'> <div className='checkbox'> <label> <input @@ -600,9 +598,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateKeysSection = function updateKeysSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); keysSection = ( <SettingItemMax title='Words that trigger mentions' @@ -645,8 +643,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateKeysSection = function updateKeysSection() { - self.props.updateSection('keys'); - }; + this.props.updateSection('keys'); + }.bind(this); keysSection = ( <SettingItemMin diff --git a/web/react/components/user_settings_security.jsx b/web/react/components/user_settings_security.jsx index ae8a5f0bc..a9f62097a 100644 --- a/web/react/components/user_settings_security.jsx +++ b/web/react/components/user_settings_security.jsx @@ -3,13 +3,23 @@ var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - displayName: 'SecurityTab', - submitPassword: function(e) { +export default class SecurityTab extends React.Component { + constructor(props) { + super(props); + + this.submitPassword = this.submitPassword.bind(this); + this.updateCurrentPassword = this.updateCurrentPassword.bind(this); + this.updateNewPassword = this.updateNewPassword.bind(this); + this.updateConfirmPassword = this.updateConfirmPassword.bind(this); + this.handleClose = this.handleClose.bind(this); + + this.state = {currentPassword: '', newPassword: '', confirmPassword: ''}; + } + submitPassword(e) { e.preventDefault(); var user = this.props.user; @@ -37,13 +47,13 @@ module.exports = React.createClass({ data.current_password = currentPassword; data.new_password = newPassword; - client.updatePassword(data, - function() { + Client.updatePassword(data, + function success() { this.props.updateSection(''); AsyncClient.getMe(); this.setState({currentPassword: '', newPassword: '', confirmPassword: ''}); }.bind(this), - function(err) { + function fail(err) { var state = this.getInitialState(); if (err.message) { state.serverError = err.message; @@ -54,47 +64,49 @@ module.exports = React.createClass({ this.setState(state); }.bind(this) ); - }, - updateCurrentPassword: function(e) { + } + updateCurrentPassword(e) { this.setState({currentPassword: e.target.value}); - }, - updateNewPassword: function(e) { + } + updateNewPassword(e) { this.setState({newPassword: e.target.value}); - }, - updateConfirmPassword: function(e) { + } + updateConfirmPassword(e) { this.setState({confirmPassword: e.target.value}); - }, - handleHistoryOpen: function() { - $("#user_settings").modal('hide'); - }, - handleDevicesOpen: function() { - $("#user_settings").modal('hide'); - }, - handleClose: function() { - $(this.getDOMNode()).find('.form-control').each(function() { + } + handleHistoryOpen() { + $('#user_settings').modal('hide'); + } + handleDevicesOpen() { + $('#user_settings').modal('hide'); + } + handleClose() { + $(React.findDOMNode(this)).find('.form-control').each(function resetValue() { this.value = ''; }); this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); this.props.updateTab('general'); - }, - componentDidMount: function() { + } + componentDidMount() { $('#user_settings').on('hidden.bs.modal', this.handleClose); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); - }, - getInitialState: function() { - return {currentPassword: '', newPassword: '', confirmPassword: ''}; - }, - render: function() { - var serverError = this.state.serverError ? this.state.serverError : null; - var passwordError = this.state.passwordError ? this.state.passwordError : null; + } + render() { + var serverError; + if (this.state.serverError) { + serverError = this.state.serverError; + } + var passwordError; + if (this.state.passwordError) { + passwordError = this.state.passwordError; + } var updateSectionStatus; var passwordSection; - var self = this; if (this.props.activeSection === 'password') { var inputs = []; var submit = null; @@ -104,7 +116,12 @@ module.exports = React.createClass({ <div className='form-group'> <label className='col-sm-5 control-label'>Current Password</label> <div className='col-sm-7'> - <input className='form-control' type='password' onChange={this.updateCurrentPassword} value={this.state.currentPassword}/> + <input + className='form-control' + type='password' + onChange={this.updateCurrentPassword} + value={this.state.currentPassword} + /> </div> </div> ); @@ -112,7 +129,12 @@ module.exports = React.createClass({ <div className='form-group'> <label className='col-sm-5 control-label'>New Password</label> <div className='col-sm-7'> - <input className='form-control' type='password' onChange={this.updateNewPassword} value={this.state.newPassword}/> + <input + className='form-control' + type='password' + onChange={this.updateNewPassword} + value={this.state.newPassword} + /> </div> </div> ); @@ -120,7 +142,12 @@ module.exports = React.createClass({ <div className='form-group'> <label className='col-sm-5 control-label'>Retype New Password</label> <div className='col-sm-7'> - <input className='form-control' type='password' onChange={this.updateConfirmPassword} value={this.state.confirmPassword}/> + <input + className='form-control' + type='password' + onChange={this.updateConfirmPassword} + value={this.state.confirmPassword} + /> </div> </div> ); @@ -134,11 +161,11 @@ module.exports = React.createClass({ ); } - updateSectionStatus = function(e) { - self.props.updateSection(''); - self.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); + updateSectionStatus = function resetSection(e) { + this.props.updateSection(''); + this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); e.preventDefault(); - }; + }.bind(this); passwordSection = ( <SettingItemMax @@ -154,17 +181,27 @@ module.exports = React.createClass({ var describe; if (this.props.user.auth_service === '') { var d = new Date(this.props.user.last_password_update); - var hour = d.getHours() % 12 ? String(d.getHours() % 12) : '12'; - var min = d.getMinutes() < 10 ? '0' + d.getMinutes() : String(d.getMinutes()); - var timeOfDay = d.getHours() >= 12 ? ' pm' : ' am'; + var hour = '12'; + if (d.getHours() % 12) { + hour = String(d.getHours() % 12); + } + var min = String(d.getMinutes()); + if (d.getMinutes() < 10) { + min = '0' + d.getMinutes(); + } + var timeOfDay = ' am'; + if (d.getHours() >= 12) { + timeOfDay = ' pm'; + } + describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay; } else { describe = 'Log in done through GitLab'; } - updateSectionStatus = function() { - self.props.updateSection('password'); - }; + updateSectionStatus = function updateSection() { + this.props.updateSection('password'); + }.bind(this); passwordSection = ( <SettingItemMin @@ -178,8 +215,20 @@ module.exports = React.createClass({ return ( <div> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' ref='title'><i className='modal-back'></i>Security Settings</h4> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <i className='modal-back'></i>Security Settings + </h4> </div> <div className='user-settings'> <h3 className='tab-header'>Security Settings</h3> @@ -187,11 +236,38 @@ module.exports = React.createClass({ {passwordSection} <div className='divider-dark'/> <br></br> - <a data-toggle='modal' className='security-links theme' data-target='#access-history' href='#' onClick={this.handleHistoryOpen}><i className='fa fa-clock-o'></i>View Access History</a> + <a + data-toggle='modal' + className='security-links theme' + data-target='#access-history' + href='#' + onClick={this.handleHistoryOpen} + > + <i className='fa fa-clock-o'></i>View Access History + </a> <b> </b> - <a data-toggle='modal' className='security-links theme' data-target='#activity-log' href='#' onClick={this.handleDevicesOpen}><i className='fa fa-globe'></i>View and Logout of Active Sessions</a> + <a + data-toggle='modal' + className='security-links theme' + data-target='#activity-log' + href='#' + onClick={this.handleDevicesOpen} + > + <i className='fa fa-globe'></i>View and Logout of Active Sessions + </a> </div> </div> ); } -}); +} + +SecurityTab.defaultProps = { + user: {}, + activeSection: '' +}; +SecurityTab.propTypes = { + user: React.PropTypes.object, + activeSection: React.PropTypes.string, + updateSection: React.PropTypes.func, + updateTab: React.PropTypes.func +}; diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 24efce0f1..6e61ab156 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -2,35 +2,46 @@ // See License.txt for license information. var Client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - displayName: 'ViewImageModal', - propTypes: { - filenames: React.PropTypes.array, - modalId: React.PropTypes.string, - channelId: React.PropTypes.string, - userId: React.PropTypes.string, - startId: React.PropTypes.number - }, - canSetState: false, - handleNext: function() { +export default class ViewImageModal extends React.Component { + constructor(props) { + super(props); + + this.canSetState = false; + + this.loadImage = this.loadImage.bind(this); + this.handleNext = this.handleNext.bind(this); + this.handlePrev = this.handlePrev.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + this.getPublicLink = this.getPublicLink.bind(this); + this.getPreviewImagePath = this.getPreviewImagePath.bind(this); + + var loaded = []; + var progress = []; + for (var i = 0; i < this.props.filenames.length; i++) { + loaded.push(false); + progress.push(0); + } + this.state = {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}}; + } + handleNext() { var id = this.state.imgId + 1; if (id > this.props.filenames.length - 1) { id = 0; } this.setState({imgId: id}); this.loadImage(id); - }, - handlePrev: function() { + } + handlePrev() { var id = this.state.imgId - 1; if (id < 0) { id = this.props.filenames.length - 1; } this.setState({imgId: id}); this.loadImage(id); - }, - handleKeyPress: function handleKeyPress(e) { + } + handleKeyPress(e) { if (!e) { return; } else if (e.keyCode === 39) { @@ -38,11 +49,11 @@ module.exports = React.createClass({ } else if (e.keyCode === 37) { this.handlePrev(); } - }, - componentWillReceiveProps: function(nextProps) { + } + componentWillReceiveProps(nextProps) { this.setState({imgId: nextProps.startId}); - }, - loadImage: function(id) { + } + loadImage(id) { var imgHeight = $(window).height() - 100; if (this.state.loaded[id] || this.state.images[id]) { $('.modal .modal-image .image-wrapper img').css('max-height', imgHeight); @@ -51,26 +62,25 @@ module.exports = React.createClass({ var filename = this.props.filenames[id]; - var fileInfo = utils.splitFileLocation(filename); - var fileType = utils.getFileType(fileInfo.ext); + var fileInfo = Utils.splitFileLocation(filename); + var fileType = Utils.getFileType(fileInfo.ext); if (fileType === 'image') { - var self = this; var img = new Image(); img.load(this.getPreviewImagePath(filename), - function() { - var progress = self.state.progress; + function load() { + var progress = this.state.progress; progress[id] = img.completedPercentage; - self.setState({progress: progress}); - }); - img.onload = function onload(imgid) { + this.setState({progress: progress}); + }.bind(this)); + img.onload = (function onload(imgid) { return function onloadReturn() { - var loaded = self.state.loaded; + var loaded = this.state.loaded; loaded[imgid] = true; - self.setState({loaded: loaded}); - $(self.refs.image.getDOMNode()).css('max-height', imgHeight); - }; - }(id); + this.setState({loaded: loaded}); + $(React.findDOMNode(this.refs.image)).css('max-height', imgHeight); + }.bind(this); + }.bind(this)(id)); var images = this.state.images; images[id] = img; this.setState({images: images}); @@ -80,52 +90,51 @@ module.exports = React.createClass({ loaded[id] = true; this.setState({loaded: loaded}); } - }, - componentDidUpdate: function() { + } + componentDidUpdate() { if (this.state.loaded[this.state.imgId]) { if (this.refs.imageWrap) { - $(this.refs.imageWrap.getDOMNode()).removeClass('default'); + $(React.findDOMNode(this.refs.imageWrap)).removeClass('default'); } } - }, - componentDidMount: function() { - var self = this; + } + componentDidMount() { $('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() { - self.setState({viewed: true}); - self.loadImage(self.state.imgId); - }); + this.setState({viewed: true}); + this.loadImage(this.state.imgId); + }.bind(this)); - $(this.refs.modal.getDOMNode()).click(function onModalClick(e) { - if (e.target === this || e.target === self.refs.imageBody.getDOMNode()) { + $(React.findDOMNode(this.refs.modal)).click(function onModalClick(e) { + if (e.target === this || e.target === React.findDOMNode(this.refs.imageBody)) { $('.image_modal').modal('hide'); } - }); + }.bind(this)); - $(this.refs.imageWrap.getDOMNode()).hover( + $(React.findDOMNode(this.refs.imageWrap)).hover( function onModalHover() { - $(self.refs.imageFooter.getDOMNode()).addClass('footer--show'); - }, function offModalHover() { - $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show'); - } + $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); + }.bind(this), function offModalHover() { + $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); + }.bind(this) ); if (this.refs.previewArrowLeft) { - $(this.refs.previewArrowLeft.getDOMNode()).hover( + $(React.findDOMNode(this.refs.previewArrowLeft)).hover( function onModalHover() { - $(self.refs.imageFooter.getDOMNode()).addClass('footer--show'); - }, function offModalHover() { - $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show'); - } + $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); + }.bind(this), function offModalHover() { + $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); + }.bind(this) ); } if (this.refs.previewArrowRight) { - $(this.refs.previewArrowRight.getDOMNode()).hover( + $(React.findDOMNode(this.refs.previewArrowRight)).hover( function onModalHover() { - $(self.refs.imageFooter.getDOMNode()).addClass('footer--show'); - }, function offModalHover() { - $(self.refs.imageFooter.getDOMNode()).removeClass('footer--show'); - } + $(React.findDOMNode(this.refs.imageFooter)).addClass('footer--show'); + }.bind(this), function offModalHover() { + $(React.findDOMNode(this.refs.imageFooter)).removeClass('footer--show'); + }.bind(this) ); } @@ -133,90 +142,93 @@ module.exports = React.createClass({ // keep track of whether or not this component is mounted so we can safely set the state asynchronously this.canSetState = true; - }, - componentWillUnmount: function() { + } + componentWillUnmount() { this.canSetState = false; $(window).off('keyup', this.handleKeyPress); - }, - getPublicLink: function() { + } + getPublicLink() { var data = {}; data.channel_id = this.props.channelId; data.user_id = this.props.userId; data.filename = this.props.filenames[this.state.imgId]; Client.getPublicLink(data, function sucess(serverData) { - if (utils.isMobile()) { + if (Utils.isMobile()) { window.location.href = serverData.public_link; } else { window.open(serverData.public_link); } }, - function error() { - } + function error() {} ); - }, - getPreviewImagePath: function(filename) { + } + getPreviewImagePath(filename) { // Returns the path to a preview image that can be used to represent a file. - var fileInfo = utils.splitFileLocation(filename); - var fileType = utils.getFileType(fileInfo.ext); + var fileInfo = Utils.splitFileLocation(filename); + var fileType = Utils.getFileType(fileInfo.ext); if (fileType === 'image') { // This is a temporary patch to fix issue with old files using absolute paths if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; } - fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; + fileInfo.path = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; return fileInfo.path + '_preview.jpg'; } // only images have proper previews, so just use a placeholder icon for non-images - return utils.getPreviewImagePathForFileType(fileType); - }, - getInitialState: function() { - var loaded = []; - var progress = []; - for (var i = 0; i < this.props.filenames.length; i++) { - loaded.push(false); - progress.push(0); - } - return {imgId: this.props.startId, viewed: false, loaded: loaded, progress: progress, images: {}, fileSizes: {}}; - }, - render: function() { + return Utils.getPreviewImagePathForFileType(fileType); + } + render() { if (this.props.filenames.length < 1 || this.props.filenames.length - 1 < this.state.imgId) { return <div/>; } var filename = this.props.filenames[this.state.imgId]; - var fileUrl = utils.getFileUrl(filename); + var fileUrl = Utils.getFileUrl(filename); - var name = decodeURIComponent(utils.getFileName(filename)); + var name = decodeURIComponent(Utils.getFileName(filename)); var content; var bgClass = ''; if (this.state.loaded[this.state.imgId]) { - var fileInfo = utils.splitFileLocation(filename); - var fileType = utils.getFileType(fileInfo.ext); + var fileInfo = Utils.splitFileLocation(filename); + var fileType = Utils.getFileType(fileInfo.ext); if (fileType === 'image') { // image files just show a preview of the file content = ( - <a href={fileUrl} target='_blank'> - <img ref='image' src={this.getPreviewImagePath(filename)}/> + <a + href={fileUrl} + target='_blank' + > + <img + ref='image' + src={this.getPreviewImagePath(filename)} + /> </a> ); } else { // non-image files include a section providing details about the file var infoString = 'File type ' + fileInfo.ext.toUpperCase(); if (this.state.fileSizes[filename] && this.state.fileSizes[filename] >= 0) { - infoString += ', Size ' + utils.fileSizeToString(this.state.fileSizes[filename]); + infoString += ', Size ' + Utils.fileSizeToString(this.state.fileSizes[filename]); } content = ( <div className='file-details__container'> - <a className={'file-details__preview'} href={fileUrl} target='_blank'> + <a + className={'file-details__preview'} + href={fileUrl} + target='_blank' + > <span className='file-details__preview-helper' /> - <img ref='image' src={this.getPreviewImagePath(filename)} /> + <img + ref='image' + src={this.getPreviewImagePath(filename)} + /> </a> <div className='file-details'> <div className='file-details__name'>{name}</div> @@ -228,19 +240,16 @@ module.exports = React.createClass({ // asynchronously request the actual size of this file if (!(filename in this.state.fileSizes)) { - var self = this; - Client.getFileInfo( filename, - function(data) { - if (self.canSetState) { - var fileSizes = self.state.fileSizes; - fileSizes[filename] = parseInt(data["size"], 10); - self.setState(fileSizes); + function success(data) { + if (this.canSetState) { + var fileSizes = this.state.fileSizes; + fileSizes[filename] = parseInt(data.size, 10); + this.setState(fileSizes); } - }, - function(err) { - } + }.bind(this), + function fail() {} ); } } @@ -250,14 +259,22 @@ module.exports = React.createClass({ if (percentage) { content = ( <div> - <img className='loader-image' src='/static/images/load.gif' /> - <span className='loader-percent' >{'Previewing ' + percentage + '%'}</span> + <img + className='loader-image' + src='/static/images/load.gif' + /> + <span className='loader-percent'> + {'Previewing ' + percentage + '%'} + </span> </div> ); } else { content = ( <div> - <img className='loader-image' src='/static/images/load.gif' /> + <img + className='loader-image' + src='/static/images/load.gif' + /> </div> ); } @@ -268,7 +285,14 @@ module.exports = React.createClass({ if (config.AllowPublicLink) { publicLink = ( <div> - <a href='#' className='public-link text' data-title='Public Image' onClick={this.getPublicLink}>Get Public Link</a> + <a + href='#' + className='public-link text' + data-title='Public Image' + onClick={this.getPublicLink} + > + Get Public Link + </a> <span className='text'> | </span> </div> ); @@ -299,18 +323,43 @@ module.exports = React.createClass({ } return ( - <div className='modal fade image_modal' ref='modal' id={this.props.modalId} tabIndex='-1' role='dialog' aria-hidden='true'> + <div + className='modal fade image_modal' + ref='modal' + id={this.props.modalId} + tabIndex='-1' + role='dialog' + aria-hidden='true' + > <div className='modal-dialog modal-image'> <div className='modal-content image-content'> - <div ref='imageBody' className='modal-body image-body'> - <div ref='imageWrap' className={'image-wrapper default ' + bgClass}> - <div className='modal-close' data-dismiss='modal'></div> + <div + ref='imageBody' + className='modal-body image-body' + > + <div + ref='imageWrap' + className={'image-wrapper default ' + bgClass} + > + <div + className='modal-close' + data-dismiss='modal' + /> {content} - <div ref='imageFooter' className='modal-button-bar'> + <div + ref='imageFooter' + className='modal-button-bar' + > <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span> <div className='image-links'> {publicLink} - <a href={fileUrl} download={name} className='text'>Download</a> + <a + href={fileUrl} + download={name} + className='text' + > + Download + </a> </div> </div> </div> @@ -322,4 +371,19 @@ module.exports = React.createClass({ </div> ); } -}); +} + +ViewImageModal.defaultProps = { + filenames: [], + modalId: '', + channelId: '', + userId: '', + startId: 0 +}; +ViewImageModal.propTypes = { + filenames: React.PropTypes.array, + modalId: React.PropTypes.string, + channelId: React.PropTypes.string, + userId: React.PropTypes.string, + startId: React.PropTypes.number +}; diff --git a/web/react/dispatcher/app_dispatcher.jsx b/web/react/dispatcher/app_dispatcher.jsx index 4ae28e8eb..04e026f46 100644 --- a/web/react/dispatcher/app_dispatcher.jsx +++ b/web/react/dispatcher/app_dispatcher.jsx @@ -8,23 +8,21 @@ var Constants = require('../utils/constants.jsx'); var PayloadSources = Constants.PayloadSources; var AppDispatcher = assign(new Dispatcher(), { + handleServerAction: function performServerAction(action) { + var payload = { + source: PayloadSources.SERVER_ACTION, + action: action + }; + this.dispatch(payload); + }, - handleServerAction: function(action) { - var payload = { - source: PayloadSources.SERVER_ACTION, - action: action - }; - this.dispatch(payload); - }, - - handleViewAction: function(action) { - var payload = { - source: PayloadSources.VIEW_ACTION, - action: action - }; - this.dispatch(payload); - } - + handleViewAction: function performViewAction(action) { + var payload = { + source: PayloadSources.VIEW_ACTION, + action: action + }; + this.dispatch(payload); + } }); module.exports = AppDispatcher; diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 98014ed12..d56b309fa 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -26,13 +26,13 @@ var ChannelMembersModal = require('../components/channel_members.jsx'); var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); var DirectChannelModal = require('../components/more_direct_channels.jsx'); -var ErrorBar = require('../components/error_bar.jsx') +var ErrorBar = require('../components/error_bar.jsx'); var ChannelLoader = require('../components/channel_loader.jsx'); var MentionList = require('../components/mention_list.jsx'); var ChannelInfoModal = require('../components/channel_info_modal.jsx'); var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); -var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx') +var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var AsyncClient = require('../utils/async_client.jsx'); @@ -40,18 +40,18 @@ var AsyncClient = require('../utils/async_client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) { +function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { AsyncClient.getConfig(); AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, - name: channel_name, - id: channel_id + name: channelName, + id: channelId }); AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_TEAM, - id: team_id + id: teamId }); // ChannelLoader must be rendered first @@ -66,12 +66,14 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - <Navbar teamDisplayName={team_name} />, + <Navbar teamDisplayName={teamName} />, document.getElementById('navbar') ); React.render( - <Sidebar teamDisplayName={team_name} teamType={team_type} />, + <Sidebar + teamDisplayName={teamName} + teamType={teamType} />, document.getElementById('sidebar-left') ); @@ -86,17 +88,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - <TeamSettingsModal teamDisplayName={team_name} />, + <TeamSettingsModal teamDisplayName={teamName} />, document.getElementById('team_settings_modal') ); React.render( - <TeamMembersModal teamDisplayName={team_name} />, + <TeamMembersModal teamDisplayName={teamName} />, document.getElementById('team_members_modal') ); React.render( - <MemberInviteModal teamType={team_type} />, + <MemberInviteModal teamType={teamType} />, document.getElementById('invite_member_modal') ); @@ -186,7 +188,9 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - <SidebarRightMenu teamDisplayName={team_name} teamType={team_type} />, + <SidebarRightMenu + teamDisplayName={teamName} + teamType={teamType} />, document.getElementById('sidebar-menu') ); @@ -225,5 +229,6 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann overlayType='center' />, document.getElementById('file_upload_overlay') ); +} -}; +global.window.setup_channel_page = setupChannelPage; diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx index 5346c0cf0..dd11857ac 100644 --- a/web/react/pages/find_team.jsx +++ b/web/react/pages/find_team.jsx @@ -3,11 +3,11 @@ var FindTeam = require('../components/find_team.jsx'); -global.window.setup_find_team_page = function() { - +function setupFindTeamPage() { React.render( <FindTeam />, document.getElementById('find-team') ); +} -}; +global.window.setup_find_team_page = setupFindTeamPage; diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx index b12fa4949..18553542c 100644 --- a/web/react/pages/home.jsx +++ b/web/react/pages/home.jsx @@ -2,14 +2,15 @@ // See License.txt for license information. var ChannelStore = require('../stores/channel_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); -global.window.setup_home_page = function(teamURL) { +function setupHomePage(teamURL) { var last = ChannelStore.getLastVisitedName(); if (last == null || last.length === 0) { - window.location = teamURL + "/channels/" + Constants.DEFAULT_CHANNEL; + window.location = teamURL + '/channels/' + Constants.DEFAULT_CHANNEL; } else { - window.location = teamURL + "/channels/" + last; + window.location = teamURL + '/channels/' + last; } } + +global.window.setup_home_page = setupHomePage; diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx index 6e7528373..e7305889d 100644 --- a/web/react/pages/login.jsx +++ b/web/react/pages/login.jsx @@ -3,9 +3,14 @@ var Login = require('../components/login.jsx'); -global.window.setup_login_page = function(team_display_name, team_name, auth_services) { +function setupLoginPage(teamDisplayName, teamName, authServices) { React.render( - <Login teamDisplayName={team_display_name} teamName={team_name} authServices={auth_services} />, + <Login + teamDisplayName={teamDisplayName} + teamName={teamName} + authServices={authServices} />, document.getElementById('login') ); -}; +} + +global.window.setup_login_page = setupLoginPage; diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx index c7a208973..2ca468bea 100644 --- a/web/react/pages/password_reset.jsx +++ b/web/react/pages/password_reset.jsx @@ -3,17 +3,17 @@ var PasswordReset = require('../components/password_reset.jsx'); -global.window.setup_password_reset_page = function(is_reset, team_display_name, team_name, hash, data) { - +function setupPasswordResetPage(isReset, teamDisplayName, teamName, hash, data) { React.render( <PasswordReset - isReset={is_reset} - teamDisplayName={team_display_name} - teamName={team_name} + isReset={isReset} + teamDisplayName={teamDisplayName} + teamName={teamName} hash={hash} data={data} />, document.getElementById('reset') ); +} -}; +global.window.setup_password_reset_page = setupPasswordResetPage; diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index 4b58025ac..e9e803aa4 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -5,7 +5,7 @@ var SignupTeam = require('../components/signup_team.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -global.window.setup_signup_team_page = function(authServices) { +function setupSignupTeamPage(authServices) { AsyncClient.getConfig(); var services = JSON.parse(authServices); @@ -14,4 +14,6 @@ global.window.setup_signup_team_page = function(authServices) { <SignupTeam services={services} />, document.getElementById('signup-team') ); -}; +} + +global.window.setup_signup_team_page = setupSignupTeamPage; diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx index 71806c2ea..43e3aae65 100644 --- a/web/react/pages/signup_team_complete.jsx +++ b/web/react/pages/signup_team_complete.jsx @@ -1,11 +1,16 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var SignupTeamComplete =require('../components/signup_team_complete.jsx'); +var SignupTeamComplete = require('../components/signup_team_complete.jsx'); -global.window.setup_signup_team_complete_page = function(email, data, hash) { +function setupSignupTeamCompletePage(email, data, hash) { React.render( - <SignupTeamComplete email={email} hash={hash} data={data}/>, + <SignupTeamComplete + email={email} + hash={hash} + data={data}/>, document.getElementById('signup-team-complete') ); -}; +} + +global.window.setup_signup_team_complete_page = setupSignupTeamCompletePage; diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx index 8f9be1f94..71b526e5d 100644 --- a/web/react/pages/signup_user_complete.jsx +++ b/web/react/pages/signup_user_complete.jsx @@ -3,9 +3,18 @@ var SignupUserComplete = require('../components/signup_user_complete.jsx'); -global.window.setup_signup_user_complete_page = function(email, name, ui_name, id, data, hash, auth_services) { +function setupSignupUserCompletePage(email, name, uiName, id, data, hash, authServices) { React.render( - <SignupUserComplete teamId={id} teamName={name} teamDisplayName={ui_name} email={email} hash={hash} data={data} authServices={auth_services} />, + <SignupUserComplete + teamId={id} + teamName={name} + teamDisplayName={uiName} + email={email} + hash={hash} + data={data} + authServices={authServices} />, document.getElementById('signup-user-complete') ); -}; +} + +global.window.setup_signup_user_complete_page = setupSignupUserCompletePage; diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx index 96b556983..f42913315 100644 --- a/web/react/pages/verify.jsx +++ b/web/react/pages/verify.jsx @@ -5,7 +5,10 @@ var EmailVerify = require('../components/email_verify.jsx'); global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) { React.render( - <EmailVerify isVerified={isVerified} teamURL={teamURL} userEmail={userEmail} />, + <EmailVerify + isVerified={isVerified} + teamURL={teamURL} + userEmail={userEmail} />, document.getElementById('verify') ); }; diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index b1f51e5f4..e1ca52746 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -12,81 +12,70 @@ function getPrefix() { // Also change model/utils.go ETAG_ROOT_VERSION var BROWSER_STORE_VERSION = '.5'; -module.exports = { - initialized: false, +class BrowserStoreClass { + constructor() { + this.getItem = this.getItem.bind(this); + this.setItem = this.setItem.bind(this); + this.removeItem = this.removeItem.bind(this); + this.setGlobalItem = this.setGlobalItem.bind(this); + this.getGlobalItem = this.getGlobalItem.bind(this); + this.removeGlobalItem = this.removeGlobalItem.bind(this); + this.clear = this.clear.bind(this); + this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this); + this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this); - initialize: function() { var currentVersion = localStorage.getItem('local_storage_version'); if (currentVersion !== BROWSER_STORE_VERSION) { this.clear(); localStorage.setItem('local_storage_version', BROWSER_STORE_VERSION); } - this.initialized = true; - }, + } - getItem: function(name, defaultValue) { + getItem(name, defaultValue) { return this.getGlobalItem(getPrefix() + name, defaultValue); - }, + } - setItem: function(name, value) { + setItem(name, value) { this.setGlobalItem(getPrefix() + name, value); - }, - - removeItem: function(name) { - if (!this.initialized) { - this.initialize(); - } + } + removeItem(name) { localStorage.removeItem(getPrefix() + name); - }, - - setGlobalItem: function(name, value) { - if (!this.initialized) { - this.initialize(); - } + } + setGlobalItem(name, value) { localStorage.setItem(name, JSON.stringify(value)); - }, - - getGlobalItem: function(name, defaultValue) { - if (!this.initialized) { - this.initialize(); - } + } + getGlobalItem(name, defaultValue) { var result = null; try { result = JSON.parse(localStorage.getItem(name)); - } catch (err) {} + } catch (err) { + result = null; + } if (result === null && typeof defaultValue !== 'undefined') { result = defaultValue; } return result; - }, - - removeGlobalItem: function(name) { - if (!this.initialized) { - this.initialize(); - } + } + removeGlobalItem(name) { localStorage.removeItem(name); - }, + } - clear: function() { + clear() { localStorage.clear(); sessionStorage.clear(); - }, + } /** * Preforms the given action on each item that has the given prefix * Signature for action is action(key, value) */ - actionOnItemsWithPrefix: function(prefix, action) { - if (!this.initialized) { - this.initialize(); - } - + actionOnItemsWithPrefix(prefix, action) { var globalPrefix = getPrefix(); var globalPrefixiLen = globalPrefix.length; for (var key in localStorage) { @@ -95,9 +84,9 @@ module.exports = { action(userkey, this.getGlobalItem(key)); } } - }, + } - isLocalStorageSupported: function() { + isLocalStorageSupported() { try { sessionStorage.setItem('testSession', '1'); sessionStorage.removeItem('testSession'); @@ -113,4 +102,7 @@ module.exports = { return false; } } -}; +} + +var BrowserStore = new BrowserStoreClass(); +export default BrowserStore; diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 678d50bbd..bd655b767 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; @@ -14,36 +13,42 @@ var CHANGE_EVENT = 'change'; var MORE_CHANGE_EVENT = 'change'; var EXTRA_INFO_EVENT = 'extra_info'; -var ChannelStore = assign({}, EventEmitter.prototype, { - currentId: null, - emitChange: function() { +class ChannelStoreClass extends EventEmitter { + constructor(props) { + super(props); + + this.setMaxListeners(11); + + this.currentId = null; + } + emitChange() { this.emit(CHANGE_EVENT); - }, - addChangeListener: function(callback) { + } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, - removeChangeListener: function(callback) { + } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, - emitMoreChange: function() { + } + emitMoreChange() { this.emit(MORE_CHANGE_EVENT); - }, - addMoreChangeListener: function(callback) { + } + addMoreChangeListener(callback) { this.on(MORE_CHANGE_EVENT, callback); - }, - removeMoreChangeListener: function(callback) { + } + removeMoreChangeListener(callback) { this.removeListener(MORE_CHANGE_EVENT, callback); - }, - emitExtraInfoChange: function() { + } + emitExtraInfoChange() { this.emit(EXTRA_INFO_EVENT); - }, - addExtraInfoChangeListener: function(callback) { + } + addExtraInfoChangeListener(callback) { this.on(EXTRA_INFO_EVENT, callback); - }, - removeExtraInfoChangeListener: function(callback) { + } + removeExtraInfoChangeListener(callback) { this.removeListener(EXTRA_INFO_EVENT, callback); - }, - findFirstBy: function(field, value) { + } + findFirstBy(field, value) { var channels = this.pGetChannels(); for (var i = 0; i < channels.length; i++) { if (channels[i][field] === value) { @@ -52,39 +57,39 @@ var ChannelStore = assign({}, EventEmitter.prototype, { } return null; - }, - get: function(id) { + } + get(id) { return this.findFirstBy('id', id); - }, - getMember: function(id) { + } + getMember(id) { return this.getAllMembers()[id]; - }, - getByName: function(name) { + } + getByName(name) { return this.findFirstBy('name', name); - }, - getAll: function() { + } + getAll() { return this.pGetChannels(); - }, - getAllMembers: function() { + } + getAllMembers() { return this.pGetChannelMembers(); - }, - getMoreAll: function() { + } + getMoreAll() { return this.pGetMoreChannels(); - }, - setCurrentId: function(id) { + } + setCurrentId(id) { this.currentId = id; - }, - setLastVisitedName: function(name) { + } + setLastVisitedName(name) { if (name == null) { BrowserStore.removeItem('last_visited_name'); } else { BrowserStore.setItem('last_visited_name', name); } - }, - getLastVisitedName: function() { + } + getLastVisitedName() { return BrowserStore.getItem('last_visited_name'); - }, - resetCounts: function(id) { + } + resetCounts(id) { var cm = this.pGetChannelMembers(); for (var cmid in cm) { if (cm[cmid].channel_id === id) { @@ -97,36 +102,36 @@ var ChannelStore = assign({}, EventEmitter.prototype, { } } this.pStoreChannelMembers(cm); - }, - getCurrentId: function() { + } + getCurrentId() { return this.currentId; - }, - getCurrent: function() { + } + getCurrent() { var currentId = this.getCurrentId(); if (currentId) { return this.get(currentId); - } else { - return null; } - }, - getCurrentMember: function() { - var currentId = ChannelStore.getCurrentId(); + + return null; + } + getCurrentMember() { + var currentId = this.getCurrentId(); if (currentId) { return this.getAllMembers()[currentId]; - } else { - return null; } - }, - setChannelMember: function(member) { + + return null; + } + setChannelMember(member) { var members = this.pGetChannelMembers(); members[member.channel_id] = member; this.pStoreChannelMembers(members); this.emitChange(); - }, - getCurrentExtraInfo: function() { - var currentId = ChannelStore.getCurrentId(); + } + getCurrentExtraInfo() { + var currentId = this.getCurrentId(); var extra = null; if (currentId) { @@ -138,8 +143,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, { } return extra; - }, - getExtraInfo: function(channelId) { + } + getExtraInfo(channelId) { var extra = null; if (channelId) { @@ -151,8 +156,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, { } return extra; - }, - pStoreChannel: function(channel) { + } + pStoreChannel(channel) { var channels = this.pGetChannels(); var found; @@ -179,28 +184,28 @@ var ChannelStore = assign({}, EventEmitter.prototype, { }); this.pStoreChannels(channels); - }, - pStoreChannels: function(channels) { + } + pStoreChannels(channels) { BrowserStore.setItem('channels', channels); - }, - pGetChannels: function() { + } + pGetChannels() { return BrowserStore.getItem('channels', []); - }, - pStoreChannelMember: function(channelMember) { + } + pStoreChannelMember(channelMember) { var members = this.pGetChannelMembers(); members[channelMember.channel_id] = channelMember; this.pStoreChannelMembers(members); - }, - pStoreChannelMembers: function(channelMembers) { + } + pStoreChannelMembers(channelMembers) { BrowserStore.setItem('channel_members', channelMembers); - }, - pGetChannelMembers: function() { + } + pGetChannelMembers() { return BrowserStore.getItem('channel_members', {}); - }, - pStoreMoreChannels: function(channels) { + } + pStoreMoreChannels(channels) { BrowserStore.setItem('more_channels', channels); - }, - pGetMoreChannels: function() { + } + pGetMoreChannels() { var channels = BrowserStore.getItem('more_channels'); if (channels == null) { @@ -209,66 +214,67 @@ var ChannelStore = assign({}, EventEmitter.prototype, { } return channels; - }, - pStoreExtraInfos: function(extraInfos) { + } + pStoreExtraInfos(extraInfos) { BrowserStore.setItem('extra_infos', extraInfos); - }, - pGetExtraInfos: function() { + } + pGetExtraInfos() { return BrowserStore.getItem('extra_infos', {}); - }, - isDefault: function(channel) { + } + isDefault(channel) { return channel.name === Constants.DEFAULT_CHANNEL; } -}); +} + +var ChannelStore = new ChannelStoreClass(); -ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { +ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payload) { var action = payload.action; var currentId; - switch(action.type) { - - case ActionTypes.CLICK_CHANNEL: - ChannelStore.setCurrentId(action.id); - ChannelStore.setLastVisitedName(action.name); - ChannelStore.resetCounts(action.id); - ChannelStore.emitChange(); - break; - - case ActionTypes.RECIEVED_CHANNELS: - ChannelStore.pStoreChannels(action.channels); - ChannelStore.pStoreChannelMembers(action.members); - currentId = ChannelStore.getCurrentId(); - if (currentId) { - ChannelStore.resetCounts(currentId); - } - ChannelStore.emitChange(); - break; - - case ActionTypes.RECIEVED_CHANNEL: - ChannelStore.pStoreChannel(action.channel); - ChannelStore.pStoreChannelMember(action.member); - currentId = ChannelStore.getCurrentId(); - if (currentId) { - ChannelStore.resetCounts(currentId); - } - ChannelStore.emitChange(); - break; + switch (action.type) { + case ActionTypes.CLICK_CHANNEL: + ChannelStore.setCurrentId(action.id); + ChannelStore.setLastVisitedName(action.name); + ChannelStore.resetCounts(action.id); + ChannelStore.emitChange(); + break; + + case ActionTypes.RECIEVED_CHANNELS: + ChannelStore.pStoreChannels(action.channels); + ChannelStore.pStoreChannelMembers(action.members); + currentId = ChannelStore.getCurrentId(); + if (currentId) { + ChannelStore.resetCounts(currentId); + } + ChannelStore.emitChange(); + break; + + case ActionTypes.RECIEVED_CHANNEL: + ChannelStore.pStoreChannel(action.channel); + ChannelStore.pStoreChannelMember(action.member); + currentId = ChannelStore.getCurrentId(); + if (currentId) { + ChannelStore.resetCounts(currentId); + } + ChannelStore.emitChange(); + break; - case ActionTypes.RECIEVED_MORE_CHANNELS: - ChannelStore.pStoreMoreChannels(action.channels); - ChannelStore.emitMoreChange(); - break; + case ActionTypes.RECIEVED_MORE_CHANNELS: + ChannelStore.pStoreMoreChannels(action.channels); + ChannelStore.emitMoreChange(); + break; - case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO: - var extraInfos = ChannelStore.pGetExtraInfos(); - extraInfos[action.extra_info.id] = action.extra_info; - ChannelStore.pStoreExtraInfos(extraInfos); - ChannelStore.emitExtraInfoChange(); - break; + case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO: + var extraInfos = ChannelStore.pGetExtraInfos(); + extraInfos[action.extra_info.id] = action.extra_info; + ChannelStore.pStoreExtraInfos(extraInfos); + ChannelStore.emitExtraInfoChange(); + break; - default: + default: + break; } }); -ChannelStore.setMaxListeners(11); -module.exports = ChannelStore; +export default ChannelStore; diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx index 7ff177b35..b397937be 100644 --- a/web/react/stores/config_store.jsx +++ b/web/react/stores/config_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var BrowserStore = require('../stores/browser_store.jsx'); @@ -12,45 +11,59 @@ var ActionTypes = Constants.ActionTypes; var CHANGE_EVENT = 'change'; -var ConfigStore = assign({}, EventEmitter.prototype, { - emitChange: function emitChange() { +class ConfigStoreClass extends EventEmitter { + constructor() { + super(); + + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.getSetting = this.getSetting.bind(this); + this.getSettingAsBoolean = this.getSettingAsBoolean.bind(this); + this.updateStoredSettings = this.updateStoredSettings.bind(this); + } + emitChange() { this.emit(CHANGE_EVENT); - }, - addChangeListener: function addChangeListener(callback) { + } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, - removeChangeListener: function removeChangeListener(callback) { + } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, - getSetting: function getSetting(key, defaultValue) { + } + getSetting(key, defaultValue) { return BrowserStore.getItem('config_' + key, defaultValue); - }, - getSettingAsBoolean: function getSettingAsNumber(key, defaultValue) { - var value = ConfigStore.getSetting(key, defaultValue); + } + getSettingAsBoolean(key, defaultValue) { + var value = this.getSetting(key, defaultValue); if (typeof value !== 'string') { - return !!value; - } else { - return value === 'true'; + return Boolean(value); } - }, - updateStoredSettings: function updateStoredSettings(settings) { - for (var key in settings) { - BrowserStore.setItem('config_' + key, settings[key]); + + return value === 'true'; + } + updateStoredSettings(settings) { + for (let key in settings) { + if (settings.hasOwnProperty(key)) { + BrowserStore.setItem('config_' + key, settings[key]); + } } } -}); +} + +var ConfigStore = new ConfigStoreClass(); ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_CONFIG: - ConfigStore.updateStoredSettings(action.settings); - ConfigStore.emitChange(); - break; - default: + case ActionTypes.RECIEVED_CONFIG: + ConfigStore.updateStoredSettings(action.settings); + ConfigStore.emitChange(); + break; + default: } }); -module.exports = ConfigStore; +export default ConfigStore; diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index 203b692ec..597c88cff 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; @@ -12,43 +11,53 @@ var BrowserStore = require('../stores/browser_store.jsx'); var CHANGE_EVENT = 'change'; -var ErrorStore = assign({}, EventEmitter.prototype, { - - emitChange: function() { - this.emit(CHANGE_EVENT); - }, - - addChangeListener: function(callback) { - this.on(CHANGE_EVENT, callback); - }, - - removeChangeListener: function(callback) { - this.removeListener(CHANGE_EVENT, callback); - }, - handledError: function() { - BrowserStore.removeItem("last_error"); - }, - getLastError: function() { - return BrowserStore.getItem('last_error'); - }, - - _storeLastError: function(error) { - BrowserStore.setItem("last_error", error); - }, -}); - -ErrorStore.dispatchToken = AppDispatcher.register(function(payload) { - var action = payload.action; - switch(action.type) { +class ErrorStoreClass extends EventEmitter { + constructor() { + super(); + + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.handledError = this.handledError.bind(this); + this.getLastError = this.getLastError.bind(this); + this.storeLastError = this.storeLastError.bind(this); + } + + emitChange() { + this.emit(CHANGE_EVENT); + } + + addChangeListener(callback) { + this.on(CHANGE_EVENT, callback); + } + + removeChangeListener(callback) { + this.removeListener(CHANGE_EVENT, callback); + } + handledError() { + BrowserStore.removeItem('last_error'); + } + getLastError() { + return BrowserStore.getItem('last_error'); + } + + storeLastError(error) { + BrowserStore.setItem('last_error', error); + } +} + +var ErrorStore = new ErrorStoreClass(); + +ErrorStore.dispatchToken = AppDispatcher.register(function registry(payload) { + var action = payload.action; + switch (action.type) { case ActionTypes.RECIEVED_ERROR: - ErrorStore._storeLastError(action.err); - ErrorStore.emitChange(); - break; + ErrorStore.storeLastError(action.err); + ErrorStore.emitChange(); + break; default: - } + } }); -module.exports = ErrorStore; - - +export default ErrorStore; diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 4038814d2..5ffe65021 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var ChannelStore = require('../stores/channel_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); @@ -18,109 +17,169 @@ var SELECTED_POST_CHANGE_EVENT = 'selected_post_change'; var MENTION_DATA_CHANGE_EVENT = 'mention_data_change'; var ADD_MENTION_EVENT = 'add_mention'; -var PostStore = assign({}, EventEmitter.prototype, { - emitChange: function emitChange() { +class PostStoreClass extends EventEmitter { + constructor() { + super(); + + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.emitSearchChange = this.emitSearchChange.bind(this); + this.addSearchChangeListener = this.addSearchChangeListener.bind(this); + this.removeSearchChangeListener = this.removeSearchChangeListener.bind(this); + this.emitSearchTermChange = this.emitSearchTermChange.bind(this); + this.addSearchTermChangeListener = this.addSearchTermChangeListener.bind(this); + this.removeSearchTermChangeListener = this.removeSearchTermChangeListener.bind(this); + this.emitSelectedPostChange = this.emitSelectedPostChange.bind(this); + this.addSelectedPostChangeListener = this.addSelectedPostChangeListener.bind(this); + this.removeSelectedPostChangeListener = this.removeSelectedPostChangeListener.bind(this); + this.emitMentionDataChange = this.emitMentionDataChange.bind(this); + this.addMentionDataChangeListener = this.addMentionDataChangeListener.bind(this); + this.removeMentionDataChangeListener = this.removeMentionDataChangeListener.bind(this); + this.emitAddMention = this.emitAddMention.bind(this); + this.addAddMentionListener = this.addAddMentionListener.bind(this); + this.removeAddMentionListener = this.removeAddMentionListener.bind(this); + this.getCurrentPosts = this.getCurrentPosts.bind(this); + this.storePosts = this.storePosts.bind(this); + this.pStorePosts = this.pStorePosts.bind(this); + this.getPosts = this.getPosts.bind(this); + this.storePost = this.storePost.bind(this); + this.pStorePost = this.pStorePost.bind(this); + this.removePost = this.removePost.bind(this); + this.storePendingPost = this.storePendingPost.bind(this); + this.pStorePendingPosts = this.pStorePendingPosts.bind(this); + this.getPendingPosts = this.getPendingPosts.bind(this); + this.storeUnseenDeletedPost = this.storeUnseenDeletedPost.bind(this); + this.storeUnseenDeletedPosts = this.storeUnseenDeletedPosts.bind(this); + this.getUnseenDeletedPosts = this.getUnseenDeletedPosts.bind(this); + this.clearUnseenDeletedPosts = this.clearUnseenDeletedPosts.bind(this); + this.removePendingPost = this.removePendingPost.bind(this); + this.pRemovePendingPost = this.pRemovePendingPost.bind(this); + this.clearPendingPosts = this.clearPendingPosts.bind(this); + this.updatePendingPost = this.updatePendingPost.bind(this); + this.storeSearchResults = this.storeSearchResults.bind(this); + this.getSearchResults = this.getSearchResults.bind(this); + this.getIsMentionSearch = this.getIsMentionSearch.bind(this); + this.storeSelectedPost = this.storeSelectedPost.bind(this); + this.getSelectedPost = this.getSelectedPost.bind(this); + this.storeSearchTerm = this.storeSearchTerm.bind(this); + this.getSearchTerm = this.getSearchTerm.bind(this); + this.getEmptyDraft = this.getEmptyDraft.bind(this); + this.storeCurrentDraft = this.storeCurrentDraft.bind(this); + this.getCurrentDraft = this.getCurrentDraft.bind(this); + this.storeDraft = this.storeDraft.bind(this); + this.getDraft = this.getDraft.bind(this); + this.storeCommentDraft = this.storeCommentDraft.bind(this); + this.getCommentDraft = this.getCommentDraft.bind(this); + this.clearDraftUploads = this.clearDraftUploads.bind(this); + this.clearCommentDraftUploads = this.clearCommentDraftUploads.bind(this); + this.storeLatestUpdate = this.storeLatestUpdate.bind(this); + this.getLatestUpdate = this.getLatestUpdate.bind(this); + } + emitChange() { this.emit(CHANGE_EVENT); - }, + } - addChangeListener: function addChangeListener(callback) { + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, + } - removeChangeListener: function removeChangeListener(callback) { + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, + } - emitSearchChange: function emitSearchChange() { + emitSearchChange() { this.emit(SEARCH_CHANGE_EVENT); - }, + } - addSearchChangeListener: function addSearchChangeListener(callback) { + addSearchChangeListener(callback) { this.on(SEARCH_CHANGE_EVENT, callback); - }, + } - removeSearchChangeListener: function removeSearchChangeListener(callback) { + removeSearchChangeListener(callback) { this.removeListener(SEARCH_CHANGE_EVENT, callback); - }, + } - emitSearchTermChange: function emitSearchTermChange(doSearch, isMentionSearch) { + emitSearchTermChange(doSearch, isMentionSearch) { this.emit(SEARCH_TERM_CHANGE_EVENT, doSearch, isMentionSearch); - }, + } - addSearchTermChangeListener: function addSearchTermChangeListener(callback) { + addSearchTermChangeListener(callback) { this.on(SEARCH_TERM_CHANGE_EVENT, callback); - }, + } - removeSearchTermChangeListener: function removeSearchTermChangeListener(callback) { + removeSearchTermChangeListener(callback) { this.removeListener(SEARCH_TERM_CHANGE_EVENT, callback); - }, + } - emitSelectedPostChange: function emitSelectedPostChange(fromSearch) { + emitSelectedPostChange(fromSearch) { this.emit(SELECTED_POST_CHANGE_EVENT, fromSearch); - }, + } - addSelectedPostChangeListener: function addSelectedPostChangeListener(callback) { + addSelectedPostChangeListener(callback) { this.on(SELECTED_POST_CHANGE_EVENT, callback); - }, + } - removeSelectedPostChangeListener: function removeSelectedPostChangeListener(callback) { + removeSelectedPostChangeListener(callback) { this.removeListener(SELECTED_POST_CHANGE_EVENT, callback); - }, + } - emitMentionDataChange: function emitMentionDataChange(id, mentionText) { + emitMentionDataChange(id, mentionText) { this.emit(MENTION_DATA_CHANGE_EVENT, id, mentionText); - }, + } - addMentionDataChangeListener: function addMentionDataChangeListener(callback) { + addMentionDataChangeListener(callback) { this.on(MENTION_DATA_CHANGE_EVENT, callback); - }, + } - removeMentionDataChangeListener: function removeMentionDataChangeListener(callback) { + removeMentionDataChangeListener(callback) { this.removeListener(MENTION_DATA_CHANGE_EVENT, callback); - }, + } - emitAddMention: function emitAddMention(id, username) { + emitAddMention(id, username) { this.emit(ADD_MENTION_EVENT, id, username); - }, + } - addAddMentionListener: function addAddMentionListener(callback) { + addAddMentionListener(callback) { this.on(ADD_MENTION_EVENT, callback); - }, + } - removeAddMentionListener: function removeAddMentionListener(callback) { + removeAddMentionListener(callback) { this.removeListener(ADD_MENTION_EVENT, callback); - }, + } - getCurrentPosts: function getCurrentPosts() { + getCurrentPosts() { var currentId = ChannelStore.getCurrentId(); if (currentId != null) { return this.getPosts(currentId); } return null; - }, - storePosts: function storePosts(channelId, newPostList) { + } + storePosts(channelId, newPostList) { if (isPostListNull(newPostList)) { return; } - var postList = makePostListNonNull(PostStore.getPosts(channelId)); - - for (var pid in newPostList.posts) { - var np = newPostList.posts[pid]; - if (np.delete_at === 0) { - postList.posts[pid] = np; - if (postList.order.indexOf(pid) === -1) { - postList.order.push(pid); - } - } else { - if (pid in postList.posts) { - delete postList.posts[pid]; - } - - var index = postList.order.indexOf(pid); - if (index !== -1) { - postList.order.splice(index, 1); + var postList = makePostListNonNull(this.getPosts(channelId)); + + for (let pid in newPostList.posts) { + if (newPostList.posts.hasOwnProperty(pid)) { + var np = newPostList.posts[pid]; + if (np.delete_at === 0) { + postList.posts[pid] = np; + if (postList.order.indexOf(pid) === -1) { + postList.order.push(pid); + } + } else { + if (pid in postList.posts) { + delete postList.posts[pid]; + } + + var index = postList.order.indexOf(pid); + if (index !== -1) { + postList.order.splice(index, 1); + } } } } @@ -146,19 +205,19 @@ var PostStore = assign({}, EventEmitter.prototype, { this.storeLatestUpdate(channelId, latestUpdate); this.pStorePosts(channelId, postList); this.emitChange(); - }, - pStorePosts: function pStorePosts(channelId, posts) { + } + pStorePosts(channelId, posts) { BrowserStore.setItem('posts_' + channelId, posts); - }, - getPosts: function getPosts(channelId) { + } + getPosts(channelId) { return BrowserStore.getItem('posts_' + channelId); - }, - storePost: function(post) { + } + storePost(post) { this.pStorePost(post); this.emitChange(); - }, - pStorePost: function(post) { - var postList = PostStore.getPosts(post.channel_id); + } + pStorePost(post) { + var postList = this.getPosts(post.channel_id); postList = makePostListNonNull(postList); if (post.pending_post_id !== '') { @@ -173,9 +232,9 @@ var PostStore = assign({}, EventEmitter.prototype, { } this.pStorePosts(post.channel_id, postList); - }, - removePost: function(postId, channelId) { - var postList = PostStore.getPosts(channelId); + } + removePost(postId, channelId) { + var postList = this.getPosts(channelId); if (isPostListNull(postList)) { return; } @@ -190,8 +249,8 @@ var PostStore = assign({}, EventEmitter.prototype, { } this.pStorePosts(channelId, postList); - }, - storePendingPost: function(post) { + } + storePendingPost(post) { post.state = Constants.POST_LOADING; var postList = this.getPendingPosts(post.channel_id); @@ -199,10 +258,10 @@ var PostStore = assign({}, EventEmitter.prototype, { postList.posts[post.pending_post_id] = post; postList.order.unshift(post.pending_post_id); - this._storePendingPosts(post.channel_id, postList); + this.pStorePendingPosts(post.channel_id, postList); this.emitChange(); - }, - _storePendingPosts: function(channelId, postList) { + } + pStorePendingPosts(channelId, postList) { var posts = postList.posts; // sort failed posts to the bottom @@ -225,11 +284,11 @@ var PostStore = assign({}, EventEmitter.prototype, { }); BrowserStore.setItem('pending_posts_' + channelId, postList); - }, - getPendingPosts: function(channelId) { + } + getPendingPosts(channelId) { return BrowserStore.getItem('pending_posts_' + channelId); - }, - storeUnseenDeletedPost: function(post) { + } + storeUnseenDeletedPost(post) { var posts = this.getUnseenDeletedPosts(post.channel_id); if (!posts) { @@ -241,21 +300,21 @@ var PostStore = assign({}, EventEmitter.prototype, { posts[post.id] = post; this.storeUnseenDeletedPosts(post.channel_id, posts); - }, - storeUnseenDeletedPosts: function(channelId, posts) { + } + storeUnseenDeletedPosts(channelId, posts) { BrowserStore.setItem('deleted_posts_' + channelId, posts); - }, - getUnseenDeletedPosts: function(channelId) { + } + getUnseenDeletedPosts(channelId) { return BrowserStore.getItem('deleted_posts_' + channelId); - }, - clearUnseenDeletedPosts: function(channelId) { + } + clearUnseenDeletedPosts(channelId) { BrowserStore.setItem('deleted_posts_' + channelId, {}); - }, - removePendingPost: function(channelId, pendingPostId) { - this._removePendingPost(channelId, pendingPostId); + } + removePendingPost(channelId, pendingPostId) { + this.pRemovePendingPost(channelId, pendingPostId); this.emitChange(); - }, - _removePendingPost: function(channelId, pendingPostId) { + } + pRemovePendingPost(channelId, pendingPostId) { var postList = this.getPendingPosts(channelId); postList = makePostListNonNull(postList); @@ -267,14 +326,14 @@ var PostStore = assign({}, EventEmitter.prototype, { postList.order.splice(index, 1); } - this._storePendingPosts(channelId, postList); - }, - clearPendingPosts: function() { + this.pStorePendingPosts(channelId, postList); + } + clearPendingPosts() { BrowserStore.actionOnItemsWithPrefix('pending_posts_', function clearPending(key) { BrowserStore.removeItem(key); }); - }, - updatePendingPost: function(post) { + } + updatePendingPost(post) { var postList = this.getPendingPosts(post.channel_id); postList = makePostListNonNull(postList); @@ -283,112 +342,114 @@ var PostStore = assign({}, EventEmitter.prototype, { } postList.posts[post.pending_post_id] = post; - this._storePendingPosts(post.channel_id, postList); + this.pStorePendingPosts(post.channel_id, postList); this.emitChange(); - }, - storeSearchResults: function storeSearchResults(results, isMentionSearch) { + } + storeSearchResults(results, isMentionSearch) { BrowserStore.setItem('search_results', results); BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch)); - }, - getSearchResults: function getSearchResults() { + } + getSearchResults() { return BrowserStore.getItem('search_results'); - }, - getIsMentionSearch: function getIsMentionSearch() { + } + getIsMentionSearch() { return BrowserStore.getItem('is_mention_search'); - }, - storeSelectedPost: function storeSelectedPost(postList) { + } + storeSelectedPost(postList) { BrowserStore.setItem('select_post', postList); - }, - getSelectedPost: function getSelectedPost() { + } + getSelectedPost() { return BrowserStore.getItem('select_post'); - }, - storeSearchTerm: function storeSearchTerm(term) { + } + storeSearchTerm(term) { BrowserStore.setItem('search_term', term); - }, - getSearchTerm: function getSearchTerm() { + } + getSearchTerm() { return BrowserStore.getItem('search_term'); - }, - getEmptyDraft: function getEmptyDraft(draft) { + } + getEmptyDraft() { return {message: '', uploadsInProgress: [], previews: []}; - }, - storeCurrentDraft: function storeCurrentDraft(draft) { + } + storeCurrentDraft(draft) { var channelId = ChannelStore.getCurrentId(); BrowserStore.setItem('draft_' + channelId, draft); - }, - getCurrentDraft: function getCurrentDraft() { + } + getCurrentDraft() { var channelId = ChannelStore.getCurrentId(); - return PostStore.getDraft(channelId); - }, - storeDraft: function storeDraft(channelId, draft) { + return this.getDraft(channelId); + } + storeDraft(channelId, draft) { BrowserStore.setItem('draft_' + channelId, draft); - }, - getDraft: function getDraft(channelId) { - return BrowserStore.getItem('draft_' + channelId, PostStore.getEmptyDraft()); - }, - storeCommentDraft: function storeCommentDraft(parentPostId, draft) { + } + getDraft(channelId) { + return BrowserStore.getItem('draft_' + channelId, this.getEmptyDraft()); + } + storeCommentDraft(parentPostId, draft) { BrowserStore.setItem('comment_draft_' + parentPostId, draft); - }, - getCommentDraft: function getCommentDraft(parentPostId) { - return BrowserStore.getItem('comment_draft_' + parentPostId, PostStore.getEmptyDraft()); - }, - clearDraftUploads: function clearDraftUploads() { + } + getCommentDraft(parentPostId) { + return BrowserStore.getItem('comment_draft_' + parentPostId, this.getEmptyDraft()); + } + clearDraftUploads() { BrowserStore.actionOnItemsWithPrefix('draft_', function clearUploads(key, value) { if (value) { value.uploadsInProgress = []; BrowserStore.setItem(key, value); } }); - }, - clearCommentDraftUploads: function clearCommentDraftUploads() { + } + clearCommentDraftUploads() { BrowserStore.actionOnItemsWithPrefix('comment_draft_', function clearUploads(key, value) { if (value) { value.uploadsInProgress = []; BrowserStore.setItem(key, value); } }); - }, - storeLatestUpdate: function(channelId, time) { + } + storeLatestUpdate(channelId, time) { BrowserStore.setItem('latest_post_' + channelId, time); - }, - getLatestUpdate: function(channelId) { + } + getLatestUpdate(channelId) { return BrowserStore.getItem('latest_post_' + channelId, 0); } -}); +} + +var PostStore = new PostStoreClass(); PostStore.dispatchToken = AppDispatcher.register(function registry(payload) { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_POSTS: - PostStore.storePosts(action.id, makePostListNonNull(action.post_list)); - break; - case ActionTypes.RECIEVED_POST: - PostStore.pStorePost(action.post); - PostStore.emitChange(); - break; - case ActionTypes.RECIEVED_SEARCH: - PostStore.storeSearchResults(action.results, action.is_mention_search); - PostStore.emitSearchChange(); - break; - case ActionTypes.RECIEVED_SEARCH_TERM: - PostStore.storeSearchTerm(action.term); - PostStore.emitSearchTermChange(action.do_search, action.is_mention_search); - break; - case ActionTypes.RECIEVED_POST_SELECTED: - PostStore.storeSelectedPost(action.post_list); - PostStore.emitSelectedPostChange(action.from_search); - break; - case ActionTypes.RECIEVED_MENTION_DATA: - PostStore.emitMentionDataChange(action.id, action.mention_text); - break; - case ActionTypes.RECIEVED_ADD_MENTION: - PostStore.emitAddMention(action.id, action.username); - break; - default: + case ActionTypes.RECIEVED_POSTS: + PostStore.storePosts(action.id, makePostListNonNull(action.post_list)); + break; + case ActionTypes.RECIEVED_POST: + PostStore.pStorePost(action.post); + PostStore.emitChange(); + break; + case ActionTypes.RECIEVED_SEARCH: + PostStore.storeSearchResults(action.results, action.is_mention_search); + PostStore.emitSearchChange(); + break; + case ActionTypes.RECIEVED_SEARCH_TERM: + PostStore.storeSearchTerm(action.term); + PostStore.emitSearchTermChange(action.do_search, action.is_mention_search); + break; + case ActionTypes.RECIEVED_POST_SELECTED: + PostStore.storeSelectedPost(action.post_list); + PostStore.emitSelectedPostChange(action.from_search); + break; + case ActionTypes.RECIEVED_MENTION_DATA: + PostStore.emitMentionDataChange(action.id, action.mention_text); + break; + case ActionTypes.RECIEVED_ADD_MENTION: + PostStore.emitAddMention(action.id, action.username); + break; + default: } }); -module.exports = PostStore; +export default PostStore; function makePostListNonNull(pl) { var postList = pl; diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index e43a8f2be..ae74059d1 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -2,10 +2,8 @@ // See License.txt for license information. var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var UserStore = require('./user_store.jsx') +var UserStore = require('./user_store.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); -var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; @@ -14,14 +12,24 @@ var CHANGE_EVENT = 'change'; var conn; -var SocketStore = assign({}, EventEmitter.prototype, { - initialize: function() { +class SocketStoreClass extends EventEmitter { + constructor() { + super(); + + this.initialize = this.initialize.bind(this); + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.sendMessage = this.sendMessage.bind(this); + + this.initialize(); + } + initialize() { if (!UserStore.getCurrentId()) { return; } - var self = this; - self.setMaxListeners(0); + this.setMaxListeners(0); if (window.WebSocket && !conn) { var protocol = 'ws://'; @@ -29,24 +37,24 @@ var SocketStore = assign({}, EventEmitter.prototype, { protocol = 'wss://'; } var connUrl = protocol + location.host + '/api/v1/websocket'; - console.log('connecting to ' + connUrl); + console.log('connecting to ' + connUrl); //eslint-disable-line no-console conn = new WebSocket(connUrl); conn.onclose = function closeConn(evt) { - console.log('websocket closed'); - console.log(evt); + console.log('websocket closed'); //eslint-disable-line no-console + console.log(evt); //eslint-disable-line no-console conn = null; setTimeout( function reconnect() { - self.initialize(); - }, + this.initialize(); + }.bind(this), 3000 ); - }; + }.bind(this); conn.onerror = function connError(evt) { - console.log('websocket error'); - console.log(evt); + console.log('websocket error'); //eslint-disable-line no-console + console.log(evt); //eslint-disable-line no-console }; conn.onmessage = function connMessage(evt) { @@ -56,17 +64,17 @@ var SocketStore = assign({}, EventEmitter.prototype, { }); }; } - }, - emitChange: function(msg) { + } + emitChange(msg) { this.emit(CHANGE_EVENT, msg); - }, - addChangeListener: function(callback) { + } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, - removeChangeListener: function(callback) { + } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, - sendMessage: function(msg) { + } + sendMessage(msg) { if (conn && conn.readyState === WebSocket.OPEN) { conn.send(JSON.stringify(msg)); } else if (!conn || conn.readyState === WebSocket.Closed) { @@ -74,19 +82,20 @@ var SocketStore = assign({}, EventEmitter.prototype, { this.initialize(); } } -}); +} + +var SocketStore = new SocketStoreClass(); -SocketStore.dispatchToken = AppDispatcher.register(function(payload) { +SocketStore.dispatchToken = AppDispatcher.register(function registry(payload) { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_MSG: + case ActionTypes.RECIEVED_MSG: SocketStore.emitChange(action.msg); break; - default: + default: } }); -SocketStore.initialize(); -module.exports = SocketStore; +export default SocketStore; diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index 3f2248c44..1f33fe03b 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; @@ -19,21 +18,38 @@ function getWindowLocationOrigin() { return utils.getWindowLocationOrigin(); } -var TeamStore = assign({}, EventEmitter.prototype, { - emitChange: function() { +class TeamStoreClass extends EventEmitter { + constructor() { + super(); + + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.get = this.get.bind(this); + this.getByName = this.getByName.bind(this); + this.getAll = this.getAll.bind(this); + this.setCurrentId = this.setCurrentId.bind(this); + this.getCurrentId = this.getCurrentId.bind(this); + this.getCurrent = this.getCurrent.bind(this); + this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this); + this.storeTeam = this.storeTeam.bind(this); + this.pStoreTeams = this.pStoreTeams.bind(this); + this.pGetTeams = this.pGetTeams.bind(this); + } + emitChange() { this.emit(CHANGE_EVENT); - }, - addChangeListener: function(callback) { + } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, - removeChangeListener: function(callback) { + } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, - get: function(id) { + } + get(id) { var c = this.pGetTeams(); return c[id]; - }, - getByName: function(name) { + } + getByName(name) { var t = this.pGetTeams(); for (var id in t) { @@ -43,64 +59,65 @@ var TeamStore = assign({}, EventEmitter.prototype, { } return null; - }, - getAll: function() { + } + getAll() { return this.pGetTeams(); - }, - setCurrentId: function(id) { + } + setCurrentId(id) { if (id === null) { BrowserStore.removeItem('current_team_id'); } else { BrowserStore.setItem('current_team_id', id); } - }, - getCurrentId: function() { + } + getCurrentId() { return BrowserStore.getItem('current_team_id'); - }, - getCurrent: function() { - var currentId = TeamStore.getCurrentId(); + } + getCurrent() { + var currentId = this.getCurrentId(); if (currentId !== null) { return this.get(currentId); } return null; - }, - getCurrentTeamUrl: function() { + } + getCurrentTeamUrl() { if (this.getCurrent()) { return getWindowLocationOrigin() + '/' + this.getCurrent().name; } return null; - }, - storeTeam: function(team) { + } + storeTeam(team) { var teams = this.pGetTeams(); teams[team.id] = team; this.pStoreTeams(teams); - }, - pStoreTeams: function(teams) { + } + pStoreTeams(teams) { BrowserStore.setItem('user_teams', teams); - }, - pGetTeams: function() { + } + pGetTeams() { return BrowserStore.getItem('user_teams', {}); } -}); +} + +var TeamStore = new TeamStoreClass(); TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) { var action = payload.action; switch (action.type) { + case ActionTypes.CLICK_TEAM: + TeamStore.setCurrentId(action.id); + TeamStore.emitChange(); + break; - case ActionTypes.CLICK_TEAM: - TeamStore.setCurrentId(action.id); - TeamStore.emitChange(); - break; - - case ActionTypes.RECIEVED_TEAM: - TeamStore.storeTeam(action.team); - TeamStore.emitChange(); - break; + case ActionTypes.RECIEVED_TEAM: + TeamStore.storeTeam(action.team); + TeamStore.emitChange(); + break; - default: + default: } }); -module.exports = TeamStore; +export default TeamStore; diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index 248495dac..f75c1d4c3 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -3,7 +3,6 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var EventEmitter = require('events').EventEmitter; -var assign = require('object-assign'); var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); @@ -16,64 +15,114 @@ var CHANGE_EVENT_AUDITS = 'change_audits'; var CHANGE_EVENT_TEAMS = 'change_teams'; var CHANGE_EVENT_STATUSES = 'change_statuses'; -var UserStore = assign({}, EventEmitter.prototype, { +class UserStoreClass extends EventEmitter { + constructor() { + super(); - gCurrentId: null, + this.emitChange = this.emitChange.bind(this); + this.addChangeListener = this.addChangeListener.bind(this); + this.removeChangeListener = this.removeChangeListener.bind(this); + this.emitSessionsChange = this.emitSessionsChange.bind(this); + this.addSessionsChangeListener = this.addSessionsChangeListener.bind(this); + this.removeSessionsChangeListener = this.removeSessionsChangeListener.bind(this); + this.emitAuditsChange = this.emitAuditsChange.bind(this); + this.addAuditsChangeListener = this.addAuditsChangeListener.bind(this); + this.removeAuditsChangeListener = this.removeAuditsChangeListener.bind(this); + this.emitTeamsChange = this.emitTeamsChange.bind(this); + this.addTeamsChangeListener = this.addTeamsChangeListener.bind(this); + this.removeTeamsChangeListener = this.removeTeamsChangeListener.bind(this); + this.emitStatusesChange = this.emitStatusesChange.bind(this); + this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this); + this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this); + this.setCurrentId = this.setCurrentId.bind(this); + this.getCurrentId = this.getCurrentId.bind(this); + this.getCurrentUser = this.getCurrentUser.bind(this); + this.setCurrentUser = this.setCurrentUser.bind(this); + this.getLastEmail = this.getLastEmail.bind(this); + this.setLastEmail = this.setLastEmail.bind(this); + this.removeCurrentUser = this.removeCurrentUser.bind(this); + this.hasProfile = this.hasProfile.bind(this); + this.getProfile = this.getProfile.bind(this); + this.getProfileByUsername = this.getProfileByUsername.bind(this); + this.getProfilesUsernameMap = this.getProfilesUsernameMap.bind(this); + this.getProfiles = this.getProfiles.bind(this); + this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this); + this.saveProfile = this.saveProfile.bind(this); + this.pStoreProfiles = this.pStoreProfiles.bind(this); + this.pGetProfiles = this.pGetProfiles.bind(this); + this.pGetProfilesUsernameMap = this.pGetProfilesUsernameMap.bind(this); + this.setSessions = this.setSessions.bind(this); + this.getSessions = this.getSessions.bind(this); + this.setAudits = this.setAudits.bind(this); + this.getAudits = this.getAudits.bind(this); + this.setTeams = this.setTeams.bind(this); + this.getTeams = this.getTeams.bind(this); + this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this); + this.getLastVersion = this.getLastVersion.bind(this); + this.setLastVersion = this.setLastVersion.bind(this); + this.setStatuses = this.setStatuses.bind(this); + this.pSetStatuses = this.pSetStatuses.bind(this); + this.setStatus = this.setStatus.bind(this); + this.getStatuses = this.getStatuses.bind(this); + this.getStatus = this.getStatus.bind(this); - emitChange: function(userId) { + this.gCurrentId = null; + } + + emitChange(userId) { this.emit(CHANGE_EVENT, userId); - }, - addChangeListener: function(callback) { + } + addChangeListener(callback) { this.on(CHANGE_EVENT, callback); - }, - removeChangeListener: function(callback) { + } + removeChangeListener(callback) { this.removeListener(CHANGE_EVENT, callback); - }, - emitSessionsChange: function() { + } + emitSessionsChange() { this.emit(CHANGE_EVENT_SESSIONS); - }, - addSessionsChangeListener: function(callback) { + } + addSessionsChangeListener(callback) { this.on(CHANGE_EVENT_SESSIONS, callback); - }, - removeSessionsChangeListener: function(callback) { + } + removeSessionsChangeListener(callback) { this.removeListener(CHANGE_EVENT_SESSIONS, callback); - }, - emitAuditsChange: function() { + } + emitAuditsChange() { this.emit(CHANGE_EVENT_AUDITS); - }, - addAuditsChangeListener: function(callback) { + } + addAuditsChangeListener(callback) { this.on(CHANGE_EVENT_AUDITS, callback); - }, - removeAuditsChangeListener: function(callback) { + } + removeAuditsChangeListener(callback) { this.removeListener(CHANGE_EVENT_AUDITS, callback); - }, - emitTeamsChange: function() { + } + emitTeamsChange() { this.emit(CHANGE_EVENT_TEAMS); - }, - addTeamsChangeListener: function(callback) { + } + addTeamsChangeListener(callback) { this.on(CHANGE_EVENT_TEAMS, callback); - }, - removeTeamsChangeListener: function(callback) { + } + removeTeamsChangeListener(callback) { this.removeListener(CHANGE_EVENT_TEAMS, callback); - }, - emitStatusesChange: function() { + } + emitStatusesChange() { this.emit(CHANGE_EVENT_STATUSES); - }, - addStatusesChangeListener: function(callback) { + } + addStatusesChangeListener(callback) { this.on(CHANGE_EVENT_STATUSES, callback); - }, - removeStatusesChangeListener: function(callback) { + } + removeStatusesChangeListener(callback) { this.removeListener(CHANGE_EVENT_STATUSES, callback); - }, - setCurrentId: function(id) { + } + setCurrentId(id) { this.gCurrentId = id; if (id == null) { BrowserStore.removeGlobalItem('current_user_id'); } else { BrowserStore.setGlobalItem('current_user_id', id); } - }, - getCurrentId: function(skipFetch) { + } + getCurrentId(skipFetch) { var currentId = this.gCurrentId; if (currentId == null) { @@ -93,46 +142,45 @@ var UserStore = assign({}, EventEmitter.prototype, { } return currentId; - }, - getCurrentUser: function() { + } + getCurrentUser() { if (this.getCurrentId() == null) { return null; } - return this._getProfiles()[this.getCurrentId()]; - }, - setCurrentUser: function(user) { + return this.pGetProfiles()[this.getCurrentId()]; + } + setCurrentUser(user) { this.setCurrentId(user.id); this.saveProfile(user); - }, - getLastEmail: function() { + } + getLastEmail() { return BrowserStore.getItem('last_email', ''); - }, - setLastEmail: function(email) { + } + setLastEmail(email) { BrowserStore.setItem('last_email', email); - }, - removeCurrentUser: function() { + } + removeCurrentUser() { this.setCurrentId(null); - }, - hasProfile: function(userId) { - return this._getProfiles()[userId] != null; - }, - getProfile: function(userId) { - return this._getProfiles()[userId]; - }, - getProfileByUsername: function(username) { - return this._getProfilesUsernameMap()[username]; - }, - getProfilesUsernameMap: function() { - return this._getProfilesUsernameMap(); - }, - getProfiles: function() { - - return this._getProfiles(); - }, - getActiveOnlyProfiles: function() { + } + hasProfile(userId) { + return this.pGetProfiles()[userId] != null; + } + getProfile(userId) { + return this.pGetProfiles()[userId]; + } + getProfileByUsername(username) { + return this.pGetProfilesUsernameMap()[username]; + } + getProfilesUsernameMap() { + return this.pGetProfilesUsernameMap(); + } + getProfiles() { + return this.pGetProfiles(); + } + getActiveOnlyProfiles() { var active = {}; - var current = this._getProfiles(); + var current = this.pGetProfiles(); for (var key in current) { if (current[key].delete_at === 0) { @@ -141,45 +189,47 @@ var UserStore = assign({}, EventEmitter.prototype, { } return active; - }, - saveProfile: function(profile) { - var ps = this._getProfiles(); + } + saveProfile(profile) { + var ps = this.pGetProfiles(); ps[profile.id] = profile; - this._storeProfiles(ps); - }, - _storeProfiles: function(profiles) { + this.pStoreProfiles(ps); + } + pStoreProfiles(profiles) { BrowserStore.setItem('profiles', profiles); var profileUsernameMap = {}; for (var id in profiles) { - profileUsernameMap[profiles[id].username] = profiles[id]; + if (profiles.hasOwnProperty(id)) { + profileUsernameMap[profiles[id].username] = profiles[id]; + } } BrowserStore.setItem('profileUsernameMap', profileUsernameMap); - }, - _getProfiles: function() { + } + pGetProfiles() { return BrowserStore.getItem('profiles', {}); - }, - _getProfilesUsernameMap: function() { + } + pGetProfilesUsernameMap() { return BrowserStore.getItem('profileUsernameMap', {}); - }, - setSessions: function(sessions) { + } + setSessions(sessions) { BrowserStore.setItem('sessions', sessions); - }, - getSessions: function() { + } + getSessions() { return BrowserStore.getItem('sessions', {loading: true}); - }, - setAudits: function(audits) { + } + setAudits(audits) { BrowserStore.setItem('audits', audits); - }, - getAudits: function() { + } + getAudits() { return BrowserStore.getItem('audits', {loading: true}); - }, - setTeams: function(teams) { + } + setTeams(teams) { BrowserStore.setItem('teams', teams); - }, - getTeams: function() { + } + getTeams() { return BrowserStore.getItem('teams', []); - }, - getCurrentMentionKeys: function() { + } + getCurrentMentionKeys() { var user = this.getCurrentUser(); var keys = []; @@ -205,74 +255,76 @@ var UserStore = assign({}, EventEmitter.prototype, { } return keys; - }, - getLastVersion: function() { + } + getLastVersion() { return BrowserStore.getItem('last_version', ''); - }, - setLastVersion: function(version) { + } + setLastVersion(version) { BrowserStore.setItem('last_version', version); - }, - setStatuses: function(statuses) { - this._setStatuses(statuses); + } + setStatuses(statuses) { + this.pSetStatuses(statuses); this.emitStatusesChange(); - }, - _setStatuses: function(statuses) { + } + pSetStatuses(statuses) { BrowserStore.setItem('statuses', statuses); - }, - setStatus: function(userId, status) { + } + setStatus(userId, status) { var statuses = this.getStatuses(); statuses[userId] = status; - this._setStatuses(statuses); + this.pSetStatuses(statuses); this.emitStatusesChange(); - }, - getStatuses: function() { + } + getStatuses() { return BrowserStore.getItem('statuses', {}); - }, - getStatus: function(id) { + } + getStatus(id) { return this.getStatuses()[id]; } -}); +} + +var UserStore = new UserStoreClass(); +UserStore.setMaxListeners(0); -UserStore.dispatchToken = AppDispatcher.register(function(payload) { +UserStore.dispatchToken = AppDispatcher.register(function registry(payload) { var action = payload.action; switch (action.type) { - case ActionTypes.RECIEVED_PROFILES: - for (var id in action.profiles) { - // profiles can have incomplete data, so don't overwrite current user - if (id === UserStore.getCurrentId()) { - continue; - } - var profile = action.profiles[id]; - UserStore.saveProfile(profile); - UserStore.emitChange(profile.id); + case ActionTypes.RECIEVED_PROFILES: + for (var id in action.profiles) { + // profiles can have incomplete data, so don't overwrite current user + if (id === UserStore.getCurrentId()) { + continue; } - break; - case ActionTypes.RECIEVED_ME: - UserStore.setCurrentUser(action.me); - UserStore.emitChange(action.me.id); - break; - case ActionTypes.RECIEVED_SESSIONS: - UserStore.setSessions(action.sessions); - UserStore.emitSessionsChange(); - break; - case ActionTypes.RECIEVED_AUDITS: - UserStore.setAudits(action.audits); - UserStore.emitAuditsChange(); - break; - case ActionTypes.RECIEVED_TEAMS: - UserStore.setTeams(action.teams); - UserStore.emitTeamsChange(); - break; - case ActionTypes.RECIEVED_STATUSES: - UserStore._setStatuses(action.statuses); - UserStore.emitStatusesChange(); - break; + var profile = action.profiles[id]; + UserStore.saveProfile(profile); + UserStore.emitChange(profile.id); + } + break; + case ActionTypes.RECIEVED_ME: + UserStore.setCurrentUser(action.me); + UserStore.emitChange(action.me.id); + break; + case ActionTypes.RECIEVED_SESSIONS: + UserStore.setSessions(action.sessions); + UserStore.emitSessionsChange(); + break; + case ActionTypes.RECIEVED_AUDITS: + UserStore.setAudits(action.audits); + UserStore.emitAuditsChange(); + break; + case ActionTypes.RECIEVED_TEAMS: + UserStore.setTeams(action.teams); + UserStore.emitTeamsChange(); + break; + case ActionTypes.RECIEVED_STATUSES: + UserStore.pSetStatuses(action.statuses); + UserStore.emitStatusesChange(); + break; - default: + default: } }); -UserStore.setMaxListeners(0); global.window.UserStore = UserStore; -module.exports = UserStore; +export default UserStore; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index c03a0230b..6ccef0506 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -4,7 +4,6 @@ var client = require('./client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); -var ConfigStore = require('../stores/config_store.jsx'); var PostStore = require('../stores/post_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var utils = require('./utils.jsx'); @@ -15,14 +14,13 @@ var ActionTypes = Constants.ActionTypes; // Used to track in progress async calls var callTracker = {}; -function dispatchError(err, method) { +export function dispatchError(err, method) { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ERROR, err: err, method: method }); } -module.exports.dispatchError = dispatchError; function isCallInProgress(callName) { if (!(callName in callTracker)) { @@ -34,14 +32,14 @@ function isCallInProgress(callName) { } if (utils.getTimestamp() - callTracker[callName] > 5000) { - console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds'); + //console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds'); return false; } return true; } -function getChannels(force, updateLastViewed, checkVersion) { +export function getChannels(force, updateLastViewed, checkVersion) { var channels = ChannelStore.getAll(); if (channels.length === 0 || force) { @@ -52,7 +50,7 @@ function getChannels(force, updateLastViewed, checkVersion) { callTracker.getChannels = utils.getTimestamp(); client.getChannels( - function(data, textStatus, xhr) { + function getChannelsSuccess(data, textStatus, xhr) { callTracker.getChannels = 0; if (checkVersion) { @@ -65,7 +63,7 @@ function getChannels(force, updateLastViewed, checkVersion) { if (serverVersion !== UserStore.getLastVersion()) { UserStore.setLastVersion(serverVersion); window.location.href = window.location.href; - console.log('Detected version update refreshing the page'); + console.log('Detected version update refreshing the page'); //eslint-disable-line no-console } } @@ -79,7 +77,7 @@ function getChannels(force, updateLastViewed, checkVersion) { members: data.members }); }, - function(err) { + function getChannelsFailure(err) { callTracker.getChannels = 0; dispatchError(err, 'getChannels'); } @@ -92,7 +90,7 @@ function getChannels(force, updateLastViewed, checkVersion) { callTracker.getChannelCounts = utils.getTimestamp(); client.getChannelCounts( - function(data, textStatus, xhr) { + function getChannelCountsSuccess(data, textStatus, xhr) { callTracker.getChannelCounts = 0; if (xhr.status === 304 || !data) { @@ -103,15 +101,17 @@ function getChannels(force, updateLastViewed, checkVersion) { var updateAtMap = data.update_times; for (var id in countMap) { - var c = ChannelStore.get(id); - var count = countMap[id]; - var updateAt = updateAtMap[id]; - if (!c || c.total_msg_count !== count || updateAt > c.update_at) { - getChannel(id); + if ({}.hasOwnProperty.call(countMap, id)) { + var c = ChannelStore.get(id); + var count = countMap[id]; + var updateAt = updateAtMap[id]; + if (!c || c.total_msg_count !== count || updateAt > c.update_at) { + getChannel(id); + } } } }, - function(err) { + function getChannelCountsFailure(err) { callTracker.getChannelCounts = 0; dispatchError(err, 'getChannelCounts'); } @@ -119,12 +119,11 @@ function getChannels(force, updateLastViewed, checkVersion) { } if (updateLastViewed && ChannelStore.getCurrentId() != null) { - module.exports.updateLastViewedAt(); + updateLastViewedAt(); } } -module.exports.getChannels = getChannels; -function getChannel(id) { +export function getChannel(id) { if (isCallInProgress('getChannel' + id)) { return; } @@ -132,7 +131,7 @@ function getChannel(id) { callTracker['getChannel' + id] = utils.getTimestamp(); client.getChannel(id, - function(data, textStatus, xhr) { + function getChannelSuccess(data, textStatus, xhr) { callTracker['getChannel' + id] = 0; if (xhr.status === 304 || !data) { @@ -145,43 +144,49 @@ function getChannel(id) { member: data.member }); }, - function(err) { + function getChannelFailure(err) { callTracker['getChannel' + id] = 0; dispatchError(err, 'getChannel'); } ); } -module.exports.getChannel = getChannel; -module.exports.updateLastViewedAt = function() { - if (isCallInProgress('updateLastViewed')) return; +export function updateLastViewedAt() { + if (isCallInProgress('updateLastViewed')) { + return; + } - if (ChannelStore.getCurrentId() == null) return; + if (ChannelStore.getCurrentId() == null) { + return; + } - callTracker['updateLastViewed'] = utils.getTimestamp(); + callTracker.updateLastViewed = utils.getTimestamp(); client.updateLastViewedAt( ChannelStore.getCurrentId(), - function(data) { - callTracker['updateLastViewed'] = 0; + function updateLastViewedAtSuccess() { + callTracker.updateLastViewed = 0; }, - function(err) { - callTracker['updateLastViewed'] = 0; + function updateLastViewdAtFailure(err) { + callTracker.updateLastViewed = 0; dispatchError(err, 'updateLastViewedAt'); } ); } -module.exports.getMoreChannels = function(force) { - if (isCallInProgress('getMoreChannels')) return; +export function getMoreChannels(force) { + if (isCallInProgress('getMoreChannels')) { + return; + } if (ChannelStore.getMoreAll().loading || force) { - - callTracker['getMoreChannels'] = utils.getTimestamp(); + callTracker.getMoreChannels = utils.getTimestamp(); client.getMoreChannels( - function(data, textStatus, xhr) { - callTracker['getMoreChannels'] = 0; + function getMoreChannelsSuccess(data, textStatus, xhr) { + callTracker.getMoreChannels = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_MORE_CHANNELS, @@ -189,37 +194,44 @@ module.exports.getMoreChannels = function(force) { members: data.members }); }, - function(err) { - callTracker['getMoreChannels'] = 0; + function getMoreChannelsFailure(err) { + callTracker.getMoreChannels = 0; dispatchError(err, 'getMoreChannels'); } ); } } -module.exports.getChannelExtraInfo = function(force) { +export function getChannelExtraInfo(force) { var channelId = ChannelStore.getCurrentId(); if (channelId != null) { - if (isCallInProgress('getChannelExtraInfo_'+channelId)) return; - var minMembers = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D' ? 1 : 0; + if (isCallInProgress('getChannelExtraInfo_' + channelId)) { + return; + } + var minMembers = 0; + if (ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D') { + minMembers = 1; + } if (ChannelStore.getCurrentExtraInfo().members.length <= minMembers || force) { - callTracker['getChannelExtraInfo_'+channelId] = utils.getTimestamp(); + callTracker['getChannelExtraInfo_' + channelId] = utils.getTimestamp(); client.getChannelExtraInfo( channelId, - function(data, textStatus, xhr) { - callTracker['getChannelExtraInfo_'+channelId] = 0; + function getChannelExtraInfoSuccess(data, textStatus, xhr) { + callTracker['getChannelExtraInfo_' + channelId] = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO, extra_info: data }); }, - function(err) { - callTracker['getChannelExtraInfo_'+channelId] = 0; + function getChannelExtraInfoFailure(err) { + callTracker['getChannelExtraInfo_' + channelId] = 0; dispatchError(err, 'getChannelExtraInfo'); } ); @@ -227,124 +239,144 @@ module.exports.getChannelExtraInfo = function(force) { } } -module.exports.getProfiles = function() { - if (isCallInProgress('getProfiles')) return; +export function getProfiles() { + if (isCallInProgress('getProfiles')) { + return; + } - callTracker['getProfiles'] = utils.getTimestamp(); + callTracker.getProfiles = utils.getTimestamp(); client.getProfiles( - function(data, textStatus, xhr) { - callTracker['getProfiles'] = 0; + function getProfilesSuccess(data, textStatus, xhr) { + callTracker.getProfiles = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_PROFILES, profiles: data }); }, - function(err) { - callTracker['getProfiles'] = 0; + function getProfilesFailure(err) { + callTracker.getProfiles = 0; dispatchError(err, 'getProfiles'); } ); } -module.exports.getSessions = function() { - if (isCallInProgress('getSessions')) return; +export function getSessions() { + if (isCallInProgress('getSessions')) { + return; + } - callTracker['getSessions'] = utils.getTimestamp(); + callTracker.getSessions = utils.getTimestamp(); client.getSessions( UserStore.getCurrentId(), - function(data, textStatus, xhr) { - callTracker['getSessions'] = 0; + function getSessionsSuccess(data, textStatus, xhr) { + callTracker.getSessions = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SESSIONS, sessions: data }); }, - function(err) { - callTracker['getSessions'] = 0; + function getSessionsFailure(err) { + callTracker.getSessions = 0; dispatchError(err, 'getSessions'); } ); } -module.exports.getAudits = function() { - if (isCallInProgress('getAudits')) return; +export function getAudits() { + if (isCallInProgress('getAudits')) { + return; + } - callTracker['getAudits'] = utils.getTimestamp(); + callTracker.getAudits = utils.getTimestamp(); client.getAudits( UserStore.getCurrentId(), - function(data, textStatus, xhr) { - callTracker['getAudits'] = 0; + function getAuditsSuccess(data, textStatus, xhr) { + callTracker.getAudits = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_AUDITS, audits: data }); }, - function(err) { - callTracker['getAudits'] = 0; + function getAuditsFailure(err) { + callTracker.getAudits = 0; dispatchError(err, 'getAudits'); } ); } -module.exports.findTeams = function(email) { - if (isCallInProgress('findTeams_'+email)) return; +export function findTeams(email) { + if (isCallInProgress('findTeams_' + email)) { + return; + } var user = UserStore.getCurrentUser(); if (user) { - callTracker['findTeams_'+email] = utils.getTimestamp(); + callTracker['findTeams_' + email] = utils.getTimestamp(); client.findTeams( user.email, - function(data, textStatus, xhr) { - callTracker['findTeams_'+email] = 0; + function findTeamsSuccess(data, textStatus, xhr) { + callTracker['findTeams_' + email] = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_TEAMS, teams: data }); }, - function(err) { - callTracker['findTeams_'+email] = 0; + function findTeamsFailure(err) { + callTracker['findTeams_' + email] = 0; dispatchError(err, 'findTeams'); } ); } } -module.exports.search = function(terms) { - if (isCallInProgress('search_'+String(terms))) return; +export function search(terms) { + if (isCallInProgress('search_' + String(terms))) { + return; + } - callTracker['search_'+String(terms)] = utils.getTimestamp(); + callTracker['search_' + String(terms)] = utils.getTimestamp(); client.search( terms, - function(data, textStatus, xhr) { - callTracker['search_'+String(terms)] = 0; + function searchSuccess(data, textStatus, xhr) { + callTracker['search_' + String(terms)] = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SEARCH, results: data }); }, - function(err) { - callTracker['search_'+String(terms)] = 0; + function searchFailure(err) { + callTracker['search_' + String(terms)] = 0; dispatchError(err, 'search'); } ); } -module.exports.getPostsPage = function(force, id, maxPosts) { +export function getPostsPage(force, id, maxPosts) { if (PostStore.getCurrentPosts() == null || force) { var channelId = id; if (channelId == null) { @@ -377,8 +409,10 @@ module.exports.getPostsPage = function(force, id, maxPosts) { channelId, 0, numPosts, - function(data, textStatus, xhr) { - if (xhr.status === 304 || !data) return; + function getPostsPageSuccess(data, textStatus, xhr) { + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POSTS, @@ -386,20 +420,20 @@ module.exports.getPostsPage = function(force, id, maxPosts) { post_list: data }); - module.exports.getProfiles(); + getProfiles(); }, - function(err) { + function getPostsPageFailure(err) { dispatchError(err, 'getPostsPage'); }, - function() { + function getPostsPageComplete() { callTracker['getPostsPage_' + channelId] = 0; } ); } } -}; +} -function getPosts(id) { +export function getPosts(id) { var channelId = id; if (channelId == null) { if (ChannelStore.getCurrentId() == null) { @@ -413,7 +447,7 @@ function getPosts(id) { } if (PostStore.getCurrentPosts() == null) { - module.exports.getPostsPage(true, id, Constants.POST_CHUNK_SIZE); + getPostsPage(true, id, Constants.POST_CHUNK_SIZE); return; } @@ -435,7 +469,7 @@ function getPosts(id) { post_list: data }); - module.exports.getProfiles(); + getProfiles(); }, function fail(err) { dispatchError(err, 'getPosts'); @@ -445,86 +479,94 @@ function getPosts(id) { } ); } -module.exports.getPosts = getPosts; -function getMe() { +export function getMe() { if (isCallInProgress('getMe')) { return; } callTracker.getMe = utils.getTimestamp(); client.getMeSynchronous( - function(data, textStatus, xhr) { + function getMeSyncSuccess(data, textStatus, xhr) { callTracker.getMe = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_ME, me: data }); }, - function(err) { + function getMeSyncFailure(err) { callTracker.getMe = 0; dispatchError(err, 'getMe'); } ); } -module.exports.getMe = getMe; -module.exports.getStatuses = function() { - if (isCallInProgress('getStatuses')) return; +export function getStatuses() { + if (isCallInProgress('getStatuses')) { + return; + } - callTracker['getStatuses'] = utils.getTimestamp(); + callTracker.getStatuses = utils.getTimestamp(); client.getStatuses( - function(data, textStatus, xhr) { - callTracker['getStatuses'] = 0; + function getStatusesSuccess(data, textStatus, xhr) { + callTracker.getStatuses = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_STATUSES, statuses: data }); }, - function(err) { - callTracker['getStatuses'] = 0; + function getStatusesFailure(err) { + callTracker.getStatuses = 0; dispatchError(err, 'getStatuses'); } ); } -module.exports.getMyTeam = function() { - if (isCallInProgress('getMyTeam')) return; +export function getMyTeam() { + if (isCallInProgress('getMyTeam')) { + return; + } - callTracker['getMyTeam'] = utils.getTimestamp(); + callTracker.getMyTeam = utils.getTimestamp(); client.getMyTeam( - function(data, textStatus, xhr) { - callTracker['getMyTeam'] = 0; + function getMyTeamSuccess(data, textStatus, xhr) { + callTracker.getMyTeam = 0; - if (xhr.status === 304 || !data) return; + if (xhr.status === 304 || !data) { + return; + } AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_TEAM, team: data }); }, - function(err) { - callTracker['getMyTeam'] = 0; + function getMyTeamFailure(err) { + callTracker.getMyTeam = 0; dispatchError(err, 'getMyTeam'); } ); } -function getConfig() { +export function getConfig() { if (isCallInProgress('getConfig')) { return; } - callTracker['getConfig'] = utils.getTimestamp(); + callTracker.getConfig = utils.getTimestamp(); client.getConfig( - function(data, textStatus, xhr) { - callTracker['getConfig'] = 0; + function getConfigSuccess(data, textStatus, xhr) { + callTracker.getConfig = 0; if (data && xhr.status !== 304) { AppDispatcher.handleServerAction({ @@ -533,10 +575,9 @@ function getConfig() { }); } }, - function(err) { - callTracker['getConfig'] = 0; + function getConfigFailure(err) { + callTracker.getConfig = 0; dispatchError(err, 'getConfig'); } ); } -module.exports.getConfig = getConfig; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 082f82a08..10f9c0b37 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -3,15 +3,15 @@ var BrowserStore = require('../stores/browser_store.jsx'); var TeamStore = require('../stores/team_store.jsx'); -module.exports.track = function(category, action, label, prop, val) { +export function track(category, action, label, prop, val) { global.window.snowplow('trackStructEvent', category, action, label, prop, val); global.window.analytics.track(action, {category: category, label: label, property: prop, value: val}); -}; +} -module.exports.trackPage = function() { +export function trackPage() { global.window.snowplow('trackPageView'); global.window.analytics.page(); -}; +} function handleError(methodName, xhr, status, err) { var LTracker = global.window.LTracker || []; @@ -41,7 +41,7 @@ function handleError(methodName, xhr, status, err) { console.error(e); //eslint-disable-line no-console LTracker.push(msg); - module.exports.track('api', 'api_weberror', methodName, 'message', msg); + track('api', 'api_weberror', methodName, 'message', msg); if (xhr.status === 401) { if (window.location.href.indexOf('/channels') === 0) { @@ -55,7 +55,7 @@ function handleError(methodName, xhr, status, err) { return e; } -module.exports.createTeamFromSignup = function(teamSignup, success, error) { +export function createTeamFromSignup(teamSignup, success, error) { $.ajax({ url: '/api/v1/teams/create_from_signup', dataType: 'json', @@ -68,9 +68,9 @@ module.exports.createTeamFromSignup = function(teamSignup, success, error) { error(e); } }); -}; +} -module.exports.createTeamWithSSO = function(team, service, success, error) { +export function createTeamWithSSO(team, service, success, error) { $.ajax({ url: '/api/v1/teams/create_with_sso/' + service, dataType: 'json', @@ -83,9 +83,9 @@ module.exports.createTeamWithSSO = function(team, service, success, error) { error(e); } }); -}; +} -module.exports.createUser = function(user, data, emailHash, success, error) { +export function createUser(user, data, emailHash, success, error) { $.ajax({ url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash), dataType: 'json', @@ -99,10 +99,10 @@ module.exports.createUser = function(user, data, emailHash, success, error) { } }); - module.exports.track('api', 'api_users_create', user.team_id, 'email', user.email); -}; + track('api', 'api_users_create', user.team_id, 'email', user.email); +} -module.exports.updateUser = function(user, success, error) { +export function updateUser(user, success, error) { $.ajax({ url: '/api/v1/users/update', dataType: 'json', @@ -116,10 +116,10 @@ module.exports.updateUser = function(user, success, error) { } }); - module.exports.track('api', 'api_users_update'); -}; + track('api', 'api_users_update'); +} -module.exports.updatePassword = function(data, success, error) { +export function updatePassword(data, success, error) { $.ajax({ url: '/api/v1/users/newpassword', dataType: 'json', @@ -133,10 +133,10 @@ module.exports.updatePassword = function(data, success, error) { } }); - module.exports.track('api', 'api_users_newpassword'); -}; + track('api', 'api_users_newpassword'); +} -module.exports.updateUserNotifyProps = function(data, success, error) { +export function updateUserNotifyProps(data, success, error) { $.ajax({ url: '/api/v1/users/update_notify', dataType: 'json', @@ -149,9 +149,9 @@ module.exports.updateUserNotifyProps = function(data, success, error) { error(e); } }); -}; +} -module.exports.updateRoles = function(data, success, error) { +export function updateRoles(data, success, error) { $.ajax({ url: '/api/v1/users/update_roles', dataType: 'json', @@ -165,10 +165,10 @@ module.exports.updateRoles = function(data, success, error) { } }); - module.exports.track('api', 'api_users_update_roles'); -}; + track('api', 'api_users_update_roles'); +} -module.exports.updateActive = function(userId, active, success, error) { +export function updateActive(userId, active, success, error) { var data = {}; data.user_id = userId; data.active = '' + active; @@ -186,10 +186,10 @@ module.exports.updateActive = function(userId, active, success, error) { } }); - module.exports.track('api', 'api_users_update_roles'); -}; + track('api', 'api_users_update_roles'); +} -module.exports.sendPasswordReset = function(data, success, error) { +export function sendPasswordReset(data, success, error) { $.ajax({ url: '/api/v1/users/send_password_reset', dataType: 'json', @@ -203,10 +203,10 @@ module.exports.sendPasswordReset = function(data, success, error) { } }); - module.exports.track('api', 'api_users_send_password_reset'); -}; + track('api', 'api_users_send_password_reset'); +} -module.exports.resetPassword = function(data, success, error) { +export function resetPassword(data, success, error) { $.ajax({ url: '/api/v1/users/reset_password', dataType: 'json', @@ -220,17 +220,17 @@ module.exports.resetPassword = function(data, success, error) { } }); - module.exports.track('api', 'api_users_reset_password'); -}; + track('api', 'api_users_reset_password'); +} -module.exports.logout = function() { - module.exports.track('api', 'api_users_logout'); +export function logout() { + track('api', 'api_users_logout'); var currentTeamUrl = TeamStore.getCurrentTeamUrl(); BrowserStore.clear(); window.location.href = currentTeamUrl + '/logout'; -}; +} -module.exports.loginByEmail = function(name, email, password, success, error) { +export function loginByEmail(name, email, password, success, error) { $.ajax({ url: '/api/v1/users/login', dataType: 'json', @@ -238,19 +238,19 @@ module.exports.loginByEmail = function(name, email, password, success, error) { type: 'POST', data: JSON.stringify({name: name, email: email, password: password}), success: function onSuccess(data, textStatus, xhr) { - module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email); + track('api', 'api_users_login_success', data.team_id, 'email', data.email); success(data, textStatus, xhr); }, error: function onError(xhr, status, err) { - module.exports.track('api', 'api_users_login_fail', window.getSubDomain(), 'email', email); + track('api', 'api_users_login_fail', name, 'email', email); var e = handleError('loginByEmail', xhr, status, err); error(e); } }); -}; +} -module.exports.revokeSession = function(altId, success, error) { +export function revokeSession(altId, success, error) { $.ajax({ url: '/api/v1/users/revoke_session', dataType: 'json', @@ -263,9 +263,9 @@ module.exports.revokeSession = function(altId, success, error) { error(e); } }); -}; +} -module.exports.getSessions = function(userId, success, error) { +export function getSessions(userId, success, error) { $.ajax({ cache: false, url: '/api/v1/users/' + userId + '/sessions', @@ -278,9 +278,9 @@ module.exports.getSessions = function(userId, success, error) { error(e); } }); -}; +} -module.exports.getAudits = function(userId, success, error) { +export function getAudits(userId, success, error) { $.ajax({ url: '/api/v1/users/' + userId + '/audits', dataType: 'json', @@ -292,9 +292,9 @@ module.exports.getAudits = function(userId, success, error) { error(e); } }); -}; +} -module.exports.getMeSynchronous = function(success, error) { +export function getMeSynchronous(success, error) { var currentUser = null; $.ajax({ async: false, @@ -318,9 +318,9 @@ module.exports.getMeSynchronous = function(success, error) { }); return currentUser; -}; +} -module.exports.inviteMembers = function(data, success, error) { +export function inviteMembers(data, success, error) { $.ajax({ url: '/api/v1/teams/invite_members', dataType: 'json', @@ -334,10 +334,10 @@ module.exports.inviteMembers = function(data, success, error) { } }); - module.exports.track('api', 'api_teams_invite_members'); -}; + track('api', 'api_teams_invite_members'); +} -module.exports.updateTeamDisplayName = function(data, success, error) { +export function updateTeamDisplayName(data, success, error) { $.ajax({ url: '/api/v1/teams/update_name', dataType: 'json', @@ -351,10 +351,10 @@ module.exports.updateTeamDisplayName = function(data, success, error) { } }); - module.exports.track('api', 'api_teams_update_name'); -}; + track('api', 'api_teams_update_name'); +} -module.exports.signupTeam = function(email, success, error) { +export function signupTeam(email, success, error) { $.ajax({ url: '/api/v1/teams/signup', dataType: 'json', @@ -368,10 +368,10 @@ module.exports.signupTeam = function(email, success, error) { } }); - module.exports.track('api', 'api_teams_signup'); -}; + track('api', 'api_teams_signup'); +} -module.exports.createTeam = function(team, success, error) { +export function createTeam(team, success, error) { $.ajax({ url: '/api/v1/teams/create', dataType: 'json', @@ -384,9 +384,9 @@ module.exports.createTeam = function(team, success, error) { error(e); } }); -}; +} -module.exports.findTeamByName = function(teamName, success, error) { +export function findTeamByName(teamName, success, error) { $.ajax({ url: '/api/v1/teams/find_team_by_name', dataType: 'json', @@ -399,9 +399,9 @@ module.exports.findTeamByName = function(teamName, success, error) { error(e); } }); -}; +} -module.exports.findTeamsSendEmail = function(email, success, error) { +export function findTeamsSendEmail(email, success, error) { $.ajax({ url: '/api/v1/teams/email_teams', dataType: 'json', @@ -415,10 +415,10 @@ module.exports.findTeamsSendEmail = function(email, success, error) { } }); - module.exports.track('api', 'api_teams_email_teams'); -}; + track('api', 'api_teams_email_teams'); +} -module.exports.findTeams = function(email, success, error) { +export function findTeams(email, success, error) { $.ajax({ url: '/api/v1/teams/find_teams', dataType: 'json', @@ -431,9 +431,9 @@ module.exports.findTeams = function(email, success, error) { error(e); } }); -}; +} -module.exports.createChannel = function(channel, success, error) { +export function createChannel(channel, success, error) { $.ajax({ url: '/api/v1/channels/create', dataType: 'json', @@ -447,10 +447,10 @@ module.exports.createChannel = function(channel, success, error) { } }); - module.exports.track('api', 'api_channels_create', channel.type, 'name', channel.name); -}; + track('api', 'api_channels_create', channel.type, 'name', channel.name); +} -module.exports.createDirectChannel = function(channel, userId, success, error) { +export function createDirectChannel(channel, userId, success, error) { $.ajax({ url: '/api/v1/channels/create_direct', dataType: 'json', @@ -458,16 +458,16 @@ module.exports.createDirectChannel = function(channel, userId, success, error) { type: 'POST', data: JSON.stringify({user_id: userId}), success: success, - error: function(xhr, status, err) { + error: function onError(xhr, status, err) { var e = handleError('createDirectChannel', xhr, status, err); error(e); } }); - module.exports.track('api', 'api_channels_create_direct', channel.type, 'name', channel.name); -}; + track('api', 'api_channels_create_direct', channel.type, 'name', channel.name); +} -module.exports.updateChannel = function(channel, success, error) { +export function updateChannel(channel, success, error) { $.ajax({ url: '/api/v1/channels/update', dataType: 'json', @@ -481,10 +481,10 @@ module.exports.updateChannel = function(channel, success, error) { } }); - module.exports.track('api', 'api_channels_update'); -}; + track('api', 'api_channels_update'); +} -module.exports.updateChannelDesc = function(data, success, error) { +export function updateChannelDesc(data, success, error) { $.ajax({ url: '/api/v1/channels/update_desc', dataType: 'json', @@ -498,10 +498,10 @@ module.exports.updateChannelDesc = function(data, success, error) { } }); - module.exports.track('api', 'api_channels_desc'); -}; + track('api', 'api_channels_desc'); +} -module.exports.updateNotifyLevel = function(data, success, error) { +export function updateNotifyLevel(data, success, error) { $.ajax({ url: '/api/v1/channels/update_notify_level', dataType: 'json', @@ -514,9 +514,9 @@ module.exports.updateNotifyLevel = function(data, success, error) { error(e); } }); -}; +} -module.exports.joinChannel = function(id, success, error) { +export function joinChannel(id, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/join', dataType: 'json', @@ -529,10 +529,10 @@ module.exports.joinChannel = function(id, success, error) { } }); - module.exports.track('api', 'api_channels_join'); -}; + track('api', 'api_channels_join'); +} -module.exports.leaveChannel = function(id, success, error) { +export function leaveChannel(id, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/leave', dataType: 'json', @@ -545,10 +545,10 @@ module.exports.leaveChannel = function(id, success, error) { } }); - module.exports.track('api', 'api_channels_leave'); -}; + track('api', 'api_channels_leave'); +} -module.exports.deleteChannel = function(id, success, error) { +export function deleteChannel(id, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/delete', dataType: 'json', @@ -561,10 +561,10 @@ module.exports.deleteChannel = function(id, success, error) { } }); - module.exports.track('api', 'api_channels_delete'); -}; + track('api', 'api_channels_delete'); +} -module.exports.updateLastViewedAt = function(channelId, success, error) { +export function updateLastViewedAt(channelId, success, error) { $.ajax({ url: '/api/v1/channels/' + channelId + '/update_last_viewed_at', dataType: 'json', @@ -576,9 +576,9 @@ module.exports.updateLastViewedAt = function(channelId, success, error) { error(e); } }); -}; +} -function getChannels(success, error) { +export function getChannels(success, error) { $.ajax({ cache: false, url: '/api/v1/channels/', @@ -592,9 +592,8 @@ function getChannels(success, error) { } }); } -module.exports.getChannels = getChannels; -module.exports.getChannel = function(id, success, error) { +export function getChannel(id, success, error) { $.ajax({ cache: false, url: '/api/v1/channels/' + id + '/', @@ -607,10 +606,10 @@ module.exports.getChannel = function(id, success, error) { } }); - module.exports.track('api', 'api_channel_get'); -}; + track('api', 'api_channel_get'); +} -module.exports.getMoreChannels = function(success, error) { +export function getMoreChannels(success, error) { $.ajax({ url: '/api/v1/channels/more', dataType: 'json', @@ -622,9 +621,9 @@ module.exports.getMoreChannels = function(success, error) { error(e); } }); -}; +} -function getChannelCounts(success, error) { +export function getChannelCounts(success, error) { $.ajax({ cache: false, url: '/api/v1/channels/counts', @@ -638,9 +637,8 @@ function getChannelCounts(success, error) { } }); } -module.exports.getChannelCounts = getChannelCounts; -module.exports.getChannelExtraInfo = function(id, success, error) { +export function getChannelExtraInfo(id, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/extra_info', dataType: 'json', @@ -651,9 +649,9 @@ module.exports.getChannelExtraInfo = function(id, success, error) { error(e); } }); -}; +} -module.exports.executeCommand = function(channelId, command, suggest, success, error) { +export function executeCommand(channelId, command, suggest, success, error) { $.ajax({ url: '/api/v1/command', dataType: 'json', @@ -666,9 +664,9 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e error(e); } }); -}; +} -module.exports.getPostsPage = function(channelId, offset, limit, success, error, complete) { +export function getPostsPage(channelId, offset, limit, success, error, complete) { $.ajax({ cache: false, url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit, @@ -682,9 +680,9 @@ module.exports.getPostsPage = function(channelId, offset, limit, success, error, }, complete: complete }); -}; +} -module.exports.getPosts = function(channelId, since, success, error, complete) { +export function getPosts(channelId, since, success, error, complete) { $.ajax({ url: '/api/v1/channels/' + channelId + '/posts/' + since, dataType: 'json', @@ -697,9 +695,9 @@ module.exports.getPosts = function(channelId, since, success, error, complete) { }, complete: complete }); -}; +} -module.exports.getPost = function(channelId, postId, success, error) { +export function getPost(channelId, postId, success, error) { $.ajax({ cache: false, url: '/api/v1/channels/' + channelId + '/post/' + postId, @@ -712,9 +710,9 @@ module.exports.getPost = function(channelId, postId, success, error) { error(e); } }); -}; +} -module.exports.search = function(terms, success, error) { +export function search(terms, success, error) { $.ajax({ url: '/api/v1/posts/search', dataType: 'json', @@ -727,10 +725,10 @@ module.exports.search = function(terms, success, error) { } }); - module.exports.track('api', 'api_posts_search'); -}; + track('api', 'api_posts_search'); +} -module.exports.deletePost = function(channelId, id, success, error) { +export function deletePost(channelId, id, success, error) { $.ajax({ url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete', dataType: 'json', @@ -743,10 +741,10 @@ module.exports.deletePost = function(channelId, id, success, error) { } }); - module.exports.track('api', 'api_posts_delete'); -}; + track('api', 'api_posts_delete'); +} -module.exports.createPost = function(post, channel, success, error) { +export function createPost(post, channel, success, error) { $.ajax({ url: '/api/v1/channels/' + post.channel_id + '/create', dataType: 'json', @@ -760,7 +758,7 @@ module.exports.createPost = function(post, channel, success, error) { } }); - module.exports.track('api', 'api_posts_create', channel.name, 'length', post.message.length); + track('api', 'api_posts_create', channel.name, 'length', post.message.length); // global.window.analytics.track('api_posts_create', { // category: 'api', @@ -770,9 +768,9 @@ module.exports.createPost = function(post, channel, success, error) { // files: (post.filenames || []).length, // mentions: (post.message.match('/<mention>/g') || []).length // }); -}; +} -module.exports.updatePost = function(post, success, error) { +export function updatePost(post, success, error) { $.ajax({ url: '/api/v1/channels/' + post.channel_id + '/update', dataType: 'json', @@ -786,10 +784,10 @@ module.exports.updatePost = function(post, success, error) { } }); - module.exports.track('api', 'api_posts_update'); -}; + track('api', 'api_posts_update'); +} -module.exports.addChannelMember = function(id, data, success, error) { +export function addChannelMember(id, data, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/add', dataType: 'json', @@ -803,10 +801,10 @@ module.exports.addChannelMember = function(id, data, success, error) { } }); - module.exports.track('api', 'api_channels_add_member'); -}; + track('api', 'api_channels_add_member'); +} -module.exports.removeChannelMember = function(id, data, success, error) { +export function removeChannelMember(id, data, success, error) { $.ajax({ url: '/api/v1/channels/' + id + '/remove', dataType: 'json', @@ -820,10 +818,10 @@ module.exports.removeChannelMember = function(id, data, success, error) { } }); - module.exports.track('api', 'api_channels_remove_member'); -}; + track('api', 'api_channels_remove_member'); +} -module.exports.getProfiles = function(success, error) { +export function getProfiles(success, error) { $.ajax({ cache: false, url: '/api/v1/users/profiles', @@ -837,9 +835,9 @@ module.exports.getProfiles = function(success, error) { error(e); } }); -}; +} -module.exports.uploadFile = function(formData, success, error) { +export function uploadFile(formData, success, error) { var request = $.ajax({ url: '/api/v1/files/upload', type: 'POST', @@ -856,12 +854,12 @@ module.exports.uploadFile = function(formData, success, error) { } }); - module.exports.track('api', 'api_files_upload'); + track('api', 'api_files_upload'); return request; -}; +} -module.exports.getFileInfo = function(filename, success, error) { +export function getFileInfo(filename, success, error) { $.ajax({ url: '/api/v1/files/get_info' + filename, dataType: 'json', @@ -873,9 +871,9 @@ module.exports.getFileInfo = function(filename, success, error) { error(e); } }); -}; +} -module.exports.getPublicLink = function(data, success, error) { +export function getPublicLink(data, success, error) { $.ajax({ url: '/api/v1/files/get_public_link', dataType: 'json', @@ -887,9 +885,9 @@ module.exports.getPublicLink = function(data, success, error) { error(e); } }); -}; +} -module.exports.uploadProfileImage = function(imageData, success, error) { +export function uploadProfileImage(imageData, success, error) { $.ajax({ url: '/api/v1/users/newimage', type: 'POST', @@ -903,9 +901,9 @@ module.exports.uploadProfileImage = function(imageData, success, error) { error(e); } }); -}; +} -module.exports.importSlack = function(fileData, success, error) { +export function importSlack(fileData, success, error) { $.ajax({ url: '/api/v1/teams/import_team', type: 'POST', @@ -919,9 +917,9 @@ module.exports.importSlack = function(fileData, success, error) { error(e); } }); -}; +} -module.exports.getStatuses = function(success, error) { +export function getStatuses(success, error) { $.ajax({ url: '/api/v1/users/status', dataType: 'json', @@ -933,9 +931,9 @@ module.exports.getStatuses = function(success, error) { error(e); } }); -}; +} -module.exports.getMyTeam = function(success, error) { +export function getMyTeam(success, error) { $.ajax({ url: '/api/v1/teams/me', dataType: 'json', @@ -947,9 +945,9 @@ module.exports.getMyTeam = function(success, error) { error(e); } }); -}; +} -module.exports.updateValetFeature = function(data, success, error) { +export function updateValetFeature(data, success, error) { $.ajax({ url: '/api/v1/teams/update_valet_feature', dataType: 'json', @@ -963,10 +961,10 @@ module.exports.updateValetFeature = function(data, success, error) { } }); - module.exports.track('api', 'api_teams_update_valet_feature'); -}; + track('api', 'api_teams_update_valet_feature'); +} -function getConfig(success, error) { +export function getConfig(success, error) { $.ajax({ url: '/api/v1/config/get_all', dataType: 'json', @@ -979,4 +977,3 @@ function getConfig(success, error) { } }); } -module.exports.getConfig = getConfig; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 8721ced7c..3a6ca1b89 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -4,97 +4,107 @@ var keyMirror = require('keymirror'); module.exports = { - ActionTypes: keyMirror({ - RECIEVED_ERROR: null, + ActionTypes: keyMirror({ + RECIEVED_ERROR: null, - CLICK_CHANNEL: null, - CREATE_CHANNEL: null, - RECIEVED_CHANNELS: null, - RECIEVED_CHANNEL: null, - RECIEVED_MORE_CHANNELS: null, - RECIEVED_CHANNEL_EXTRA_INFO: null, + CLICK_CHANNEL: null, + CREATE_CHANNEL: null, + RECIEVED_CHANNELS: null, + RECIEVED_CHANNEL: null, + RECIEVED_MORE_CHANNELS: null, + RECIEVED_CHANNEL_EXTRA_INFO: null, - RECIEVED_POSTS: null, - RECIEVED_POST: null, - RECIEVED_SEARCH: null, - RECIEVED_POST_SELECTED: null, - RECIEVED_MENTION_DATA: null, - RECIEVED_ADD_MENTION: null, + RECIEVED_POSTS: null, + RECIEVED_POST: null, + RECIEVED_SEARCH: null, + RECIEVED_POST_SELECTED: null, + RECIEVED_MENTION_DATA: null, + RECIEVED_ADD_MENTION: null, - RECIEVED_PROFILES: null, - RECIEVED_ME: null, - RECIEVED_SESSIONS: null, - RECIEVED_AUDITS: null, - RECIEVED_TEAMS: null, - RECIEVED_STATUSES: null, + RECIEVED_PROFILES: null, + RECIEVED_ME: null, + RECIEVED_SESSIONS: null, + RECIEVED_AUDITS: null, + RECIEVED_TEAMS: null, + RECIEVED_STATUSES: null, - RECIEVED_MSG: null, + RECIEVED_MSG: null, - CLICK_TEAM: null, - RECIEVED_TEAM: null, + CLICK_TEAM: null, + RECIEVED_TEAM: null, - RECIEVED_CONFIG: null - }), + RECIEVED_CONFIG: null + }), - PayloadSources: keyMirror({ - SERVER_ACTION: null, - VIEW_ACTION: null - }), - SPECIAL_MENTIONS: ['all', 'channel'], - CHARACTER_LIMIT: 4000, - IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'], - AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'], - VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'], - SPREADSHEET_TYPES: ['ppt', 'pptx', 'csv'], - EXCEL_TYPES: ['xlsx'], - WORD_TYPES: ['doc', 'docx'], - CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'], - PDF_TYPES: ['pdf'], - PATCH_TYPES: ['patch'], - ICON_FROM_TYPE: {'audio': 'audio', 'video': 'video', 'spreadsheet': 'ppt', 'pdf': 'pdf', 'code': 'code' , 'word': 'word' , 'excel': 'excel' , 'patch': 'patch', 'other': 'generic'}, - MAX_DISPLAY_FILES: 5, - MAX_UPLOAD_FILES: 5, - MAX_FILE_SIZE: 50000000, // 50 MB - THUMBNAIL_WIDTH: 128, - THUMBNAIL_HEIGHT: 100, - DEFAULT_CHANNEL: 'town-square', - OFFTOPIC_CHANNEL: 'off-topic', - GITLAB_SERVICE: 'gitlab', - EMAIL_SERVICE: 'email', - POST_CHUNK_SIZE: 60, - MAX_POST_CHUNKS: 3, - POST_LOADING: 'loading', - POST_FAILED: 'failed', - POST_DELETED: 'deleted', - RESERVED_TEAM_NAMES: [ - "www", - "web", - "admin", - "support", - "notify", - "test", - "demo", - "mail", - "team", - "channel", - "internal", - "localhost", - "dockerhost", - "stag", - "post", - "cluster", - "api", - ], - RESERVED_USERNAMES: [ - "valet", - "all", - "channel", - ], - MONTHS: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - MAX_DMS: 20, - MAX_POST_LEN: 4000, - ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", - OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>", - MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", - COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>" + PayloadSources: keyMirror({ + SERVER_ACTION: null, + VIEW_ACTION: null + }), + SPECIAL_MENTIONS: ['all', 'channel'], + CHARACTER_LIMIT: 4000, + IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'], + AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'], + VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'], + PRESENTATION_TYPES: ['ppt', 'pptx'], + SPREADSHEET_TYPES: ['xlsx', 'csv'], + WORD_TYPES: ['doc', 'docx'], + CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'], + PDF_TYPES: ['pdf'], + PATCH_TYPES: ['patch'], + ICON_FROM_TYPE: { + audio: 'audio', + video: 'video', + spreadsheet: 'excel', + presentation: 'ppt', + pdf: 'pdf', + code: 'code', + word: 'word', + patch: 'patch', + other: 'generic' + }, + MAX_DISPLAY_FILES: 5, + MAX_UPLOAD_FILES: 5, + MAX_FILE_SIZE: 50000000, // 50 MB + THUMBNAIL_WIDTH: 128, + THUMBNAIL_HEIGHT: 100, + DEFAULT_CHANNEL: 'town-square', + OFFTOPIC_CHANNEL: 'off-topic', + GITLAB_SERVICE: 'gitlab', + EMAIL_SERVICE: 'email', + POST_CHUNK_SIZE: 60, + MAX_POST_CHUNKS: 3, + POST_LOADING: 'loading', + POST_FAILED: 'failed', + POST_DELETED: 'deleted', + RESERVED_TEAM_NAMES: [ + 'www', + 'web', + 'admin', + 'support', + 'notify', + 'test', + 'demo', + 'mail', + 'team', + 'channel', + 'internal', + 'localhost', + 'dockerhost', + 'stag', + 'post', + 'cluster', + 'api' + ], + RESERVED_USERNAMES: [ + 'valet', + 'all', + 'channel' + ], + MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + MAX_DMS: 20, + MAX_POST_LEN: 4000, + ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", + OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>", + MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", + COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>" }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index a1dc72ae2..6267ebd8f 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -10,19 +10,19 @@ var AsyncClient = require('./async_client.jsx'); var client = require('./client.jsx'); var Autolinker = require('autolinker'); -module.exports.isEmail = function(email) { +export function isEmail(email) { var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; return regex.test(email); -}; +} -module.exports.cleanUpUrlable = function(input) { +export function cleanUpUrlable(input) { var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-'); cleaned = cleaned.replace(/^\-+/, ''); cleaned = cleaned.replace(/\-+$/, ''); return cleaned; -}; +} -module.exports.isTestDomain = function() { +export function isTestDomain() { if ((/^localhost/).test(window.location.hostname)) { return true; } @@ -52,38 +52,9 @@ module.exports.isTestDomain = function() { } return false; -}; - -function getSubDomain() { - if (module.exports.isTestDomain()) { - return ''; - } - - if ((/^www/).test(window.location.hostname)) { - return ''; - } - - if ((/^beta/).test(window.location.hostname)) { - return ''; - } - - if ((/^ci/).test(window.location.hostname)) { - return ''; - } - - var parts = window.location.hostname.split('.'); - - if (parts.length !== 3) { - return ''; - } - - return parts[0]; } -global.window.getSubDomain = getSubDomain; -module.exports.getSubDomain = getSubDomain; - -module.exports.getDomainWithOutSub = function() { +export function getDomainWithOutSub() { var parts = window.location.host.split('.'); if (parts.length === 1) { @@ -95,17 +66,17 @@ module.exports.getDomainWithOutSub = function() { } return parts[1] + '.' + parts[2]; -}; +} -module.exports.getCookie = function(name) { +export function getCookie(name) { var value = '; ' + document.cookie; var parts = value.split('; ' + name + '='); if (parts.length === 2) { return parts.pop().split(';').shift(); } -}; +} -module.exports.notifyMe = function(title, body, channel) { +export function notifyMe(title, body, channel) { if ('Notification' in window && Notification.permission !== 'denied') { Notification.requestPermission(function onRequestPermission(permission) { if (Notification.permission !== permission) { @@ -117,7 +88,7 @@ module.exports.notifyMe = function(title, body, channel) { notification.onclick = function onClick() { window.focus(); if (channel) { - module.exports.switchChannel(channel); + switchChannel(channel); } else { window.location.href = '/'; } @@ -128,16 +99,16 @@ module.exports.notifyMe = function(title, body, channel) { } }); } -}; +} -module.exports.ding = function() { - if (!module.exports.isBrowserFirefox()) { +export function ding() { + if (!isBrowserFirefox()) { var audio = new Audio('/static/images/ding.mp3'); audio.play(); } -}; +} -module.exports.getUrlParameter = function(sParam) { +export function getUrlParameter(sParam) { var sPageURL = window.location.search.substring(1); var sURLVariables = sPageURL.split('&'); for (var i = 0; i < sURLVariables.length; i++) { @@ -147,20 +118,20 @@ module.exports.getUrlParameter = function(sParam) { } } return null; -}; +} -module.exports.getDateForUnixTicks = function(ticks) { +export function getDateForUnixTicks(ticks) { return new Date(ticks); -}; +} -module.exports.displayDate = function(ticks) { +export function displayDate(ticks) { var d = new Date(ticks); var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear(); -}; +} -module.exports.displayTime = function(ticks) { +export function displayTime(ticks) { var d = new Date(ticks); var hours = d.getHours(); var minutes = d.getMinutes(); @@ -178,9 +149,9 @@ module.exports.displayTime = function(ticks) { minutes = '0' + minutes; } return hours + ':' + minutes + ' ' + ampm; -}; +} -module.exports.displayDateTime = function(ticks) { +export function displayDateTime(ticks) { var seconds = Math.floor((Date.now() - ticks) / 1000); var interval = Math.floor(seconds / 3600); @@ -203,16 +174,16 @@ module.exports.displayDateTime = function(ticks) { } return '1 minute ago'; -}; +} -module.exports.displayCommentDateTime = function(ticks) { - return module.exports.displayDate(ticks) + ' ' + module.exports.displayTime(ticks); +export function displayCommentDateTime(ticks) { + return displayDate(ticks) + ' ' + displayTime(ticks); } // returns Unix timestamp in milliseconds -module.exports.getTimestamp = function() { +export function getTimestamp() { return Date.now(); -}; +} function testUrlMatch(text) { var urlMatcher = new Autolinker.matchParser.MatchParser({ @@ -240,7 +211,7 @@ function testUrlMatch(text) { return result; } -module.exports.extractLinks = function(text) { +export function extractLinks(text) { var repRegex = new RegExp('<br>', 'g'); var matches = testUrlMatch(text.replace(repRegex, '\n')); @@ -254,11 +225,11 @@ module.exports.extractLinks = function(text) { } return {links: links, text: text}; -}; +} -module.exports.escapeRegExp = function(string) { +export function escapeRegExp(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); -}; +} function handleYoutubeTime(link) { var timeRegex = /[\\?&]t=([0-9hms]+)/; @@ -317,7 +288,7 @@ function getYoutubeEmbed(link) { return; } var metadata = data.items[0].snippet; - $('.video-type.' + youtubeId).html("Youtube - ") + $('.video-type.' + youtubeId).html('Youtube - '); $('.video-uploader.' + youtubeId).html(metadata.channelTitle); $('.video-title.' + youtubeId).find('a').html(metadata.title); $('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight); @@ -328,7 +299,7 @@ function getYoutubeEmbed(link) { async: true, url: 'https://www.googleapis.com/youtube/v3/videos', type: 'GET', - data: {part: 'snippet', id: youtubeId, key:config.GoogleDeveloperKey}, + data: {part: 'snippet', id: youtubeId, key: config.GoogleDeveloperKey}, success: success }); } @@ -340,12 +311,22 @@ function getYoutubeEmbed(link) { <span className={'video-title ' + youtubeId}><a href={link}></a></span> </h4> <h4 className={'video-uploader ' + youtubeId}></h4> - <div className='video-div embed-responsive-item' id={youtubeId} onClick={onClick}> + <div + className='video-div embed-responsive-item' + id={youtubeId} + onClick={onClick} + > <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'> - <div id={youtubeId} className='video-thumbnail__container'> - <img className='video-thumbnail' src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}/> + <div + id={youtubeId} + className='video-thumbnail__container' + > + <img + className='video-thumbnail' + src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'} + /> <div className='block'> - <span className='play-button'><span></span></span> + <span className='play-button'><span/></span> </div> </div> </div> @@ -354,7 +335,7 @@ function getYoutubeEmbed(link) { ); } -module.exports.getEmbed = function(link) { +export function getEmbed(link) { var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/; var match = link.trim().match(ytRegex); @@ -406,13 +387,13 @@ module.exports.getEmbed = function(link) { </div> ); */ -}; +} -module.exports.areStatesEqual = function(state1, state2) { +export function areStatesEqual(state1, state2) { return JSON.stringify(state1) === JSON.stringify(state2); -}; +} -module.exports.replaceHtmlEntities = function(text) { +export function replaceHtmlEntities(text) { var tagsToReplace = { '&': '&', '<': '<', @@ -426,9 +407,9 @@ module.exports.replaceHtmlEntities = function(text) { } } return newtext; -}; +} -module.exports.insertHtmlEntities = function(text) { +export function insertHtmlEntities(text) { var tagsToReplace = { '&': '&', '<': '<', @@ -442,33 +423,33 @@ module.exports.insertHtmlEntities = function(text) { } } return newtext; -}; +} -module.exports.searchForTerm = function(term) { +export function searchForTerm(term) { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SEARCH_TERM, term: term, do_search: true }); -}; +} var puncStartRegex = /^((?![@#])\W)+/g; var puncEndRegex = /(\W)+$/g; -module.exports.textToJsx = function(text, options) { - - if (options && options['singleline']) { - var repRegex = new RegExp('\n', 'g'); +export function textToJsx(textin, options) { + var text = textin; + if (options && options.singleline) { + var repRegex = new RegExp('\n', 'g'); //eslint-disable-line no-control-regex text = text.replace(repRegex, ' '); } - var searchTerm = '' - if (options && options['searchTerm']) { - searchTerm = options['searchTerm'].toLowerCase() + var searchTerm = ''; + if (options && options.searchTerm) { + searchTerm = options.searchTerm.toLowerCase(); } var mentionClass = 'mention-highlight'; - if (options && options['noMentionHighlight']) { + if (options && options.noMentionHighlight) { mentionClass = ''; } @@ -480,11 +461,11 @@ module.exports.textToJsx = function(text, options) { var implicitKeywords = UserStore.getCurrentMentionKeys(); var lines = text.split('\n'); - for (var i = 0; i < lines.length; i++) { + for (let i = 0; i < lines.length; i++) { var line = lines[i]; var words = line.split(' '); var highlightSearchClass = ''; - for (var z = 0; z < words.length; z++) { + for (let z = 0; z < words.length; z++) { var word = words[z]; var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim(); var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here @@ -493,15 +474,17 @@ module.exports.textToJsx = function(text, options) { if (searchTerm !== '') { let searchWords = searchTerm.split(' '); for (let idx in searchWords) { - let searchWord = searchWords[idx]; - if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) { - highlightSearchClass = ' search-highlight'; - break; - } else if (searchWord.charAt(searchWord.length - 1) === '*') { - let searchWordPrefix = searchWord.slice(0,-1); - if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) { + if ({}.hasOwnProperty.call(searchWords, idx)) { + let searchWord = searchWords[idx]; + if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) { highlightSearchClass = ' search-highlight'; break; + } else if (searchWord.charAt(searchWord.length - 1) === '*') { + let searchWordPrefix = searchWord.slice(0, -1); + if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) { + highlightSearchClass = ' search-highlight'; + break; + } } } } @@ -509,68 +492,147 @@ module.exports.textToJsx = function(text, options) { if (explicitMention && (UserStore.getProfileByUsername(explicitMention[1]) || - Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) - { - var name = explicitMention[1]; - // do both a non-case sensitive and case senstive check - var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : ''; + Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) { + let name = explicitMention[1]; - var suffix = word.match(puncEndRegex); - var prefix = word.match(puncStartRegex); - - if (searchTerm === name) { - highlightSearchClass = ' search-highlight'; - } + // do both a non-case sensitive and case senstive check + let mClass = ''; + if (('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) { + mClass = mentionClass; + } - inner.push(<span key={name+i+z+'_span'}>{prefix}<a className={mClass + highlightSearchClass + ' mention-link'} key={name+i+z+'_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>); - } else if (testUrlMatch(word).length) { - var match = testUrlMatch(word)[0]; - var link = match.link; + let suffix = word.match(puncEndRegex); + let prefix = word.match(puncStartRegex); - var prefix = word.substring(0,word.indexOf(match.text)); - var suffix = word.substring(word.indexOf(match.text)+match.text.length); + if (searchTerm === name) { + highlightSearchClass = ' search-highlight'; + } - inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>); + inner.push( + <span key={name + i + z + '_span'}> + {prefix} + <a + className={mClass + highlightSearchClass + ' mention-link'} + key={name + i + z + '_link'} + href='#' + onClick={() => searchForTerm(name)} //eslint-disable-line no-loop-func + > + @{name} + </a> + {suffix} + {' '} + </span> + ); + } else if (testUrlMatch(word).length) { + let match = testUrlMatch(word)[0]; + let link = match.link; + + let prefix = word.substring(0, word.indexOf(match.text)); + let suffix = word.substring(word.indexOf(match.text) + match.text.length); + + inner.push( + <span key={word + i + z + '_span'}> + {prefix} + <a + key={word + i + z + '_link'} + className={'theme' + highlightSearchClass} + target='_blank' + href={link} + > + {match.text} + </a> + {suffix} + {' '} + </span> + ); + } else if (trimWord.match(hashRegex)) { + let suffix = word.match(puncEndRegex); + let prefix = word.match(puncStartRegex); + let mClass = ''; + if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { + mClass = mentionClass; + } - } else if (trimWord.match(hashRegex)) { - var suffix = word.match(puncEndRegex); - var prefix = word.match(puncStartRegex); - var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : ''; + if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) { + highlightSearchClass = ' search-highlight'; + } - if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) { + inner.push( + <span key={word + i + z + '_span'}> + {prefix} + <a + key={word + i + z + '_hash'} + className={'theme ' + mClass + highlightSearchClass} + href='#' + onClick={() => searchForTerm(trimWord)} //eslint-disable-line no-loop-func + > + {trimWord} + </a> + {suffix} + {' '} + </span> + ); + } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { + let suffix = word.match(puncEndRegex); + let prefix = word.match(puncStartRegex); + + if (trimWord.charAt(0) === '@') { + if (searchTerm === trimWord.substring(1).toLowerCase()) { highlightSearchClass = ' search-highlight'; } - - inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>); - - } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { - var suffix = word.match(puncEndRegex); - var prefix = word.match(puncStartRegex); - - if (trimWord.charAt(0) === '@') { - if (searchTerm === trimWord.substring(1).toLowerCase()) { - highlightSearchClass = ' search-highlight'; - } - inner.push(<span key={word+i+z+'_span'} key={name+i+z+'_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+'_link'} href='#'>{trimWord}</a>{suffix} </span>); - } else { - inner.push(<span key={word+i+z+'_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>); - } - - } else if (word === '') { - // if word is empty dont include a span + inner.push( + <span key={word + i + z + '_span'}> + {prefix} + <a + className={mentionClass + highlightSearchClass} + key={name + i + z + '_link'} + href='#' + > + {trimWord} + </a> + {suffix} + {' '} + </span> + ); } else { - inner.push(<span key={word+i+z+'_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>); + inner.push( + <span key={word + i + z + '_span'}> + {prefix} + <span className={mentionClass + highlightSearchClass}> + {replaceHtmlEntities(trimWord)} + </span> + {suffix} + {' '} + </span> + ); } - highlightSearchClass = ''; + } else if (word === '') { + + // if word is empty dont include a span + + } else { + inner.push( + <span key={word + i + z + '_span'}> + <span className={highlightSearchClass}> + {replaceHtmlEntities(word)} + </span> + {' '} + </span> + ); + } + highlightSearchClass = ''; + } + if (i !== lines.length - 1) { + inner.push( + <br key={'br_' + i}/> + ); } - if (i != lines.length-1) - inner.push(<br key={'br_'+i+z}/>); } return inner; } -module.exports.getFileType = function(extin) { +export function getFileType(extin) { var ext = extin.toLowerCase(); if (Constants.IMAGE_TYPES.indexOf(ext) > -1) { return 'image'; @@ -596,8 +658,8 @@ module.exports.getFileType = function(extin) { return 'word'; } - if (Constants.EXCEL_TYPES.indexOf(ext) > -1) { - return 'excel'; + if (Constants.PRESENTATION_TYPES.indexOf(ext) > -1) { + return 'presentation'; } if (Constants.PDF_TYPES.indexOf(ext) > -1) { @@ -609,9 +671,9 @@ module.exports.getFileType = function(extin) { } return 'other'; -}; +} -module.exports.getPreviewImagePathForFileType = function(fileTypeIn) { +export function getPreviewImagePathForFileType(fileTypeIn) { var fileType = fileTypeIn.toLowerCase(); var icon; @@ -622,9 +684,9 @@ module.exports.getPreviewImagePathForFileType = function(fileTypeIn) { } return '/static/images/icons/' + icon + '.png'; -}; +} -module.exports.getIconClassName = function(fileTypeIn) { +export function getIconClassName(fileTypeIn) { var fileType = fileTypeIn.toLowerCase(); if (fileType in Constants.ICON_FROM_TYPE) { @@ -632,9 +694,9 @@ module.exports.getIconClassName = function(fileTypeIn) { } return 'glyphicon-file'; -}; +} -module.exports.splitFileLocation = function(fileLocation) { +export function splitFileLocation(fileLocation) { var fileSplit = fileLocation.split('.'); var ext = ''; @@ -647,16 +709,16 @@ module.exports.splitFileLocation = function(fileLocation) { var filename = filePath.split('/')[filePath.split('/').length - 1]; return {ext: ext, name: filename, path: filePath}; -}; +} -module.exports.toTitleCase = function(str) { +export function toTitleCase(str) { function doTitleCase(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); } return str.replace(/\w\S*/g, doTitleCase); -}; +} -module.exports.changeCss = function(className, classValue) { +export function changeCss(className, classValue) { // we need invisible container to store additional css definitions var cssMainContainer = $('#css-modifier-container'); if (cssMainContainer.length === 0) { @@ -674,9 +736,9 @@ module.exports.changeCss = function(className, classValue) { // append additional style classContainer.html('<style>' + className + ' {' + classValue + '}</style>'); -}; +} -module.exports.rgb2hex = function(rgbIn) { +export function rgb2hex(rgbIn) { if (/^#[0-9A-F]{6}$/i.test(rgbIn)) { return rgbIn; } @@ -686,9 +748,9 @@ module.exports.rgb2hex = function(rgbIn) { return ('0' + parseInt(x, 10).toString(16)).slice(-2); } return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); -}; +} -module.exports.placeCaretAtEnd = function(el) { +export function placeCaretAtEnd(el) { el.focus(); if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') { var range = document.createRange(); @@ -703,9 +765,9 @@ module.exports.placeCaretAtEnd = function(el) { textRange.collapse(false); textRange.select(); } -}; +} -module.exports.getCaretPosition = function(el) { +export function getCaretPosition(el) { if (el.selectionStart) { return el.selectionStart; } else if (document.selection) { @@ -724,9 +786,9 @@ module.exports.getCaretPosition = function(el) { return rc.text.length; } return 0; -}; +} -module.exports.setSelectionRange = function(input, selectionStart, selectionEnd) { +export function setSelectionRange(input, selectionStart, selectionEnd) { if (input.setSelectionRange) { input.focus(); input.setSelectionRange(selectionStart, selectionEnd); @@ -737,13 +799,13 @@ module.exports.setSelectionRange = function(input, selectionStart, selectionEnd) range.moveStart('character', selectionStart); range.select(); } -}; +} -module.exports.setCaretPosition = function(input, pos) { - module.exports.setSelectionRange(input, pos, pos); -}; +export function setCaretPosition(input, pos) { + setSelectionRange(input, pos, pos); +} -module.exports.getSelectedText = function(input) { +export function getSelectedText(input) { var selectedText; if (typeof document.selection !== 'undefined') { input.focus(); @@ -756,9 +818,9 @@ module.exports.getSelectedText = function(input) { } return selectedText; -}; +} -module.exports.isValidUsername = function(name) { +export function isValidUsername(name) { var error = ''; if (!name) { error = 'This field is required'; @@ -780,20 +842,18 @@ module.exports.isValidUsername = function(name) { } return error; -}; +} -function updateTabTitle(name) { +export function updateTabTitle(name) { document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-')); } -module.exports.updateTabTitle = updateTabTitle; -function updateAddressBar(channelName) { +export function updateAddressBar(channelName) { var teamURL = window.location.href.split('/channels')[0]; history.replaceState('data', '', teamURL + '/channels/' + channelName); } -module.exports.updateAddressBar = updateAddressBar; -function switchChannel(channel, teammateName) { +export function switchChannel(channel, teammateName) { AppDispatcher.handleViewAction({ type: ActionTypes.CLICK_CHANNEL, name: channel.name, @@ -819,20 +879,19 @@ function switchChannel(channel, teammateName) { return false; } -module.exports.switchChannel = switchChannel; -module.exports.isMobile = function() { +export function isMobile() { return screen.width <= 768; -}; +} -module.exports.isComment = function(post) { +export function isComment(post) { if ('root_id' in post) { return post.root_id !== ''; } return false; -}; +} -module.exports.getDirectTeammate = function(channelId) { +export function getDirectTeammate(channelId) { var userIds = ChannelStore.get(channelId).name.split('__'); var curUserId = UserStore.getCurrentId(); var teammate = {}; @@ -849,9 +908,9 @@ module.exports.getDirectTeammate = function(channelId) { } return teammate; -}; +} -Image.prototype.load = function(url, progressCallback) { +Image.prototype.load = function imageLoad(url, progressCallback) { var self = this; var xmlHTTP = new XMLHttpRequest(); xmlHTTP.open('GET', url, true); @@ -878,7 +937,7 @@ Image.prototype.load = function(url, progressCallback) { Image.prototype.completedPercentage = 0; -module.exports.changeColor = function(colourIn, amt) { +export function changeColor(colourIn, amt) { var usePound = false; var col = colourIn; @@ -919,10 +978,9 @@ module.exports.changeColor = function(colourIn, amt) { } return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); -}; - -module.exports.changeOpacity = function(oldColor, opacity) { +} +export function changeOpacity(oldColor, opacity) { var col = oldColor; if (col[0] === '#') { col = col.slice(1); @@ -933,9 +991,9 @@ module.exports.changeOpacity = function(oldColor, opacity) { var b = parseInt(col.substring(4, 6), 16); return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; -}; +} -module.exports.getFullName = function(user) { +export function getFullName(user) { if (user.first_name && user.last_name) { return user.first_name + ' ' + user.last_name; } else if (user.first_name) { @@ -945,23 +1003,23 @@ module.exports.getFullName = function(user) { } return ''; -}; +} -module.exports.getDisplayName = function(user) { +export function getDisplayName(user) { if (user.nickname && user.nickname.trim().length > 0) { return user.nickname; } - var fullName = module.exports.getFullName(user); + var fullName = getFullName(user); if (fullName) { return fullName; } return user.username; -}; +} //IE10 does not set window.location.origin automatically so this must be called instead when using it -module.exports.getWindowLocationOrigin = function() { +export function getWindowLocationOrigin() { var windowLocationOrigin = window.location.origin; if (!windowLocationOrigin) { windowLocationOrigin = window.location.protocol + '//' + window.location.hostname; @@ -970,10 +1028,10 @@ module.exports.getWindowLocationOrigin = function() { } } return windowLocationOrigin; -}; +} // Converts a file size in bytes into a human-readable string of the form '123MB'. -module.exports.fileSizeToString = function(bytes) { +export function fileSizeToString(bytes) { // it's unlikely that we'll have files bigger than this if (bytes > 1024 * 1024 * 1024 * 1024) { return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + 'TB'; @@ -986,29 +1044,29 @@ module.exports.fileSizeToString = function(bytes) { } return bytes + 'B'; -}; +} // Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server. -module.exports.getFileUrl = function(filename) { +export function getFileUrl(filename) { var url = filename; // This is a temporary patch to fix issue with old files using absolute paths if (url.indexOf('/api/v1/files/get') !== -1) { url = filename.split('/api/v1/files/get')[1]; } - url = module.exports.getWindowLocationOrigin() + '/api/v1/files/get' + url; + url = getWindowLocationOrigin() + '/api/v1/files/get' + url; return url; -}; +} // Gets the name of a file (including extension) from a given url or file path. -module.exports.getFileName = function(path) { +export function getFileName(path) { var split = path.split('/'); return split[split.length - 1]; -}; +} // Generates a RFC-4122 version 4 compliant globally unique identifier. -module.exports.generateId = function() { +export function generateId() { // implementation taken from http://stackoverflow.com/a/2117523 var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; @@ -1026,14 +1084,14 @@ module.exports.generateId = function() { }); return id; -}; +} -module.exports.isBrowserFirefox = function() { +export function isBrowserFirefox() { return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1; -}; +} // Checks if browser is IE10 or IE11 -module.exports.isBrowserIE = function() { +export function isBrowserIE() { if (window.navigator && window.navigator.userAgent) { var ua = window.navigator.userAgent; @@ -1041,14 +1099,14 @@ module.exports.isBrowserIE = function() { } return false; -}; +} -module.exports.isBrowserEdge = function() { +export function isBrowserEdge() { return window.naviagtor && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1; -}; +} // Used to get the id of the other user from a DM channel -module.exports.getUserIdFromChannelName = function(channel) { +export function getUserIdFromChannelName(channel) { var ids = channel.name.split('__'); var otherUserId = ''; if (ids[0] === UserStore.getCurrentId()) { @@ -1058,13 +1116,13 @@ module.exports.getUserIdFromChannelName = function(channel) { } return otherUserId; -}; +} -module.exports.importSlack = function(file, success, error) { +export function importSlack(file, success, error) { var formData = new FormData(); formData.append('file', file, file.name); formData.append('filesize', file.size); formData.append('importFrom', 'slack'); client.importSlack(formData, success, error); -}; +} |