summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
authorCorey Hulen <corey@hulen.com>2015-09-02 12:41:28 -0700
committerCorey Hulen <corey@hulen.com>2015-09-02 12:41:28 -0700
commitf9dd82253cb64b2508d2ac21821b5108354a3fb0 (patch)
tree48cd35a5b439d46821bde5e4534d467704b1bbbe /web/react
parent833f6b7df123f25d5aa6bee6aee0c90c82b74c38 (diff)
parentb9e16f41f161c772e1701c4ac47ca5319c706912 (diff)
downloadchat-f9dd82253cb64b2508d2ac21821b5108354a3fb0.tar.gz
chat-f9dd82253cb64b2508d2ac21821b5108354a3fb0.tar.bz2
chat-f9dd82253cb64b2508d2ac21821b5108354a3fb0.zip
Merge pull request #545 from mattermost/mm-2066
MM-2066 Refactoring for style guide
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/access_history_modal.jsx108
-rw-r--r--web/react/components/channel_notifications.jsx152
-rw-r--r--web/react/components/get_link_modal.jsx141
-rw-r--r--web/react/components/loading_screen.jsx37
-rw-r--r--web/react/components/navbar.jsx436
-rw-r--r--web/react/components/post_header.jsx41
-rw-r--r--web/react/components/sidebar.jsx574
-rw-r--r--web/react/components/team_feature_tab.jsx116
-rw-r--r--web/react/components/team_settings.jsx121
-rw-r--r--web/react/components/team_signup_allowed_domains_page.jsx96
-rw-r--r--web/react/components/team_signup_password_page.jsx91
-rw-r--r--web/react/components/user_profile.jsx104
-rw-r--r--web/react/components/user_settings_appearance.jsx168
-rw-r--r--web/react/components/user_settings_security.jsx180
-rw-r--r--web/react/components/view_image.jsx292
15 files changed, 1678 insertions, 979 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'>&times;</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'>&times;</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/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/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'>&times;</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'>&times;</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/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/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/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/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'>&times;</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'>&times;</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_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_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>&nbsp;
- <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>&nbsp;
+ <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_password_page.jsx b/web/react/components/team_signup_password_page.jsx
index 18cf05dad..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,15 +35,14 @@ 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(data);
if (this.props.hash > 0) {
@@ -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/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">&times;</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'>&times;</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_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'>&times;</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'>&times;</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
+};