summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/.eslintrc65
-rw-r--r--web/react/components/access_history_modal.jsx113
-rw-r--r--web/react/components/activity_log_modal.jsx184
-rw-r--r--web/react/components/channel_header.jsx436
-rw-r--r--web/react/components/channel_info_modal.jsx97
-rw-r--r--web/react/components/channel_invite_modal.jsx38
-rw-r--r--web/react/components/channel_loader.jsx45
-rw-r--r--web/react/components/channel_members.jsx280
-rw-r--r--web/react/components/channel_notifications.jsx160
-rw-r--r--web/react/components/command_list.jsx97
-rw-r--r--web/react/components/confirm_modal.jsx83
-rw-r--r--web/react/components/create_comment.jsx213
-rw-r--r--web/react/components/create_post.jsx280
-rw-r--r--web/react/components/delete_channel_modal.jsx127
-rw-r--r--web/react/components/delete_post_modal.jsx148
-rw-r--r--web/react/components/edit_channel_modal.jsx181
-rw-r--r--web/react/components/edit_post_modal.jsx149
-rw-r--r--web/react/components/email_verify.jsx69
-rw-r--r--web/react/components/error_bar.jsx81
-rw-r--r--web/react/components/file_attachment.jsx195
-rw-r--r--web/react/components/file_attachment_list.jsx63
-rw-r--r--web/react/components/file_preview.jsx106
-rw-r--r--web/react/components/file_upload.jsx230
-rw-r--r--web/react/components/file_upload_overlay.jsx14
-rw-r--r--web/react/components/find_team.jsx70
-rw-r--r--web/react/components/get_link_modal.jsx142
-rw-r--r--web/react/components/invite_member_modal.jsx211
-rw-r--r--web/react/components/loading_screen.jsx37
-rw-r--r--web/react/components/login.jsx47
-rw-r--r--web/react/components/member_list.jsx47
-rw-r--r--web/react/components/member_list_item.jsx127
-rw-r--r--web/react/components/member_list_team.jsx131
-rw-r--r--web/react/components/member_list_team_item.jsx203
-rw-r--r--web/react/components/mention.jsx64
-rw-r--r--web/react/components/mention_list.jsx145
-rw-r--r--web/react/components/message_wrapper.jsx27
-rw-r--r--web/react/components/more_channels.jsx34
-rw-r--r--web/react/components/more_direct_channels.jsx66
-rw-r--r--web/react/components/msg_typing.jsx85
-rw-r--r--web/react/components/navbar.jsx439
-rw-r--r--web/react/components/navbar_dropdown.jsx216
-rw-r--r--web/react/components/new_channel.jsx126
-rw-r--r--web/react/components/notify_counts.jsx29
-rw-r--r--web/react/components/password_reset.jsx164
-rw-r--r--web/react/components/password_reset_form.jsx101
-rw-r--r--web/react/components/password_reset_send_link.jsx98
-rw-r--r--web/react/components/popover_list_members.jsx80
-rw-r--r--web/react/components/post.jsx125
-rw-r--r--web/react/components/post_body.jsx143
-rw-r--r--web/react/components/post_deleted_modal.jsx73
-rw-r--r--web/react/components/post_header.jsx41
-rw-r--r--web/react/components/post_info.jsx17
-rw-r--r--web/react/components/post_list.jsx43
-rw-r--r--web/react/components/removed_from_channel_modal.jsx129
-rw-r--r--web/react/components/rename_channel_modal.jsx286
-rw-r--r--web/react/components/rhs_comment.jsx26
-rw-r--r--web/react/components/rhs_header_post.jsx8
-rw-r--r--web/react/components/rhs_root_post.jsx17
-rw-r--r--web/react/components/rhs_thread.jsx33
-rw-r--r--web/react/components/search_bar.jsx132
-rw-r--r--web/react/components/search_results.jsx218
-rw-r--r--web/react/components/search_results_header.jsx61
-rw-r--r--web/react/components/search_results_item.jsx105
-rw-r--r--web/react/components/setting_item_max.jsx72
-rw-r--r--web/react/components/setting_item_min.jsx35
-rw-r--r--web/react/components/setting_picture.jsx89
-rw-r--r--web/react/components/setting_upload.jsx72
-rw-r--r--web/react/components/settings_sidebar.jsx60
-rw-r--r--web/react/components/sidebar.jsx576
-rw-r--r--web/react/components/sidebar_header.jsx157
-rw-r--r--web/react/components/sidebar_right.jsx94
-rw-r--r--web/react/components/sidebar_right_menu.jsx78
-rw-r--r--web/react/components/signup_team.jsx22
-rw-r--r--web/react/components/signup_team_complete.jsx120
-rw-r--r--web/react/components/signup_user_complete.jsx132
-rw-r--r--web/react/components/team_feature_tab.jsx120
-rw-r--r--web/react/components/team_general_tab.jsx38
-rw-r--r--web/react/components/team_import_tab.jsx104
-rw-r--r--web/react/components/team_members.jsx140
-rw-r--r--web/react/components/team_settings.jsx121
-rw-r--r--web/react/components/team_settings_modal.jsx116
-rw-r--r--web/react/components/team_signup_allowed_domains_page.jsx97
-rw-r--r--web/react/components/team_signup_choose_auth.jsx5
-rw-r--r--web/react/components/team_signup_display_name_page.jsx78
-rw-r--r--web/react/components/team_signup_email_item.jsx60
-rw-r--r--web/react/components/team_signup_password_page.jsx92
-rw-r--r--web/react/components/team_signup_send_invites_page.jsx23
-rw-r--r--web/react/components/team_signup_url_page.jsx114
-rw-r--r--web/react/components/team_signup_username_page.jsx80
-rw-r--r--web/react/components/team_signup_welcome_page.jsx108
-rw-r--r--web/react/components/team_signup_with_email.jsx19
-rw-r--r--web/react/components/team_signup_with_sso.jsx3
-rw-r--r--web/react/components/textbox.jsx291
-rw-r--r--web/react/components/user_profile.jsx106
-rw-r--r--web/react/components/user_settings.jsx76
-rw-r--r--web/react/components/user_settings_appearance.jsx173
-rw-r--r--web/react/components/user_settings_general.jsx278
-rw-r--r--web/react/components/user_settings_modal.jsx86
-rw-r--r--web/react/components/user_settings_notifications.jsx105
-rw-r--r--web/react/components/user_settings_security.jsx208
-rw-r--r--web/react/components/view_image.jsx301
-rw-r--r--web/react/dispatcher/app_dispatcher.jsx30
-rw-r--r--web/react/package.json12
-rw-r--r--web/react/pages/channel.jsx36
-rw-r--r--web/react/pages/find_team.jsx6
-rw-r--r--web/react/pages/home.jsx9
-rw-r--r--web/react/pages/login.jsx12
-rw-r--r--web/react/pages/password_reset.jsx12
-rw-r--r--web/react/pages/signup_team.jsx6
-rw-r--r--web/react/pages/signup_team_complete.jsx14
-rw-r--r--web/react/pages/signup_user_complete.jsx16
-rw-r--r--web/react/pages/verify.jsx6
-rw-r--r--web/react/stores/browser_store.jsx80
-rw-r--r--web/react/stores/channel_store.jsx255
-rw-r--r--web/react/stores/config_store.jsx65
-rw-r--r--web/react/stores/error_store.jsx81
-rw-r--r--web/react/stores/post_store.jsx387
-rw-r--r--web/react/stores/socket_store.jsx67
-rw-r--r--web/react/stores/team_store.jsx97
-rw-r--r--web/react/stores/user_store.jsx336
-rw-r--r--web/react/utils/async_client.jsx293
-rw-r--r--web/react/utils/client.jsx297
-rw-r--r--web/react/utils/config.js48
-rw-r--r--web/react/utils/constants.jsx183
-rw-r--r--web/react/utils/utils.jsx529
125 files changed, 9384 insertions, 5542 deletions
diff --git a/web/react/.eslintrc b/web/react/.eslintrc
index cdf96905b..53cc75913 100644
--- a/web/react/.eslintrc
+++ b/web/react/.eslintrc
@@ -4,7 +4,9 @@
"jsx": true,
"blockBindings": true,
"modules": true,
- "classes": true
+ "classes": true,
+ "arrowFunctions": true,
+ "defaultParams": true,
},
"plugins": [
"react"
@@ -21,36 +23,36 @@
"rules": {
"comma-dangle": [2, "never"],
"no-cond-assign": [2, "except-parens"],
- "no-console": 1,
- "no-constant-condition": 1,
- "no-debugger": 1,
+ "no-console": 2,
+ "no-constant-condition": 2,
+ "no-debugger": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
- "no-empty": 1,
- "no-ex-assign": 1,
+ "no-empty": 2,
+ "no-ex-assign": 2,
"no-extra-semi": 2,
- "no-func-assign": 1,
+ "no-func-assign": 2,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
+ "no-unexpected-multiline": 2,
"no-unreachable": 2,
"valid-typeof": 2,
- "no-unexpected-multiline": 2,
- "block-scoped-var": 1,
+ "block-scoped-var": 2,
"complexity": [1, 8],
"consistent-return": 2,
"curly": [2, "all"],
- "dot-notation": 2,
"dot-location": [2, "object"],
+ "dot-notation": 2,
"eqeqeq": [2, "smart"],
- "guard-for-in": 1,
- "no-alert": 1,
+ "guard-for-in": 2,
+ "no-alert": 2,
"no-array-constructor": 2,
"no-caller": 2,
- "no-div-regex": 1,
- "no-else-return": 1,
+ "no-div-regex": 2,
+ "no-else-return": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
@@ -58,7 +60,7 @@
"no-implied-eval": 2,
"no-iterator": 2,
"no-labels": 2,
- "no-lone-blocks": 1,
+ "no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": [2, { "exceptions": { "Property": false } }],
"no-multi-str": 0,
@@ -78,10 +80,11 @@
"no-self-compare": 2,
"no-sequences": 2,
"no-throw-literal": 2,
- "no-unused-expressions": 2,
"no-undef-init": 2,
+ "no-unused-expressions": 2,
+ "no-useless-concat": 1,
"no-void": 2,
- "no-warning-comments": 0,
+ "no-warning-comments": 1,
"no-with": 2,
"radix": 2,
"vars-on-top": 0,
@@ -97,7 +100,7 @@
// Style
"array-bracket-spacing": [2, "never"],
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
- "camelcase": [2, {"properties": "always"}],
+ "camelcase": [2, {"properties": "never"}],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"computed-property-spacing": [2, "never"],
@@ -135,14 +138,33 @@
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"wrap-regex": 2,
+ // ES6 stuff
+ "arrow-parens": [2, "always"],
+ "arrow-spacing": [2, { "before": true, "after": true }],
+ "constructor-super": 2,
+ "generator-star-spacing": [2, {"before": false, "after": true}],
+ "no-class-assign": 2,
+ "no-const-assign": 2,
+ "no-dupe-class-members": 2,
+ "no-this-before-super": 2,
+ "no-var": 0,
+ "object-shorthand": [1, "always"],
+ "prefer-arrow-callback": 1,
+ "prefer-const": 1,
+ "prefer-spread": 2,
+ "prefer-reflect": 1,
+ "prefer-template": 0,
+ "require-yield": 2,
+
// React Specific
"react/display-name": [2, { "acceptTranspilerName": true }],
"react/jsx-boolean-value": [2, "always"],
+ "react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }],
"react/jsx-curly-spacing": [2, "never"],
+ "react/jsx-indent-props": [2, 4],
"react/jsx-max-props-per-line": [2, { "maximum": 1 }],
- // SOON "react/jsx-indent-props": [2, 4],
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
- "react/jsx-no-literals": 0,
+ "react/jsx-no-literals": 1,
"react/jsx-no-undef": 2,
"react/jsx-quotes": [2, "single", "avoid-escape"],
"react/jsx-uses-react": 2,
@@ -151,10 +173,11 @@
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-multi-comp": 2,
+ "react/no-set-state": 0,
"react/no-unknown-property": 2,
"react/prop-types": 2,
- "react/sort-comp": 0,
"react/self-closing-comp": 2,
+ "react/sort-comp": 0,
"react/wrap-multilines": 2
}
}
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index a19e5c16e..a080150dd 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,11 +85,14 @@ 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] = (
- <div className='access-history__table'>
+ <div
+ key={'accessHistoryEntryKey' + i}
+ className='access-history__table'
+ >
<div className='access__date'>{newDate}</div>
<div className='access__report'>
<div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div>
@@ -102,14 +115,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 +153,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 1192a72bc..7cbd4021e 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -1,136 +1,170 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var LoadingScreen = require('./loading_screen.jsx');
-var utils = require('../utils/utils.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const LoadingScreen = require('./loading_screen.jsx');
+const Utils = require('../utils/utils.jsx');
-function getStateFromStoresForSessions() {
- return {
- sessions: UserStore.getSessions(),
- serverError: null,
- clientError: null
- };
-}
+export default class ActivityLogModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitRevoke = this.submitRevoke.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleMoreInfo = this.handleMoreInfo.bind(this);
-module.exports = React.createClass({
- displayName: 'ActivityLogModal',
- submitRevoke: function(altId) {
+ this.state = this.getStateFromStores();
+ this.state.moreInfo = [];
+ }
+ getStateFromStores() {
+ return {
+ sessions: UserStore.getSessions(),
+ serverError: null,
+ clientError: null
+ };
+ }
+ submitRevoke(altId) {
Client.revokeSession(altId,
- function(data) {
+ function handleRevokeSuccess() {
AsyncClient.getSessions();
- }.bind(this),
- function(err) {
- var state = getStateFromStoresForSessions();
+ },
+ function handleRevokeError(err) {
+ let state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}.bind(this)
);
- },
- componentDidMount: function() {
+ }
+ componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function handleShow() {
AsyncClient.getSessions();
});
- var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
$('#user_settings').modal('show');
- self.setState({moreInfo: []});
- });
- },
- componentWillUnmount: function() {
+ this.setState({moreInfo: []});
+ }.bind(this));
+ }
+ componentWillUnmount() {
UserStore.removeSessionsChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
- var newState = getStateFromStoresForSessions();
- if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) {
+ }
+ onListenerChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
}
- },
- handleMoreInfo: function(index) {
- var newMoreInfo = this.state.moreInfo;
+ }
+ handleMoreInfo(index) {
+ let newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
this.setState({moreInfo: newMoreInfo});
- },
- getInitialState: function() {
- var initialState = getStateFromStoresForSessions();
- initialState.moreInfo = [];
- return initialState;
- },
- render: function() {
- var activityList = [];
- var serverError = this.state.serverError;
-
- // Squash any false-y value for server error into null
- if (!serverError) {
- serverError = null;
- }
+ }
+ render() {
+ let activityList = [];
- for (var i = 0; i < this.state.sessions.length; i++) {
- var currentSession = this.state.sessions[i];
- var lastAccessTime = new Date(currentSession.last_activity_at);
- var firstAccessTime = new Date(currentSession.create_at);
- var devicePicture = '';
+ for (let i = 0; i < this.state.sessions.length; i++) {
+ const currentSession = this.state.sessions[i];
+ const lastAccessTime = new Date(currentSession.last_activity_at);
+ const firstAccessTime = new Date(currentSession.create_at);
+ let devicePicture = '';
if (currentSession.props.platform === 'Windows') {
devicePicture = 'fa fa-windows';
- }
- else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
+ } else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
devicePicture = 'fa fa-apple';
- }
- else if (currentSession.props.platform === 'Linux') {
+ } else if (currentSession.props.platform === 'Linux') {
devicePicture = 'fa fa-linux';
}
- var moreInfo;
+ let moreInfo;
if (this.state.moreInfo[i]) {
moreInfo = (
<div>
- <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
- <div>{'OS: ' + currentSession.props.os}</div>
- <div>{'Browser: ' + currentSession.props.browser}</div>
- <div>{'Session ID: ' + currentSession.alt_id}</div>
+ <div>{`First time active: ${firstAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
+ <div>{`OS: ${currentSession.props.os}`}</div>
+ <div>{`Browser: ${currentSession.props.browser}`}</div>
+ <div>{`Session ID: ${currentSession.alt_id}`}</div>
</div>
);
} else {
- moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ moreInfo = (
+ <a
+ className='theme'
+ href='#'
+ onClick={this.handleMoreInfo.bind(this, i)}
+ >
+ More info
+ </a>
+ );
}
activityList[i] = (
- <div className='activity-log__table'>
+ <div
+ key={'activityLogEntryKey' + i}
+ className='activity-log__table'
+ >
<div className='activity-log__report'>
<div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
<div className='report__info'>
- <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ <div>{`Last activity: ${lastAccessTime.toDateString()}, ${lastAccessTime.toLocaleTimeString()}`}</div>
{moreInfo}
</div>
</div>
- <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div>
+ <div className='activity-log__action'>
+ <button
+ onClick={this.submitRevoke.bind(this, currentSession.alt_id)}
+ className='btn btn-primary'
+ >
+ Logout
+ </button>
+ </div>
</div>
);
}
- var content;
+ let content;
if (this.state.sessions.loading) {
- content = (<LoadingScreen />);
+ content = <LoadingScreen />;
} else {
- content = (<form role='form'>{activityList}</form>);
+ content = <form role='form'>{activityList}</form>;
}
return (
<div>
- <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='activity-log'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog modal-lg'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ Active Sessions
+ </h4>
</div>
<p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
- <div ref='modalBody' className='modal-body'>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
{content}
</div>
</div>
@@ -139,4 +173,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 0254d0e82..87b9cab04 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -1,139 +1,85 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var NavbarSearchBox = require('./search_bar.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var Client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-var MessageWrapper = require('./message_wrapper.jsx');
-
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-var PopoverListMembers = React.createClass({
- componentDidMount: function() {
- var originalLeave = $.fn.popover.Constructor.prototype.leave;
- $.fn.popover.Constructor.prototype.leave = function(obj) {
- var selfObj;
- if (obj instanceof this.constructor) {
- selfObj = obj;
- } else {
- selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
- }
- originalLeave.call(this, obj);
-
- if (obj.currentTarget && selfObj.$tip) {
- selfObj.$tip.one('mouseenter', function() {
- clearTimeout(selfObj.timeout);
- selfObj.$tip.one('mouseleave', function() {
- $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
- });
- });
- }
- };
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const NavbarSearchBox = require('./search_bar.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+const MessageWrapper = require('./message_wrapper.jsx');
+const PopoverListMembers = require('./popover_list_members.jsx');
- $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
- $('body').on('click', function(e) {
- if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
- $('#member_popover').popover('hide');
- }
- });
- },
-
- render: function() {
- var popoverHtml = '';
- var members = this.props.members;
- var count;
- if (members.length > 20) {
- count = '20+';
- } else {
- count = members.length || '-';
- }
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
- if (members) {
- members.sort(function(a, b) {
- return a.username.localeCompare(b.username);
- });
+export default class ChannelHeader extends React.Component {
+ constructor(props) {
+ super(props);
- members.forEach(function(m) {
- popoverHtml += "<div class='text--nowrap'>" + m.username + '</div>';
- });
- }
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.onSocketChange = this.onSocketChange.bind(this);
+ this.handleLeave = this.handleLeave.bind(this);
+ this.searchMentions = this.searchMentions.bind(this);
- return (
- <div id='member_popover' data-toggle='popover' data-content={popoverHtml} data-original-title='Members' >
- <div id='member_tooltip' data-placement='left' data-toggle='tooltip' title='View Channel Members'>
- {count} <span className='glyphicon glyphicon-user' aria-hidden='true'></span>
- </div>
- </div>
- );
+ this.state = this.getStateFromStores();
}
-});
-
-function getStateFromStores() {
- return {
- channel: ChannelStore.getCurrent(),
- memberChannel: ChannelStore.getCurrentMember(),
- memberTeam: UserStore.getCurrentUser(),
- users: ChannelStore.getCurrentExtraInfo().members,
- searchVisible: PostStore.getSearchResults() != null
- };
-}
-
-module.exports = React.createClass({
- displayName: 'ChannelHeader',
- componentDidMount: function() {
+ getStateFromStores() {
+ return {
+ channel: ChannelStore.getCurrent(),
+ memberChannel: ChannelStore.getCurrentMember(),
+ memberTeam: UserStore.getCurrentUser(),
+ users: ChannelStore.getCurrentExtraInfo().members,
+ searchVisible: PostStore.getSearchResults() !== null
+ };
+ }
+ componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
PostStore.addSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
SocketStore.addChangeListener(this.onSocketChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
PostStore.removeSearchChangeListener(this.onListenerChange);
UserStore.addChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ }
+ onListenerChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}});
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
if (msg.action === 'new_user') {
AsyncClient.getChannelExtraInfo(true);
}
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- handleLeave: function() {
+ }
+ handleLeave() {
Client.leaveChannel(this.state.channel.id,
- function() {
- var townsquare = ChannelStore.getByName('town-square');
- utils.switchChannel(townsquare);
+ function handleLeaveSuccess() {
+ const townsquare = ChannelStore.getByName('town-square');
+ Utils.switchChannel(townsquare);
},
- function(err) {
+ function handleLeaveError(err) {
AsyncClient.dispatchError(err, 'handleLeave');
}
);
- },
- searchMentions: function(e) {
+ }
+ searchMentions(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
+ const user = UserStore.getCurrentUser();
- var terms = '';
+ let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- var termKeys = UserStore.getCurrentMentionKeys();
+ let termKeys = UserStore.getCurrentMentionKeys();
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1);
}
@@ -149,23 +95,23 @@ module.exports = React.createClass({
do_search: true,
is_mention_search: true
});
- },
- render: function() {
- if (this.state.channel == null) {
+ }
+ render() {
+ if (this.state.channel === null) {
return null;
}
- var channel = this.state.channel;
- var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
- var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
- var channelTitle = channel.display_name;
- var currentId = UserStore.getCurrentId();
- var isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
- var isDirect = (this.state.channel.type === 'D');
+ const channel = this.state.channel;
+ const description = Utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
+ const popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
+ let channelTitle = channel.display_name;
+ const currentId = UserStore.getCurrentId();
+ const isAdmin = this.state.memberChannel.roles.indexOf('admin') > -1 || this.state.memberTeam.roles.indexOf('admin') > -1;
+ const isDirect = (this.state.channel.type === 'D');
if (isDirect) {
if (this.state.users.length > 1) {
- var contact;
+ let contact;
if (this.state.users[0].id === currentId) {
contact = this.state.users[1];
} else {
@@ -175,64 +121,244 @@ module.exports = React.createClass({
}
}
- var channelTerm = 'Channel';
+ let channelTerm = 'Channel';
if (channel.type === 'P') {
channelTerm = 'Group';
}
+ let dropdownContents = [];
+ if (!isDirect) {
+ dropdownContents.push(
+ <li
+ key='view_info'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_info'
+ data-channelid={channel.id}
+ href='#'
+ >
+ View Info
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
+ dropdownContents.push(
+ <li
+ key='add_members'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ href='#'
+ >
+ Add Members
+ </a>
+ </li>
+ );
+
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='manage_members'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ data-toggle='modal'
+ data-target='#channel_members'
+ href='#'
+ >
+ Manage Members
+ </a>
+ </li>
+ );
+ }
+ }
+
+ dropdownContents.push(
+ <li
+ key='set_channel_description'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set {channelTerm} Description...
+ </a>
+ </li>
+ );
+ dropdownContents.push(
+ <li
+ key='notification_preferences'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#channel_notifications'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Notification Preferences
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='rename_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#rename_channel'
+ data-display={channel.display_name}
+ data-name={channel.name}
+ data-channelid={channel.id}
+ >
+ Rename {channelTerm}...
+ </a>
+ </li>
+ );
+ dropdownContents.push(
+ <li
+ key='delete_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#delete_channel'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Delete {channelTerm}...
+ </a>
+ </li>
+ );
+ }
+
+ dropdownContents.push(
+ <li
+ key='leave_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleLeave}
+ >
+ Leave {channelTerm}
+ </a>
+ </li>
+ );
+ }
+ } else {
+ dropdownContents.push(
+ <li
+ key='edit_description_direct'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#edit_channel'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ Set Channel Description...
+ </a>
+ </li>
+ );
+ }
+
return (
<table className='channel-header alt'>
<tr>
<th>
<div className='channel-header__info'>
<div className='dropdown'>
- <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
<strong className='heading'>{channelTitle} </strong>
- <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
+ <span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
</a>
- {!isDirect ?
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li>
- {!ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>
- : null
- }
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>
- : null
- }
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set {channelTerm} Description...</a></li>
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename {channelTerm}...</a></li>
- : null
- }
- {isAdmin && !ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete {channelTerm}...</a></li>
- : null
- }
- {!ChannelStore.isDefault(channel) ?
- <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave {channelTerm}</a></li>
- : null
- }
- </ul>
- :
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {dropdownContents}
</ul>
- }
</div>
- <div data-toggle='popover' data-content={popoverContent} className='description'>{description}</div>
+ <div
+ data-toggle='popover'
+ data-content={popoverContent}
+ className='description'
+ >
+ {description}
+ </div>
</div>
</th>
- <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th>
+ <th>
+ <PopoverListMembers
+ members={this.state.users}
+ channelId={channel.id}
+ />
+ </th>
<th className='search-bar__container'><NavbarSearchBox /></th>
<th>
<div className='dropdown channel-header__links'>
- <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_right_dropdown' data-toggle='dropdown' aria-expanded='true'>
- <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a>
- <ul className='dropdown-menu dropdown-menu-right' role='menu' aria-labelledby='channel_header_right_dropdown'>
- <li role='presentation'><a role='menuitem' href='#' onClick={this.searchMentions}>Recent Mentions</a></li>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_right_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
+ </a>
+ <ul
+ className='dropdown-menu dropdown-menu-right'
+ role='menu'
+ aria-labelledby='channel_header_right_dropdown'
+ >
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.searchMentions}
+ >
+ Recent Mentions
+ </a>
+ </li>
</ul>
</div>
</th>
@@ -240,4 +366,4 @@ module.exports = React.createClass({
</table>
);
}
-});
+}
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index 6d999870a..fdd9a5c17 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -3,57 +3,86 @@
var ChannelStore = require('../stores/channel_store.jsx');
-module.exports = React.createClass({
- componentDidMount: function() {
+export default class CommandList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ channel_id: ChannelStore.getCurrentId()
+ };
+ }
+
+ componentDidMount() {
var self = this;
- if(this.refs.modal) {
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = e.relatedTarget;
- self.setState({ channel_id: $(button).attr('data-channelid') });
- });
+ if (this.refs.modal) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
+ var button = e.relatedTarget;
+ self.setState({channel_id: $(button).attr('data-channelid')});
+ });
}
- },
- getInitialState: function() {
- return { channel_id: ChannelStore.getCurrentId() };
- },
- render: function() {
+ }
+
+ render() {
var channel = ChannelStore.get(this.state.channel_id);
if (!channel) {
channel = {};
- channel.display_name = "No Channel Found";
- channel.name = "No Channel Found";
- channel.id = "No Channel Found";
+ channel.display_name = 'No Channel Found';
+ channel.name = 'No Channel Found';
+ channel.id = 'No Channel Found';
}
return (
- <div className="modal fade" ref="modal" id="channel_info" 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"><span className="name">{channel.display_name}</span></h4>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='channel_info'
+ 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'
+ >
+ <span className='name'>{channel.display_name}</span>
+ </h4>
</div>
- <div className="modal-body">
- <div className="row form-group">
- <div className="col-sm-3 info__label">Channel Name: </div>
- <div className="col-sm-9">{channel.display_name}</div>
+ <div className='modal-body'>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>Channel Name: </div>
+ <div className='col-sm-9'>{channel.display_name}</div>
</div>
- <div className="row form-group">
- <div className="col-sm-3 info__label">Channel Handle:</div>
- <div className="col-sm-9">{channel.name}</div>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>Channel Handle:</div>
+ <div className='col-sm-9'>{channel.name}</div>
</div>
- <div className="row">
- <div className="col-sm-3 info__label">Channel ID:</div>
- <div className="col-sm-9">{channel.id}</div>
+ <div className='row'>
+ <div className='col-sm-3 info__label'>Channel ID:</div>
+ <div className='col-sm-9'>{channel.id}</div>
</div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >Close</button>
</div>
</div>
</div>
</div>
);
}
-});
+}
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index b70811db1..5feeb4e88 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -9,7 +9,6 @@ var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-
export default class ChannelInviteModal extends React.Component {
constructor() {
super();
@@ -129,15 +128,37 @@ export default class ChannelInviteModal extends React.Component {
if (this.state.loading) {
content = (<LoadingScreen />);
} else {
- content = (<MemberList memberList={this.state.nonmembers} isAdmin={isAdmin} handleInvite={this.handleInvite} />);
+ content = (
+ <MemberList
+ memberList={this.state.nonmembers}
+ isAdmin={isAdmin}
+ handleInvite={this.handleInvite}
+ />
+ );
}
return (
- <div className='modal fade' id='channel_invite' tabIndex='-1' role='dialog' aria-hidden='true'>
- <div className='modal-dialog' role='document'>
+ <div
+ className='modal fade'
+ id='channel_invite'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div
+ className='modal-dialog'
+ role='document'
+ >
<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>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
<h4 className='modal-title'>Add New Members to <span className='name'>{this.state.channelName}</span></h4>
</div>
<div className='modal-body'>
@@ -145,7 +166,11 @@ export default class ChannelInviteModal extends React.Component {
{content}
</div>
<div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >Close</button>
</div>
</div>
</div>
@@ -153,4 +178,3 @@ export default class ChannelInviteModal extends React.Component {
);
}
}
-ChannelInviteModal.displayName = 'ChannelInviteModal';
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx
index 0fa433383..8e8ed3f73 100644
--- a/web/react/components/channel_loader.jsx
+++ b/web/react/components/channel_loader.jsx
@@ -10,13 +10,18 @@ var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-var Constants = require('../utils/constants.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- componentDidMount: function() {
+export default class ChannelLoader extends React.Component {
+ constructor(props) {
+ super(props);
+ this.onSocketChange = this.onSocketChange.bind(this);
+
+ this.state = {};
+ }
+ componentDidMount() {
/* Initial aysnc loads */
AsyncClient.getMe();
AsyncClient.getPosts(ChannelStore.getCurrentId());
@@ -60,32 +65,32 @@ module.exports = React.createClass({
var user = UserStore.getCurrentUser();
if (user.props && user.props.theme) {
- utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
- utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
- utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
- utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
- utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
- utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';');
+ Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';');
+ Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
+ Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
+ Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
+ Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';');
}
if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) + ';');
- utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';');
+ Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
} else if (user.props.theme === '#000000') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) + ';');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';');
$('.team__header').addClass('theme--black');
} else if (user.props.theme === '#585858') {
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) + ';');
+ Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
$('.team__header').addClass('theme--gray');
}
- },
- onSocketChange: function(msg) {
+ }
+ onSocketChange(msg) {
if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) {
UserStore.setStatus(msg.user_id, 'online');
}
- },
- render: function() {
+ }
+ render() {
return <div/>;
}
-});
+}
diff --git a/web/react/components/channel_members.jsx b/web/react/components/channel_members.jsx
index db4bec400..04fa2c7a2 100644
--- a/web/react/components/channel_members.jsx
+++ b/web/react/components/channel_members.jsx
@@ -1,154 +1,200 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var MemberList = require('./member_list.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-
-function getStateFromStores() {
- var users = UserStore.getActiveOnlyProfiles();
- var member_list = ChannelStore.getCurrentExtraInfo().members;
-
- var nonmember_list = [];
- for (var id in users) {
- var found = false;
- for (var i = 0; i < member_list.length; i++) {
- if (member_list[i].id === id) {
- found = true;
- break;
+const UserStore = require('../stores/user_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const MemberList = require('./member_list.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+export default class ChannelMembers extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+
+ this.state = this.getStateFromStores();
+ }
+ getStateFromStores() {
+ const users = UserStore.getActiveOnlyProfiles();
+ let memberList = ChannelStore.getCurrentExtraInfo().members;
+
+ let nonmemberList = [];
+ for (let id in users) {
+ if (users.hasOwnProperty(id)) {
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ nonmemberList.push(users[id]);
+ }
}
}
- if (!found) {
- nonmember_list.push(users[id]);
+
+ function compareByUsername(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ } else if (a.username > b.username) {
+ return 1;
+ }
+
+ return 0;
}
- }
- member_list.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
- return 0;
- });
-
- nonmember_list.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
- return 0;
- });
-
- var channel_name = ChannelStore.getCurrent() ? ChannelStore.getCurrent().display_name : "";
-
- return {
- nonmember_list: nonmember_list,
- member_list: member_list,
- channel_name: channel_name
- };
-}
+ memberList.sort(compareByUsername);
+ nonmemberList.sort(compareByUsername);
+
+ const channel = ChannelStore.getCurrent();
+ let channelName = '';
+ if (channel) {
+ channelName = channel.display_name;
+ }
-module.exports = React.createClass({
- componentDidMount: function() {
- ChannelStore.addExtraInfoChangeListener(this._onChange);
- ChannelStore.addChangeListener(this._onChange);
- var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ render_members: false });
- });
-
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- self.setState({ render_members: true });
- });
- },
- componentWillUnmount: function() {
- ChannelStore.removeExtraInfoChangeListener(this._onChange);
- ChannelStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var new_state = getStateFromStores();
- if (!utils.areStatesEqual(this.state, new_state)) {
- this.setState(new_state);
+ return {
+ nonmemberList: nonmemberList,
+ memberList: memberList,
+ channelName: channelName
+ };
+ }
+ componentDidMount() {
+ ChannelStore.addExtraInfoChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function handleHide() {
+ this.setState({renderMembers: false});
+ }.bind(this));
+
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow() {
+ this.setState({renderMembers: true});
+ }.bind(this));
+ }
+ componentWillUnmount() {
+ ChannelStore.removeExtraInfoChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ onChange() {
+ const newState = this.getStateFromStores();
+ if (!Utils.areStatesEqual(this.state, newState)) {
+ this.setState(newState);
}
- },
- handleRemove: function(user_id) {
+ }
+ handleRemove(userId) {
// Make sure the user is a member of the channel
- var member_list = this.state.member_list;
- var found = false;
- for (var i = 0; i < member_list.length; i++) {
- if (member_list[i].id === user_id) {
+ let memberList = this.state.memberList;
+ let found = false;
+ for (let i = 0; i < memberList.length; i++) {
+ if (memberList[i].id === userId) {
found = true;
break;
}
}
- if (!found) { return };
+ if (!found) {
+ return;
+ }
- var data = {};
- data['user_id'] = user_id;
+ let data = {};
+ data.user_id = userId;
- client.removeChannelMember(ChannelStore.getCurrentId(), data,
- function(data) {
- var old_member;
- for (var i = 0; i < member_list.length; i++) {
- if (user_id === member_list[i].id) {
- old_member = member_list[i];
- member_list.splice(i, 1);
+ Client.removeChannelMember(ChannelStore.getCurrentId(), data,
+ function handleRemoveSuccess() {
+ let oldMember;
+ for (let i = 0; i < memberList.length; i++) {
+ if (userId === memberList[i].id) {
+ oldMember = memberList[i];
+ memberList.splice(i, 1);
break;
}
}
- var nonmember_list = this.state.nonmember_list;
- if (old_member) {
- nonmember_list.push(old_member);
+ let nonmemberList = this.state.nonmemberList;
+ if (oldMember) {
+ nonmemberList.push(oldMember);
}
- this.setState({ member_list: member_list, nonmember_list: nonmember_list });
+ this.setState({memberList: memberList, nonmemberList: nonmemberList});
AsyncClient.getChannelExtraInfo(true);
}.bind(this),
- function(err) {
- this.setState({ invite_error: err.message });
+ function handleRemoveError(err) {
+ this.setState({inviteError: err.message});
}.bind(this)
);
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- var currentMember = ChannelStore.getCurrentMember();
- var isAdmin = false;
+ }
+ render() {
+ const currentMember = ChannelStore.getCurrentMember();
+ let isAdmin = false;
if (currentMember) {
- isAdmin = currentMember.roles.indexOf("admin") > -1 || UserStore.getCurrentUser().roles.indexOf("admin") > -1;
+ isAdmin = currentMember.roles.indexOf('admin') > -1 || UserStore.getCurrentUser().roles.indexOf('admin') > -1;
+ }
+
+ var memberList = null;
+ if (this.state.renderMembers) {
+ memberList = (
+ <MemberList
+ memberList={this.state.memberList}
+ isAdmin={isAdmin}
+ handleRemove={this.handleRemove}
+ />
+ );
}
return (
- <div className="modal fade" ref="modal" id="channel_members" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
- <h4 className="modal-title"><span className="name">{this.state.channel_name}</span> Members</h4>
- <a className="btn btn-md btn-primary" data-toggle="modal" data-target="#channel_invite"><i className="glyphicon glyphicon-envelope"/> Add New Members</a>
- </div>
- <div ref="modalBody" className="modal-body">
- <div className="col-sm-12">
- <div className="team-member-list">
- { this.state.render_members ?
- <MemberList
- memberList={this.state.member_list}
- isAdmin={isAdmin}
- handleRemove={this.handleRemove}
- />
- : "" }
+ <div
+ className='modal fade'
+ ref='modal'
+ id='channel_members'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>×</span>
+ </button>
+ <h4 className='modal-title'><span className='name'>{this.state.channelName}</span> Members</h4>
+ <a
+ className='btn btn-md btn-primary'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ >
+ <i className='glyphicon glyphicon-envelope'/> Add New Members
+ </a>
+ </div>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
+ <div className='col-sm-12'>
+ <div className='team-member-list'>
+ {memberList}
+ </div>
</div>
</div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Close
+ </button>
+ </div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
- </div>
- </div>
- </div>
+ </div>
</div>
-
);
}
-});
+}
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 884bad9d2..83067240d 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,44 +157,54 @@ export default class ChannelNotifications extends React.Component {
);
handleUpdateSection = function updateSection(e) {
- self.updateSection('');
- self.onListenerChange();
+ this.updateSection('');
+ this.onListenerChange();
e.preventDefault();
- };
+ }.bind(this);
+
+ let curChannel = ChannelStore.get(this.state.channelId);
+ let extraInfo = (<span>These settings will override the global notification settings</span>);
- desktopSection = (
+ if (curChannel && curChannel.display_name) {
+ extraInfo = (<span>These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel</span>);
+ }
+
+ return (
<SettingItemMax
title='Send desktop notifications'
inputs={inputs}
submit={this.handleUpdate}
server_error={serverError}
updateSection={handleUpdateSection}
+ extraInfo={extraInfo}
/>
);
- } 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 +213,8 @@ export default class ChannelNotifications extends React.Component {
quietActive[1] = true;
}
+ var inputs = [];
+
inputs.push(
<div>
<div className='radio'>
@@ -215,7 +222,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 +234,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 +252,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 +266,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/command_list.jsx b/web/react/components/command_list.jsx
index 5efe98dc6..63bd57c2a 100644
--- a/web/react/components/command_list.jsx
+++ b/web/react/components/command_list.jsx
@@ -3,25 +3,40 @@
var client = require('../utils/client.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { suggestions: [ ], cmd: "" };
- },
- handleClick: function(i) {
- this.props.addCommand(this.state.suggestions[i].suggestion)
- this.setState({ suggestions: [ ], cmd: "" });
- },
- addFirstCommand: function() {
- if (this.state.suggestions.length == 0) return;
+export default class CommandList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ this.addFirstCommand = this.addFirstCommand.bind(this);
+ this.isEmpty = this.isEmpty.bind(this);
+ this.getSuggestedCommands = this.getSuggestedCommands.bind(this);
+
+ this.state = {
+ suggestions: [ ],
+ cmd: ''
+ };
+ }
+
+ handleClick(i) {
+ this.props.addCommand(this.state.suggestions[i].suggestion);
+ this.setState({suggestions: [ ], cmd: ''});
+ }
+
+ addFirstCommand() {
+ if (this.state.suggestions.length === 0) {
+ return;
+ }
this.handleClick(0);
- },
- isEmpty: function() {
- return this.state.suggestions.length == 0;
- },
- getSuggestedCommands: function(cmd) {
+ }
- if (!cmd || cmd.charAt(0) != '/') {
- this.setState({ suggestions: [ ], cmd: "" });
+ isEmpty() {
+ return this.state.suggestions.length === 0;
+ }
+
+ getSuggestedCommands(cmd) {
+ if (!cmd || cmd.charAt(0) !== '/') {
+ this.setState({suggestions: [ ], cmd: ''});
return;
}
@@ -29,36 +44,56 @@ module.exports = React.createClass({
this.props.channelId,
cmd,
true,
- function(data) {
+ function success(data) {
if (data.suggestions.length === 1 && data.suggestions[0].suggestion === cmd) {
data.suggestions = [];
}
- this.setState({ suggestions: data.suggestions, cmd: cmd });
+ this.setState({suggestions: data.suggestions, cmd: cmd});
}.bind(this),
- function(err){
- }
+ function fail() {
+ }
);
- },
- render: function() {
- if (this.state.suggestions.length == 0) return (<div/>);
+ }
+
+ render() {
+ if (this.state.suggestions.length === 0) {
+ return (<div/>);
+ }
var suggestions = [];
for (var i = 0; i < this.state.suggestions.length; i++) {
- if (this.state.suggestions[i].suggestion != this.state.cmd) {
+ if (this.state.suggestions[i].suggestion !== this.state.cmd) {
suggestions.push(
- <div key={i} className="command-name" onClick={this.handleClick.bind(this, i)}>
- <div className="pull-left"><strong>{ this.state.suggestions[i].suggestion }</strong></div>
- <div className="command-desc pull-right">{ this.state.suggestions[i].description }</div>
+ <div
+ key={i}
+ className='command-name'
+ onClick={this.handleClick.bind(this, i)}
+ >
+ <div className='command__title'><strong>{this.state.suggestions[i].suggestion}</strong></div>
+ <div className='command__desc'>{this.state.suggestions[i].description}</div>
</div>
);
}
}
return (
- <div ref="mentionlist" className="command-box" style={{height:(this.state.suggestions.length*37)+2}}>
- { suggestions }
+ <div
+ ref='mentionlist'
+ className='command-box'
+ style={{height: (this.state.suggestions.length * 56) + 2}}
+ >
+ {suggestions}
</div>
);
}
-});
+}
+
+CommandList.defaultProps = {
+ channelId: null
+};
+
+CommandList.propTypes = {
+ addCommand: React.PropTypes.func,
+ channelId: React.PropTypes.string
+}; \ No newline at end of file
diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx
index 3be13cf9b..cb3b9c5e3 100644
--- a/web/react/components/confirm_modal.jsx
+++ b/web/react/components/confirm_modal.jsx
@@ -1,31 +1,70 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- handleConfirm: function() {
- $('#'+this.props.parent_id).attr('data-confirm', 'true');
- $('#'+this.props.parent_id).modal('hide');
- $('#'+this.props.id).modal('hide');
- },
- render: function() {
+export default class ConfirmModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleConfirm = this.handleConfirm.bind(this);
+
+ this.state = {};
+ }
+ handleConfirm() {
+ $('#' + this.props.parent_id).attr('data-confirm', 'true');
+ $('#' + this.props.parent_id).modal('hide');
+ $('#' + this.props.id).modal('hide');
+ }
+ render() {
return (
- <div className="modal fade" id={this.props.id} tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <h4 className="modal-title">{this.props.title}</h4>
- </div>
- <div className="modal-body">
- {this.props.message}
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button onClick={this.handleConfirm} type="button" className="btn btn-primary">{this.props.confirm_button}</button>
+ <div
+ className='modal fade'
+ id={this.props.id}
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <h4 className='modal-title'>{this.props.title}</h4>
+ </div>
+ <div className='modal-body'>
+ {this.props.message}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleConfirm}
+ type='button'
+ className='btn btn-primary'
+ >
+ {this.props.confirm_button}
+ </button>
+ </div>
</div>
- </div>
- </div>
+ </div>
</div>
);
}
-});
+}
+ConfirmModal.defaultProps = {
+ parent_id: '',
+ id: '',
+ title: '',
+ message: '',
+ confirm_button: ''
+};
+ConfirmModal.propTypes = {
+ parent_id: React.PropTypes.string,
+ id: React.PropTypes.string,
+ title: React.PropTypes.string,
+ message: React.PropTypes.string,
+ confirm_button: React.PropTypes.string
+};
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index c2b7e222f..c2fc0dcf3 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -1,24 +1,48 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var Textbox = require('./textbox.jsx');
-var MsgTyping = require('./msg_typing.jsx');
-var FileUpload = require('./file_upload.jsx');
-var FilePreview = require('./file_preview.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-module.exports = React.createClass({
- lastTime: 0,
- handleSubmit: function(e) {
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const Textbox = require('./textbox.jsx');
+const MsgTyping = require('./msg_typing.jsx');
+const FileUpload = require('./file_upload.jsx');
+const FilePreview = require('./file_preview.jsx');
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class CreateComment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.lastTime = 0;
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.handleUploadStart = this.handleUploadStart.bind(this);
+ this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
+ this.handleUploadError = this.handleUploadError.bind(this);
+ this.removePreview = this.removePreview.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.getFileCount = this.getFileCount.bind(this);
+
+ PostStore.clearCommentDraftUploads();
+
+ const draft = PostStore.getCommentDraft(this.props.rootId);
+ this.state = {
+ messageText: draft.message,
+ uploadsInProgress: draft.uploadsInProgress,
+ previews: draft.previews,
+ submitting: false
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
if (this.state.uploadsInProgress.length > 0) {
@@ -29,7 +53,7 @@ module.exports = React.createClass({
return;
}
- var post = {};
+ let post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -38,30 +62,30 @@ module.exports = React.createClass({
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: 'Comment length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'});
+ this.setState({postError: `Comment length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return;
}
- var user_id = UserStore.getCurrentId();
+ const userId = UserStore.getCurrentId();
post.channel_id = this.props.channelId;
post.root_id = this.props.rootId;
post.parent_id = this.props.rootId;
post.filenames = this.state.previews;
- var time = utils.getTimestamp();
- post.pending_post_id = user_id + ':'+ time;
- post.user_id = user_id;
+ const time = Utils.getTimestamp();
+ post.pending_post_id = `${userId}:${time}`;
+ post.user_id = userId;
post.create_at = time;
PostStore.storePendingPost(post);
PostStore.storeCommentDraft(this.props.rootId, null);
- client.createPost(post, ChannelStore.getCurrent(),
- function(data) {
+ Client.createPost(post, ChannelStore.getCurrent(),
+ function handlePostSuccess(data) {
AsyncClient.getPosts(this.props.channelId);
- var channel = ChannelStore.get(this.props.channelId);
- var member = ChannelStore.getMember(this.props.channelId);
+ const channel = ChannelStore.get(this.props.channelId);
+ let member = ChannelStore.getMember(this.props.channelId);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
@@ -71,8 +95,8 @@ module.exports = React.createClass({
post: data
});
}.bind(this),
- function(err) {
- var state = {};
+ function handlePostError(err) {
+ let state = {};
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
@@ -90,76 +114,76 @@ module.exports = React.createClass({
);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- },
- commentMsgKeyPress: function(e) {
+ }
+ commentMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.textbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e);
}
- var t = Date.now();
+ const t = Date.now();
if ((t - this.lastTime) > 5000) {
- SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {'parent_id': this.props.rootId}});
+ SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}});
this.lastTime = t;
}
- },
- handleUserInput: function(messageText) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ }
+ handleUserInput(messageText) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
draft.message = messageText;
PostStore.storeCommentDraft(this.props.rootId, draft);
$('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight);
$('.post-right__scroll').perfectScrollbar('update');
this.setState({messageText: messageText});
- },
- handleUploadStart: function(clientIds, channelId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ }
+ handleUploadStart(clientIds) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
- draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
+ draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress']});
- },
- handleFileUploadComplete: function(filenames, clientIds, channelId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ this.setState({uploadsInProgress: draft.uploadsInProgress});
+ }
+ handleFileUploadComplete(filenames, clientIds) {
+ let draft = PostStore.getCommentDraft(this.props.rootId);
// remove each finished file from uploads
- for (var i = 0; i < clientIds.length; i++) {
- var index = draft['uploadsInProgress'].indexOf(clientIds[i]);
+ for (let i = 0; i < clientIds.length; i++) {
+ const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
}
- draft['previews'] = draft['previews'].concat(filenames);
+ draft.previews = draft.previews.concat(filenames);
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
- },
- handleUploadError: function(err, clientId) {
+ this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ }
+ handleUploadError(err, clientId) {
if (clientId !== -1) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ let draft = PostStore.getCommentDraft(this.props.rootId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
+ const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else {
this.setState({serverError: err});
}
- },
- removePreview: function(id) {
- var previews = this.state.previews;
- var uploadsInProgress = this.state.uploadsInProgress;
+ }
+ removePreview(id) {
+ let previews = this.state.previews;
+ let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload
- var index = previews.indexOf(id);
+ let index = previews.indexOf(id);
if (index !== -1) {
previews.splice(index, 1);
} else {
@@ -171,30 +195,24 @@ module.exports = React.createClass({
}
}
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ let draft = PostStore.getCommentDraft(this.props.rootId);
draft.previews = previews;
draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCommentDraft(this.props.rootId, draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
- },
- getInitialState: function() {
- PostStore.clearCommentDraftUploads();
-
- var draft = PostStore.getCommentDraft(this.props.rootId);
- return {messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews'], submitting: false};
- },
- componentWillReceiveProps: function(newProps) {
+ }
+ componentWillReceiveProps(newProps) {
if (newProps.rootId !== this.props.rootId) {
- var draft = PostStore.getCommentDraft(newProps.rootId);
- this.setState({messageText: draft['message'], uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
+ const draft = PostStore.getCommentDraft(newProps.rootId);
+ this.setState({messageText: draft.message, uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
}
- },
- getFileCount: function(channelId) {
+ }
+ getFileCount() {
return this.state.previews.length + this.state.uploadsInProgress.length;
- },
- render: function() {
- var serverError = null;
+ }
+ render() {
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='form-group has-error'>
@@ -203,22 +221,23 @@ module.exports = React.createClass({
);
}
- var postError = null;
+ let postError = null;
if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>;
}
- var preview = null;
+ let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
files={this.state.previews}
onRemove={this.removePreview}
- uploadsInProgress={this.state.uploadsInProgress} />
+ uploadsInProgress={this.state.uploadsInProgress}
+ />
);
}
- var postFooterClassName = 'post-create-footer';
+ let postFooterClassName = 'post-create-footer';
if (postError) {
postFooterClassName += ' has-error';
}
@@ -226,7 +245,10 @@ module.exports = React.createClass({
return (
<form onSubmit={this.handleSubmit}>
<div className='post-create'>
- <div id={this.props.rootId} className='post-create-body comment-create-body'>
+ <div
+ id={this.props.rootId}
+ className='post-create-body comment-create-body'
+ >
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.commentMsgKeyPress}
@@ -234,7 +256,8 @@ module.exports = React.createClass({
createMessage='Add a comment...'
initialText=''
id='reply_textbox'
- ref='textbox' />
+ ref='textbox'
+ />
<FileUpload
ref='fileUpload'
getFileCount={this.getFileCount}
@@ -242,11 +265,20 @@ module.exports = React.createClass({
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
postType='comment'
- channelId={this.props.channelId} />
+ channelId={this.props.channelId}
+ />
</div>
- <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />
+ <MsgTyping
+ channelId={this.props.channelId}
+ parentId={this.props.rootId}
+ />
<div className={postFooterClassName}>
- <input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} />
+ <input
+ type='button'
+ className='btn btn-primary comment-btn pull-right'
+ value='Add Comment'
+ onClick={this.handleSubmit}
+ />
{postError}
{serverError}
</div>
@@ -255,4 +287,9 @@ module.exports = React.createClass({
</form>
);
}
-});
+}
+
+CreateComment.propTypes = {
+ channelId: React.PropTypes.string.isRequired,
+ rootId: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index b9142223f..871b72a43 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -1,33 +1,68 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
-var MsgTyping = require('./msg_typing.jsx');
-var Textbox = require('./textbox.jsx');
-var FileUpload = require('./file_upload.jsx');
-var FilePreview = require('./file_preview.jsx');
-var utils = require('../utils/utils.jsx');
-
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-module.exports = React.createClass({
- displayName: 'CreatePost',
- lastTime: 0,
- handleSubmit: function(e) {
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const SocketStore = require('../stores/socket_store.jsx');
+const MsgTyping = require('./msg_typing.jsx');
+const Textbox = require('./textbox.jsx');
+const FileUpload = require('./file_upload.jsx');
+const FilePreview = require('./file_preview.jsx');
+const Utils = require('../utils/utils.jsx');
+
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class CreatePost extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.lastTime = 0;
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.resizePostHolder = this.resizePostHolder.bind(this);
+ this.handleUploadStart = this.handleUploadStart.bind(this);
+ this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this);
+ this.handleUploadError = this.handleUploadError.bind(this);
+ this.removePreview = this.removePreview.bind(this);
+ this.onChange = this.onChange.bind(this);
+ this.getFileCount = this.getFileCount.bind(this);
+
+ PostStore.clearDraftUploads();
+
+ const draft = PostStore.getCurrentDraft();
+ let previews = [];
+ let messageText = '';
+ let uploadsInProgress = [];
+ if (draft && draft.previews && draft.message) {
+ previews = draft.previews;
+ messageText = draft.message;
+ uploadsInProgress = draft.uploadsInProgress;
+ }
+
+ this.state = {
+ channelId: ChannelStore.getCurrentId(),
+ messageText: messageText,
+ uploadsInProgress: uploadsInProgress,
+ previews: previews,
+ submitting: false,
+ initialText: messageText
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
if (this.state.uploadsInProgress.length > 0 || this.state.submitting) {
return;
}
- var post = {};
+ let post = {};
post.filenames = [];
post.message = this.state.messageText;
@@ -36,18 +71,18 @@ module.exports = React.createClass({
}
if (post.message.length > Constants.CHARACTER_LIMIT) {
- this.setState({postError: 'Post length must be less than ' + Constants.CHARACTER_LIMIT + ' characters.'});
+ this.setState({postError: `Post length must be less than ${Constants.CHARACTER_LIMIT} characters.`});
return;
}
this.setState({submitting: true, serverError: null});
if (post.message.indexOf('/') === 0) {
- client.executeCommand(
+ Client.executeCommand(
this.state.channelId,
post.message,
false,
- function(data) {
+ function handleCommandSuccess(data) {
PostStore.storeDraft(data.channel_id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
@@ -55,8 +90,8 @@ module.exports = React.createClass({
window.location.href = data.goto_location;
}
}.bind(this),
- function(err) {
- var state = {};
+ function handleCommandError(err) {
+ let state = {};
state.serverError = err.message;
state.submitting = false;
this.setState(state);
@@ -66,26 +101,25 @@ module.exports = React.createClass({
post.channel_id = this.state.channelId;
post.filenames = this.state.previews;
- var time = utils.getTimestamp();
- var userId = UserStore.getCurrentId();
- post.pending_post_id = userId + ':' + time;
+ const time = Utils.getTimestamp();
+ const userId = UserStore.getCurrentId();
+ post.pending_post_id = `${userId}:${time}`;
post.user_id = userId;
post.create_at = time;
post.root_id = this.state.rootId;
post.parent_id = this.state.parentId;
- var channel = ChannelStore.get(this.state.channelId);
+ const channel = ChannelStore.get(this.state.channelId);
PostStore.storePendingPost(post);
PostStore.storeDraft(channel.id, null);
this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null});
- client.createPost(post, channel,
- function(data) {
- this.resizePostHolder();
+ Client.createPost(post, channel,
+ function handlePostSuccess(data) {
AsyncClient.getPosts();
- var member = ChannelStore.getMember(channel.id);
+ let member = ChannelStore.getMember(channel.id);
member.msg_count = channel.total_msg_count;
member.last_viewed_at = Date.now();
ChannelStore.setChannelMember(member);
@@ -94,9 +128,9 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST,
post: data
});
- }.bind(this),
- function(err) {
- var state = {};
+ },
+ function handlePostError(err) {
+ let state = {};
if (err.message === 'Invalid RootId parameter') {
if ($('#post_deleted').length > 0) {
@@ -113,83 +147,79 @@ module.exports = React.createClass({
}.bind(this)
);
}
- },
- componentDidUpdate: function() {
- this.resizePostHolder();
- },
- postMsgKeyPress: function(e) {
+ }
+ postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.textbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e);
}
- var t = Date.now();
+ const t = Date.now();
if ((t - this.lastTime) > 5000) {
- SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {'parent_id': ''}, state: {}});
+ SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}});
this.lastTime = t;
}
- },
- handleUserInput: function(messageText) {
- this.resizePostHolder();
+ }
+ handleUserInput(messageText) {
this.setState({messageText: messageText});
- var draft = PostStore.getCurrentDraft();
- draft['message'] = messageText;
+ let draft = PostStore.getCurrentDraft();
+ draft.message = messageText;
PostStore.storeCurrentDraft(draft);
- },
- resizePostHolder: function() {
- var height = $(window).height() - $(this.refs.topDiv.getDOMNode()).height() - $('#error_bar').outerHeight() - 50;
- $('.post-list-holder-by-time').css('height', height + 'px');
+ }
+ resizePostHolder() {
+ const height = $(window).height() - $(React.findDOMNode(this.refs.topDiv)).height() - $('#error_bar').outerHeight() - 50;
+ $('.post-list-holder-by-time').css('height', `${height}px`);
$(window).trigger('resize');
- },
- handleUploadStart: function(clientIds, channelId) {
- var draft = PostStore.getDraft(channelId);
+ }
+ handleUploadStart(clientIds, channelId) {
+ let draft = PostStore.getDraft(channelId);
- draft['uploadsInProgress'] = draft['uploadsInProgress'].concat(clientIds);
+ draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds);
PostStore.storeDraft(channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress']});
- },
- handleFileUploadComplete: function(filenames, clientIds, channelId) {
- var draft = PostStore.getDraft(channelId);
+ this.setState({uploadsInProgress: draft.uploadsInProgress});
+ }
+ handleFileUploadComplete(filenames, clientIds, channelId) {
+ let draft = PostStore.getDraft(channelId);
// remove each finished file from uploads
- for (var i = 0; i < clientIds.length; i++) {
- var index = draft['uploadsInProgress'].indexOf(clientIds[i]);
+ for (let i = 0; i < clientIds.length; i++) {
+ const index = draft.uploadsInProgress.indexOf(clientIds[i]);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
}
- draft['previews'] = draft['previews'].concat(filenames);
+ draft.previews = draft.previews.concat(filenames);
PostStore.storeDraft(channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
- },
- handleUploadError: function(err, clientId) {
+ this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews});
+ }
+ handleUploadError(err, clientId) {
if (clientId !== -1) {
- var draft = PostStore.getDraft(this.state.channelId);
+ let draft = PostStore.getDraft(this.state.channelId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
+ const index = draft.uploadsInProgress.indexOf(clientId);
if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
+ draft.uploadsInProgress.splice(index, 1);
}
PostStore.storeDraft(this.state.channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err});
} else {
this.setState({serverError: err});
}
- },
- removePreview: function(id) {
- var previews = this.state.previews;
- var uploadsInProgress = this.state.uploadsInProgress;
+ }
+ removePreview(id) {
+ let previews = this.state.previews;
+ let uploadsInProgress = this.state.uploadsInProgress;
// id can either be the path of an uploaded file or the client id of an in progress upload
- var index = previews.indexOf(id);
+ let index = previews.indexOf(id);
if (index !== -1) {
previews.splice(index, 1);
} else {
@@ -201,28 +231,28 @@ module.exports = React.createClass({
}
}
- var draft = PostStore.getCurrentDraft();
- draft['previews'] = previews;
- draft['uploadsInProgress'] = uploadsInProgress;
+ let draft = PostStore.getCurrentDraft();
+ draft.previews = previews;
+ draft.uploadsInProgress = uploadsInProgress;
PostStore.storeCurrentDraft(draft);
this.setState({previews: previews, uploadsInProgress: uploadsInProgress});
- },
- componentDidMount: function() {
- ChannelStore.addChangeListener(this._onChange);
+ }
+ componentDidMount() {
+ ChannelStore.addChangeListener(this.onChange);
this.resizePostHolder();
- },
- componentWillUnmount: function() {
- ChannelStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var channelId = ChannelStore.getCurrentId();
+ }
+ componentWillUnmount() {
+ ChannelStore.removeChangeListener(this.onChange);
+ }
+ onChange() {
+ const channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) {
- var draft = PostStore.getCurrentDraft();
+ let draft = PostStore.getCurrentDraft();
- var previews = [];
- var messageText = '';
- var uploadsInProgress = [];
+ let previews = [];
+ let messageText = '';
+ let uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
@@ -231,33 +261,17 @@ module.exports = React.createClass({
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
}
- },
- getInitialState: function() {
- PostStore.clearDraftUploads();
-
- var draft = PostStore.getCurrentDraft();
- var previews = [];
- var messageText = '';
- var uploadsInProgress = [];
- if (draft && draft.previews && draft.message) {
- previews = draft.previews;
- messageText = draft.message;
- uploadsInProgress = draft.uploadsInProgress;
- }
-
- return {channelId: ChannelStore.getCurrentId(), messageText: messageText, uploadsInProgress: uploadsInProgress, previews: previews, submitting: false, initialText: messageText};
- },
- getFileCount: function(channelId) {
+ }
+ getFileCount(channelId) {
if (channelId === this.state.channelId) {
return this.state.previews.length + this.state.uploadsInProgress.length;
- } else {
- var draft = PostStore.getDraft(channelId);
-
- return draft['previews'].length + draft['uploadsInProgress'].length;
}
- },
- render: function() {
- var serverError = null;
+
+ const draft = PostStore.getDraft(channelId);
+ return draft.previews.length + draft.uploadsInProgress.length;
+ }
+ render() {
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='has-error'>
@@ -266,38 +280,46 @@ module.exports = React.createClass({
);
}
- var postError = null;
+ let postError = null;
if (this.state.postError) {
postError = <label className='control-label'>{this.state.postError}</label>;
}
- var preview = null;
+ let preview = null;
if (this.state.previews.length > 0 || this.state.uploadsInProgress.length > 0) {
preview = (
<FilePreview
files={this.state.previews}
onRemove={this.removePreview}
- uploadsInProgress={this.state.uploadsInProgress} />
+ uploadsInProgress={this.state.uploadsInProgress}
+ />
);
}
- var postFooterClassName = 'post-create-footer';
+ let postFooterClassName = 'post-create-footer';
if (postError) {
postFooterClassName += ' has-error';
}
return (
- <form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}>
+ <form
+ id='create_post'
+ ref='topDiv'
+ role='form'
+ onSubmit={this.handleSubmit}
+ >
<div className='post-create'>
<div className='post-create-body'>
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress}
+ onHeightChange={this.resizePostHolder}
messageText={this.state.messageText}
createMessage='Write a message...'
channelId={this.state.channelId}
id='post_textbox'
- ref='textbox' />
+ ref='textbox'
+ />
<FileUpload
ref='fileUpload'
getFileCount={this.getFileCount}
@@ -305,16 +327,20 @@ module.exports = React.createClass({
onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError}
postType='post'
- channelId='' />
+ channelId=''
+ />
</div>
<div className={postFooterClassName}>
{postError}
{serverError}
{preview}
- <MsgTyping channelId={this.state.channelId} parentId=''/>
+ <MsgTyping
+ channelId={this.state.channelId}
+ parentId=''
+ />
</div>
</div>
</form>
);
}
-});
+}
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index 589737271..4efb9cb23 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -1,58 +1,99 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client =require('../utils/client.jsx');
-var AsyncClient =require('../utils/async_client.jsx');
-var ChannelStore =require('../stores/channel_store.jsx')
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
-module.exports = React.createClass({
- handleDelete: function(e) {
- if (this.state.channel_id.length != 26) return;
+export default class DeleteChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
- Client.deleteChannel(this.state.channel_id,
- function(data) {
+ this.handleDelete = this.handleDelete.bind(this);
+
+ this.state = {
+ title: '',
+ channelId: ''
+ };
+ }
+ handleDelete() {
+ if (this.state.channelId.length !== 26) {
+ return;
+ }
+
+ Client.deleteChannel(this.state.channelId,
+ function handleDeleteSuccess() {
AsyncClient.getChannels(true);
window.location.href = '/';
- }.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "handleDelete");
- }.bind(this)
+ },
+ function handleDeleteError(err) {
+ AsyncClient.dispatchError(err, 'handleDelete');
+ }
);
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
var button = $(e.relatedTarget);
- self.setState({ title: button.attr('data-title'), channel_id: button.attr('data-channelid') });
- });
- },
- getInitialState: function() {
- return { title: "", channel_id: "" };
- },
- render: function() {
-
- var channelType = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'P' ? "private group" : "channel"
+ this.setState({
+ title: button.attr('data-title'),
+ channelId: button.attr('data-channelid')
+ });
+ }.bind(this));
+ }
+ render() {
+ const channel = ChannelStore.getCurrent();
+ let channelType = 'channel';
+ if (channel && channel.type === 'P') {
+ channelType = 'private group';
+ }
return (
- <div className="modal fade" ref="modal" id="delete_channel" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Confirm DELETE Channel</h4>
- </div>
- <div className="modal-body">
- <p>
- Are you sure you wish to delete the {this.state.title} {channelType}?
- </p>
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button>
- </div>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='delete_channel'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Confirm DELETE Channel</h4>
+ </div>
+ <div className='modal-body'>
+ <p>
+ Are you sure you wish to delete the {this.state.title} {channelType}?
+ </p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-danger'
+ data-dismiss='modal'
+ onClick={this.handleDelete}
+ >
+ Delete
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 55d6f509c..075f9c742 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -4,20 +4,28 @@
var Client = require('../utils/client.jsx');
var PostStore = require('../stores/post_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-module.exports = React.createClass({
- handleDelete: function(e) {
- Client.deletePost(this.state.channel_id, this.state.post_id,
- function(data) {
- var selected_list = this.state.selectedList;
- if (selected_list && selected_list.order && selected_list.order.length > 0) {
- var selected_post = selected_list.posts[selected_list.order[0]];
- if ((selected_post.id === this.state.post_id && this.state.title === "Post") || selected_post.root_id === this.state.post_id) {
+export default class DeletePostModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleDelete = this.handleDelete.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0};
+ }
+ handleDelete() {
+ Client.deletePost(this.state.channelId, this.state.postId,
+ function deleteSuccess() {
+ var selectedList = this.state.selectedList;
+ if (selectedList && selectedList.order && selectedList.order.length > 0) {
+ var selectedPost = selectedList.posts[selectedList.order[0]];
+ if ((selectedPost.id === this.state.postId && this.state.title === 'Post') || selectedPost.root_id === this.state.postId) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH,
results: null
@@ -27,14 +35,14 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
- } else if (selected_post.id === this.state.post_id && this.state.title === "Comment") {
- if (selected_post.root_id && selected_post.root_id.length > 0 && selected_list.posts[selected_post.root_id]) {
- selected_list.order = [selected_post.root_id];
- delete selected_list.posts[selected_post.id];
+ } else if (selectedPost.id === this.state.postId && this.state.title === 'Comment') {
+ if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) {
+ selectedList.order = [selectedPost.root_id];
+ delete selectedList.posts[selectedPost.id];
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST_SELECTED,
- post_list: selected_list
+ post_list: selectedList
});
AppDispatcher.handleServerAction({
@@ -44,67 +52,97 @@ module.exports = React.createClass({
}
}
}
- PostStore.removePost(this.state.post_id, this.state.channel_id);
- AsyncClient.getPosts(this.state.channel_id);
+ PostStore.removePost(this.state.postId, this.state.channelId);
+ AsyncClient.getPosts(this.state.channelId);
}.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "deletePost");
- }.bind(this)
+ function deleteFailed(err) {
+ AsyncClient.dispatchError(err, 'deletePost');
+ }
);
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) {
var newState = {};
- if(BrowserStore.getItem('edit_state_transfer')) {
+ if (BrowserStore.getItem('edit_state_transfer')) {
newState = BrowserStore.getItem('edit_state_transfer');
BrowserStore.removeItem('edit_state_transfer');
} else {
var button = e.relatedTarget;
- newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') };
+ newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')};
}
- self.setState(newState);
- });
- PostStore.addSelectedPostChangeListener(this._onChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSelectedPostChangeListener(this._onChange);
- },
- _onChange: function() {
+ this.setState(newState);
+ }.bind(this));
+ PostStore.addSelectedPostChangeListener(this.onListenerChange);
+ }
+ componentWillUnmount() {
+ PostStore.removeSelectedPostChangeListener(this.onListenerChange);
+ }
+ onListenerChange() {
var newList = PostStore.getSelectedPost();
- if (!utils.areStatesEqual(this.state.selectedList, newList)) {
- this.setState({ selectedList: newList });
+ if (!Utils.areStatesEqual(this.state.selectedList, newList)) {
+ this.setState({selectedList: newList});
+ }
+ }
+ render() {
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var commentWarning = '';
+ if (this.state.comments > 0) {
+ commentWarning = 'This post has ' + this.state.comments + ' comment(s) on it.';
}
- },
- getInitialState: function() {
- return { title: "", post_id: "", channel_id: "", selectedList: PostStore.getSelectedPost(), comments: 0 };
- },
- render: function() {
- var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
return (
- <div className="modal fade" id="delete_post" ref="modal" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog modal-push-down">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Confirm {this.state.title} Delete</h4>
+ <div
+ className='modal fade'
+ id='delete_post'
+ ref='modal'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog modal-push-down'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Confirm {this.state.title} Delete</h4>
</div>
- <div className="modal-body">
+ <div className='modal-body'>
Are you sure you want to delete the {this.state.title.toLowerCase()}?
<br/>
<br/>
- { this.state.comments > 0 ?
- "This post has " + this.state.comments + " comment(s) on it."
- : "" }
+ {commentWarning}
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button>
+ {error}
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-danger'
+ data-dismiss='modal'
+ onClick={this.handleDelete}
+ >
+ Delete
+ </button>
</div>
</div>
</div>
</div>
);
}
-});
+}
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 76f0c2c4d..e93bab431 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -1,79 +1,142 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
-module.exports = React.createClass({
- handleEdit: function(e) {
+export default class EditChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleEdit = this.handleEdit.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = {
+ description: '',
+ title: '',
+ channelId: '',
+ serverError: ''
+ };
+ }
+ handleEdit() {
var data = {};
- data["channel_id"] = this.state.channel_id;
- if (data["channel_id"].length !== 26) return;
- data["channel_description"] = this.state.description.trim();
+ data.channel_id = this.state.channelId;
+
+ if (data.channel_id.length !== 26) {
+ return;
+ }
+
+ data.channel_description = this.state.description.trim();
Client.updateChannelDesc(data,
- function(data) {
- this.setState({ server_error: "" });
- AsyncClient.getChannel(this.state.channel_id);
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function handleUpdateSuccess() {
+ this.setState({serverError: ''});
+ AsyncClient.getChannel(this.state.channelId);
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this),
- function(err) {
- if (err.message === "Invalid channel_description parameter") {
- this.setState({ server_error: "This description is too long, please enter a shorter one" });
- }
- else {
- this.setState({ server_error: err.message });
+ function handleUpdateError(err) {
+ if (err.message === 'Invalid channel_description parameter') {
+ this.setState({serverError: 'This description is too long, please enter a shorter one'});
+ } else {
+ this.setState({serverError: err.message});
}
}.bind(this)
);
- },
- handleUserInput: function(e) {
- this.setState({ description: e.target.value });
- },
- handleClose: function() {
- this.setState({description: "", server_error: ""});
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = e.relatedTarget;
- self.setState({ description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), server_error: "" });
- });
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose)
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose)
- },
- getInitialState: function() {
- return { description: "", title: "", channel_id: "" };
- },
- render: function() {
- var server_error = this.state.server_error ? <div className='form-group has-error'><br/><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ }
+ handleUserInput(e) {
+ this.setState({description: e.target.value});
+ }
+ handleClose() {
+ this.setState({description: '', serverError: ''});
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
+ const button = e.relatedTarget;
+ this.setState({description: $(button).attr('data-desc'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''});
+ }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ render() {
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>;
+ }
- var editTitle = <h4 className='modal-title' ref='title'>Edit Description</h4>;
+ var editTitle = (
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Edit Description
+ </h4>
+ );
if (this.state.title) {
- editTitle = <h4 className='modal-title' ref='title'>Edit Description for <span className='name'>{this.state.title}</span></h4>;
+ editTitle = (
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Edit Description for <span className='name'>{this.state.title}</span>
+ </h4>
+ );
}
return (
- <div className="modal fade" ref="modal" id="edit_channel" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- {editTitle}
- </div>
- <div className="modal-body">
- <textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea>
- { server_error }
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button>
- </div>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='edit_channel'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ {editTitle}
+ </div>
+ <div className='modal-body'>
+ <textarea
+ className='form-control no-resize'
+ rows='6'
+ ref='channelDesc'
+ maxLength='1024'
+ value={this.state.description}
+ onChange={this.handleUserInput}
+ />
+ {serverError}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleEdit}
+ >
+ Save
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index 1c5a1ed5e..ad7bd30b5 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -3,13 +3,21 @@
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-var Constants = require('../utils/constants.jsx');
-var utils = require('../utils/utils.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-module.exports = React.createClass({
- handleEdit: function(e) {
+export default class EditPostModal extends React.Component {
+ constructor() {
+ super();
+
+ this.handleEdit = this.handleEdit.bind(this);
+ this.handleEditInput = this.handleEditInput.bind(this);
+ this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+
+ this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
+ }
+ handleEdit() {
var updatedPost = {};
updatedPost.message = this.state.editText.trim();
@@ -17,8 +25,8 @@ module.exports = React.createClass({
var tempState = this.state;
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
- $("#edit_post").modal('hide');
- $("#delete_post").modal('show');
+ $('#edit_post').modal('hide');
+ $('#delete_post').modal('show');
return;
}
@@ -26,79 +34,106 @@ module.exports = React.createClass({
updatedPost.channel_id = this.state.channel_id;
Client.updatePost(updatedPost,
- function(data) {
+ function success() {
AsyncClient.getPosts(this.state.channel_id);
window.scrollTo(0, 0);
}.bind(this),
- function(err) {
- AsyncClient.dispatchError(err, "updatePost");
- }.bind(this)
+ function error(err) {
+ AsyncClient.dispatchError(err, 'updatePost');
+ }
);
- $("#edit_post").modal('hide');
+ $('#edit_post').modal('hide');
$(this.state.refocusId).focus();
- },
- handleEditInput: function(editMessage) {
+ }
+ handleEditInput(editMessage) {
this.setState({editText: editMessage});
- },
- handleEditKeyPress: function(e) {
- if (e.which == 13 && !e.shiftKey && !e.altKey) {
+ }
+ handleEditKeyPress(e) {
+ if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault();
- this.refs.editbox.getDOMNode().blur();
+ React.findDOMNode(this.refs.editbox).blur();
this.handleEdit(e);
}
- },
- handleUserInput: function(e) {
- this.setState({ editText: e.target.value });
- },
- componentDidMount: function() {
+ }
+ handleUserInput(e) {
+ this.setState({editText: e.target.value});
+ }
+ componentDidMount() {
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({editText: "", title: "", channel_id: "", post_id: "", comments: 0, refocusId: "", error: ''});
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function onHidden() {
+ self.setState({editText: '', title: '', channel_id: '', post_id: '', comments: 0, refocusId: '', error: ''});
});
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) {
var button = e.relatedTarget;
- self.setState({ editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid') });
+ self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')});
});
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() {
self.refs.editbox.resize();
});
- },
- getInitialState: function() {
- return { editText: "", title: "", post_id: "", channel_id: "", comments: 0, refocusId: "" };
- },
- render: function() {
- var error = this.state.error ? <div className='form-group has-error'><br /><label className='control-label'>{ this.state.error }</label></div> : <div className='form-group'><br /></div>;
+ }
+ render() {
+ var error = (<div className='form-group'><br /></div>);
+ if (this.state.error) {
+ error = (<div className='form-group has-error'><br /><label className='control-label'>{this.state.error}</label></div>);
+ }
return (
- <div className="modal fade edit-modal" ref="modal" id="edit_post" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog modal-push-down">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={this.handleEditClose}><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title">Edit {this.state.title}</h4>
- </div>
- <div className="edit-modal-body modal-body">
- <Textbox
- onUserInput={this.handleEditInput}
- onKeyPress={this.handleEditKeyPress}
- messageText={this.state.editText}
- createMessage="Edit the post..."
- id="edit_textbox"
- ref="editbox"
- />
- { error }
- </div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button>
- </div>
+ <div
+ className='modal fade edit-modal'
+ ref='modal'
+ id='edit_post'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog modal-push-down'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ onClick={this.handleEditClose}
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4 className='modal-title'>Edit {this.state.title}</h4>
+ </div>
+ <div className='edit-modal-body modal-body'>
+ <Textbox
+ onUserInput={this.handleEditInput}
+ onKeyPress={this.handleEditKeyPress}
+ messageText={this.state.editText}
+ createMessage='Edit the post...'
+ id='edit_textbox'
+ ref='editbox'
+ />
+ {error}
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ type='button'
+ className='btn btn-primary'
+ onClick={this.handleEdit}
+ >
+ Save
+ </button>
+ </div>
+ </div>
</div>
- </div>
</div>
);
}
-});
+}
diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx
index 678eb9928..95948c8dd 100644
--- a/web/react/components/email_verify.jsx
+++ b/web/react/components/email_verify.jsx
@@ -1,35 +1,62 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- handleResend: function() {
- window.location.href = window.location.href + "&resend=true"
- },
- render: function() {
- var title = "";
- var body = "";
- var resend = "";
- if (this.props.isVerified === "true") {
- title = config.SiteName + " Email Verified";
- body = <p>Your email has been verified! Click <a href={this.props.teamURL + "?email=" + this.props.userEmail}>here</a> to log in.</p>;
+import {config} from '../utils/config.js';
+
+export default class EmailVerify extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleResend = this.handleResend.bind(this);
+
+ this.state = {};
+ }
+ handleResend() {
+ window.location.href = window.location.href + '&resend=true';
+ }
+ render() {
+ var title = '';
+ var body = '';
+ var resend = '';
+ if (this.props.isVerified === 'true') {
+ title = config.SiteName + ' Email Verified';
+ body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
} else {
- title = config.SiteName + " Email Not Verified";
+ title = config.SiteName + ' Email Not Verified';
body = <p>Please verify your email address. Check your inbox for an email.</p>;
- resend = <button onClick={this.handleResend} className="btn btn-primary">Resend Email</button>
+ resend = (
+ <button
+ onClick={this.handleResend}
+ className='btn btn-primary'
+ >
+ Resend Email
+ </button>
+ );
}
return (
- <div className="col-sm-offset-4 col-sm-4">
- <div className="panel panel-default">
- <div className="panel-heading">
- <h3 className="panel-title">{ title }</h3>
+ <div className='col-sm-offset-4 col-sm-4'>
+ <div className='panel panel-default'>
+ <div className='panel-heading'>
+ <h3 className='panel-title'>{title}</h3>
</div>
- <div className="panel-body">
- { body }
- { resend }
+ <div className='panel-body'>
+ {body}
+ {resend}
</div>
</div>
</div>
);
}
-});
+}
+
+EmailVerify.defaultProps = {
+ isVerified: 'false',
+ teamURL: '',
+ userEmail: ''
+};
+EmailVerify.propTypes = {
+ isVerified: React.PropTypes.string,
+ teamURL: React.PropTypes.string,
+ userEmail: React.PropTypes.string
+};
diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx
index f7514a009..87d94a41d 100644
--- a/web/react/components/error_bar.jsx
+++ b/web/react/components/error_bar.jsx
@@ -7,32 +7,40 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getStateFromStores() {
- var error = ErrorStore.getLastError();
- if (error && error.message !== "There appears to be a problem with your internet connection") {
- return { message: error.message };
- } else {
- return { message: null };
- }
-}
+export default class ErrorBar extends React.Component {
+ constructor() {
+ super();
-module.exports = React.createClass({
- displayName: 'ErrorBar',
+ this.onErrorChange = this.onErrorChange.bind(this);
+ this.handleClose = this.handleClose.bind(this);
- componentDidMount: function() {
- ErrorStore.addChangeListener(this._onChange);
+ this.state = this.getStateFromStores();
+ if (this.state.message) {
+ setTimeout(this.handleClose, 10000);
+ }
+ }
+ getStateFromStores() {
+ var error = ErrorStore.getLastError();
+ if (!error || error.message === 'There appears to be a problem with your internet connection') {
+ return {message: null};
+ }
+
+ return {message: error.message};
+ }
+ componentDidMount() {
+ ErrorStore.addChangeListener(this.onErrorChange);
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
- $(window).resize(function() {
+ $(window).resize(function onResize() {
if (this.state.message) {
$('body').css('padding-top', $(React.findDOMNode(this)).outerHeight());
}
}.bind(this));
- },
- componentWillUnmount: function() {
- ErrorStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
- var newState = getStateFromStores();
+ }
+ componentWillUnmount() {
+ ErrorStore.removeChangeListener(this.onErrorChange);
+ }
+ onErrorChange() {
+ var newState = this.getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
if (newState.message) {
setTimeout(this.handleClose, 10000);
@@ -40,9 +48,11 @@ module.exports = React.createClass({
this.setState(newState);
}
- },
- handleClose: function(e) {
- if (e) e.preventDefault();
+ }
+ handleClose(e) {
+ if (e) {
+ e.preventDefault();
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
@@ -50,24 +60,23 @@ module.exports = React.createClass({
});
$('body').css('padding-top', '0');
- },
- getInitialState: function() {
- var state = getStateFromStores();
- if (state.message) {
- setTimeout(this.handleClose, 10000);
- }
- return state;
- },
- render: function() {
+ }
+ render() {
if (this.state.message) {
return (
- <div className="error-bar">
+ <div className='error-bar'>
<span>{this.state.message}</span>
- <a href="#" className="error-bar__close" onClick={this.handleClose}>&times;</a>
+ <a
+ href='#'
+ className='error-bar__close'
+ onClick={this.handleClose}
+ >
+ &times;
+ </a>
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-}); \ No newline at end of file
+}
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index 45e6c5e28..78693df98 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -5,31 +5,24 @@ var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- displayName: "FileAttachment",
- canSetState: false,
- propTypes: {
- // a list of file pathes displayed by the parent FileAttachmentList
- filename: React.PropTypes.string.isRequired,
- // the index of this attachment preview in the parent FileAttachmentList
- index: React.PropTypes.number.isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
- // handler for when the thumbnail is clicked
- handleImageClick: React.PropTypes.func
- },
- getInitialState: function() {
- return {fileSize: -1};
- },
- componentDidMount: function() {
+export default class FileAttachment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.loadFiles = this.loadFiles.bind(this);
+
+ this.canSetState = false;
+ this.state = {fileSize: -1};
+ }
+ componentDidMount() {
this.loadFiles();
- },
- componentDidUpdate: function(prevProps) {
+ }
+ componentDidUpdate(prevProps) {
if (this.props.filename !== prevProps.filename) {
this.loadFiles();
}
- },
- loadFiles: function() {
+ }
+ loadFiles() {
this.canSetState = true;
var filename = this.props.filename;
@@ -39,91 +32,96 @@ module.exports = React.createClass({
var type = utils.getFileType(fileInfo.ext);
// This is a temporary patch to fix issue with old files using absolute paths
- if (fileInfo.path.indexOf("/api/v1/files/get") != -1) {
- fileInfo.path = fileInfo.path.split("/api/v1/files/get")[1];
+ if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) {
+ fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1];
}
- fileInfo.path = utils.getWindowLocationOrigin() + "/api/v1/files/get" + fileInfo.path;
-
- if (type === "image") {
- var self = this;
- $('<img/>').attr('src', fileInfo.path+'_thumb.jpg').load(function(path, name){ return function() {
- $(this).remove();
- if (name in self.refs) {
- var imgDiv = self.refs[name].getDOMNode();
-
- $(imgDiv).removeClass('post__load');
- $(imgDiv).addClass('post__image');
-
- var width = this.width || $(this).width();
- var height = this.height || $(this).height();
-
- if (width < Constants.THUMBNAIL_WIDTH
- && height < Constants.THUMBNAIL_HEIGHT) {
- $(imgDiv).addClass('small');
- } else {
- $(imgDiv).addClass('normal');
+ fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path;
+
+ if (type === 'image') {
+ var self = this; // Need this reference since we use the given "this"
+ $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) {
+ return function loader() {
+ $(this).remove();
+ if (name in self.refs) {
+ var imgDiv = React.findDOMNode(self.refs[name]);
+
+ $(imgDiv).removeClass('post__load');
+ $(imgDiv).addClass('post__image');
+
+ var width = this.width || $(this).width();
+ var height = this.height || $(this).height();
+
+ if (width < Constants.THUMBNAIL_WIDTH &&
+ height < Constants.THUMBNAIL_HEIGHT) {
+ $(imgDiv).addClass('small');
+ } else {
+ $(imgDiv).addClass('normal');
+ }
+
+ var re1 = new RegExp(' ', 'g');
+ var re2 = new RegExp('\\(', 'g');
+ var re3 = new RegExp('\\)', 'g');
+ var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
+ $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)');
}
-
- var re1 = new RegExp(' ', 'g');
- var re2 = new RegExp('\\(', 'g');
- var re3 = new RegExp('\\)', 'g');
- var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29');
- $(imgDiv).css('background-image', 'url('+url+'_thumb.jpg)');
- }
- }}(fileInfo.path, filename));
+ };
+ }(fileInfo.path, filename));
}
}
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
// keep track of when this component is mounted so that we can asynchronously change state without worrying about whether or not we're mounted
this.canSetState = false;
- },
- shouldComponentUpdate: function(nextProps, nextState) {
+ }
+ shouldComponentUpdate(nextProps, nextState) {
if (!utils.areStatesEqual(nextProps, this.props)) {
return true;
}
// the only time this object should update is when it receives an updated file size which we can usually handle without re-rendering
- if (nextState.fileSize != this.state.fileSize) {
+ if (nextState.fileSize !== this.state.fileSize) {
if (this.refs.fileSize) {
// update the UI element to display the file size without re-rendering the whole component
- this.refs.fileSize.getDOMNode().innerHTML = utils.fileSizeToString(nextState.fileSize);
+ React.findDOMNode(this.refs.fileSize).innerHTML = utils.fileSizeToString(nextState.fileSize);
return false;
- } else {
- // we can't find the element that should hold the file size so we must not have rendered yet
- return true;
}
- } else {
+
+ // we can't find the element that should hold the file size so we must not have rendered yet
return true;
}
- },
- render: function() {
+
+ return true;
+ }
+ render() {
var filename = this.props.filename;
var fileInfo = utils.splitFileLocation(filename);
var type = utils.getFileType(fileInfo.ext);
var thumbnail;
- if (type === "image") {
- thumbnail = <div ref={filename} className="post__load" style={{backgroundImage: 'url(/static/images/load.gif)'}}/>;
+ if (type === 'image') {
+ thumbnail = (
+ <div
+ ref={filename}
+ className='post__load'
+ style={{backgroundImage: 'url(/static/images/load.gif)'}}
+ />
+ );
} else {
- thumbnail = <div className={"file-icon "+utils.getIconClassName(type)}/>;
+ thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>;
}
- var fileSizeString = "";
+ var fileSizeString = '';
if (this.state.fileSize < 0) {
- var self = this;
-
Client.getFileInfo(
filename,
- function(data) {
- if (self.canSetState) {
- self.setState({fileSize: parseInt(data["size"], 10)});
+ function success(data) {
+ if (this.canSetState) {
+ this.setState({fileSize: parseInt(data.size, 10)});
}
- },
- function(err) {
- }
+ }.bind(this),
+ function error() {}
);
} else {
fileSizeString = utils.fileSizeToString(this.state.fileSize);
@@ -132,25 +130,54 @@ module.exports = React.createClass({
var filenameString = decodeURIComponent(utils.getFileName(filename));
var trimmedFilename;
if (filenameString.length > 35) {
- trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
+ trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + '...';
} else {
trimmedFilename = filenameString;
}
return (
- <div className="post-image__column" key={filename}>
- <a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
- data-img-id={this.props.index} data-toggle="modal" data-target={"#" + this.props.modalId }>
+ <div
+ className='post-image__column'
+ key={filename}
+ >
+ <a className='post-image__thumbnail'
+ href='#'
+ onClick={this.props.handleImageClick}
+ data-img-id={this.props.index}
+ data-toggle='modal'
+ data-target={'#' + this.props.modalId}
+ >
{thumbnail}
</a>
- <div className="post-image__details">
- <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
+ <div className='post-image__details'>
+ <div
+ data-toggle='tooltip'
+ title={filenameString}
+ className='post-image__name'
+ >
+ {trimmedFilename}
+ </div>
<div>
- <span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
- <span className="post-image__size">{fileSizeString}</span>
+ <span className='post-image__type'>{fileInfo.ext.toUpperCase()}</span>
+ <span className='post-image__size'>{fileSizeString}</span>
</div>
</div>
</div>
);
}
-});
+}
+
+FileAttachment.propTypes = {
+
+ // a list of file pathes displayed by the parent FileAttachmentList
+ filename: React.PropTypes.string.isRequired,
+
+ // the index of this attachment preview in the parent FileAttachmentList
+ index: React.PropTypes.number.isRequired,
+
+ // the identifier of the modal dialog used to preview files
+ modalId: React.PropTypes.string.isRequired,
+
+ // handler for when the thumbnail is clicked
+ handleImageClick: React.PropTypes.func
+};
diff --git a/web/react/components/file_attachment_list.jsx b/web/react/components/file_attachment_list.jsx
index df4424d03..abe72089a 100644
--- a/web/react/components/file_attachment_list.jsx
+++ b/web/react/components/file_attachment_list.jsx
@@ -5,33 +5,37 @@ var ViewImageModal = require('./view_image.jsx');
var FileAttachment = require('./file_attachment.jsx');
var Constants = require('../utils/constants.jsx');
-module.exports = React.createClass({
- displayName: "FileAttachmentList",
- propTypes: {
- // a list of file pathes displayed by this
- filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
- // the identifier of the modal dialog used to preview files
- modalId: React.PropTypes.string.isRequired,
- // the channel that this is part of
- channelId: React.PropTypes.string,
- // the user that owns the post that this is attached to
- userId: React.PropTypes.string
- },
- getInitialState: function() {
- return {startImgId: 0};
- },
- render: function() {
+export default class FileAttachmentList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleImageClick = this.handleImageClick.bind(this);
+
+ this.state = {startImgId: 0};
+ }
+ handleImageClick(e) {
+ this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'), 10)});
+ }
+ render() {
var filenames = this.props.filenames;
var modalId = this.props.modalId;
var postFiles = [];
for (var i = 0; i < filenames.length && i < Constants.MAX_DISPLAY_FILES; i++) {
- postFiles.push(<FileAttachment key={i} filename={filenames[i]} index={i} modalId={modalId} handleImageClick={this.handleImageClick} />);
+ postFiles.push(
+ <FileAttachment
+ key={i}
+ filename={filenames[i]}
+ index={i}
+ modalId={modalId}
+ handleImageClick={this.handleImageClick}
+ />
+ );
}
return (
<div>
- <div className="post-image__columns">
+ <div className='post-image__columns'>
{postFiles}
</div>
<ViewImageModal
@@ -39,11 +43,24 @@ module.exports = React.createClass({
userId={this.props.userId}
modalId={modalId}
startId={this.state.startImgId}
- filenames={filenames} />
+ filenames={filenames}
+ />
</div>
);
- },
- handleImageClick: function(e) {
- this.setState({startImgId: parseInt($(e.target.parentNode).attr('data-img-id'))});
}
-});
+}
+
+FileAttachmentList.propTypes = {
+
+ // a list of file pathes displayed by this
+ filenames: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
+
+ // the identifier of the modal dialog used to preview files
+ modalId: React.PropTypes.string.isRequired,
+
+ // the channel that this is part of
+ channelId: React.PropTypes.string,
+
+ // the user that owns the post that this is attached to
+ userId: React.PropTypes.string
+};
diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx
index d1b2f734a..33382a439 100644
--- a/web/react/components/file_preview.jsx
+++ b/web/react/components/file_preview.jsx
@@ -1,14 +1,17 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- handleRemove: function(e) {
+export default class FilePreview extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleRemove = this.handleRemove.bind(this);
+
+ this.state = {};
+ }
+ handleRemove(e) {
var previewDiv = e.target.parentNode.parentNode;
if (previewDiv.hasAttribute('data-filename')) {
@@ -16,51 +19,96 @@ module.exports = React.createClass({
} else if (previewDiv.hasAttribute('data-client-id')) {
this.props.onRemove(previewDiv.getAttribute('data-client-id'));
}
- },
- render: function() {
+ }
+ render() {
var previews = [];
- this.props.files.forEach(function(filename) {
-
+ this.props.files.forEach(function setupPreview(fullFilename) {
+ var filename = fullFilename;
var originalFilename = filename;
var filenameSplit = filename.split('.');
- var ext = filenameSplit[filenameSplit.length-1];
- var type = utils.getFileType(ext);
+ var ext = filenameSplit[filenameSplit.length - 1];
+ var type = Utils.getFileType(ext);
+
// This is a temporary patch to fix issue with old files using absolute paths
- if (filename.indexOf("/api/v1/files/get") != -1) {
- filename = filename.split("/api/v1/files/get")[1];
+
+ if (filename.indexOf('/api/v1/files/get') !== -1) {
+ filename = filename.split('/api/v1/files/get')[1];
}
- filename = utils.getWindowLocationOrigin() + "/api/v1/files/get" + filename;
+ filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename;
- if (type === "image") {
+ if (type === 'image') {
previews.push(
- <div key={filename} className="preview-div" data-filename={originalFilename}>
- <img className="preview-img" src={filename}/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={filename}
+ className='preview-div'
+ data-filename={originalFilename}
+ >
+ <img
+ className='preview-img'
+ src={filename}
+ />
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
} else {
previews.push(
- <div key={filename} className="preview-div custom-file" data-filename={originalFilename}>
- <div className={"file-icon "+utils.getIconClassName(type)}/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={filename}
+ className='preview-div custom-file'
+ data-filename={originalFilename}
+ >
+ <div className={'file-icon ' + Utils.getIconClassName(type)}/>
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
}
}.bind(this));
- this.props.uploadsInProgress.forEach(function(clientId) {
+ this.props.uploadsInProgress.forEach(function addUploadsInProgress(clientId) {
previews.push(
- <div className="preview-div" data-client-id={clientId}>
- <img className="spinner" src="/static/images/load.gif"/>
- <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a>
+ <div
+ key={clientId}
+ className='preview-div'
+ data-client-id={clientId}
+ >
+ <img
+ className='spinner'
+ src='/static/images/load.gif'
+ />
+ <a
+ className='remove-preview'
+ onClick={this.handleRemove}
+ >
+ <i className='glyphicon glyphicon-remove'/>
+ </a>
</div>
);
}.bind(this));
return (
- <div className="preview-container">
+ <div className='preview-container'>
{previews}
</div>
);
}
-});
+}
+
+FilePreview.defaultProps = {
+ files: null,
+ uploadsInProgress: null
+};
+FilePreview.propTypes = {
+ onRemove: React.PropTypes.func.isRequired,
+ files: React.PropTypes.array,
+ uploadsInProgress: React.PropTypes.array
+};
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index e77982559..534f0136e 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -6,71 +6,64 @@ var Constants = require('../utils/constants.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- displayName: 'FileUpload',
- propTypes: {
- onUploadError: React.PropTypes.func,
- getFileCount: React.PropTypes.func,
- onFileUpload: React.PropTypes.func,
- onUploadStart: React.PropTypes.func,
- channelId: React.PropTypes.string,
- postType: React.PropTypes.string
- },
- getInitialState: function() {
- return {requests: {}};
- },
- handleChange: function() {
- var element = $(this.refs.fileInput.getDOMNode());
- var files = element.prop('files');
+export default class FileUpload extends React.Component {
+ constructor(props) {
+ super(props);
- var channelId = this.props.channelId || ChannelStore.getCurrentId();
+ this.uploadFiles = this.uploadFiles.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleDrop = this.handleDrop.bind(this);
- this.props.onUploadError(null);
+ this.state = {
+ requests: {}
+ };
+ }
- // This looks redundant, but must be done this way due to
- // setState being an asynchronous call
- var numFiles = 0;
- for (var i = 0; i < files.length; i++) {
- if (files[i].size <= Constants.MAX_FILE_SIZE) {
- numFiles++;
- }
+ fileUploadSuccess(channelId, data) {
+ var parsedData = $.parseJSON(data);
+ this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
+
+ var requests = this.state.requests;
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
}
+ this.setState({requests: requests});
+ }
- var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles);
+ fileUploadFail(clientId, err) {
+ this.props.onUploadError(err, clientId);
+ }
- if (numFiles > numToUpload) {
- this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
- }
+ uploadFiles(files) {
+ // clear any existing errors
+ this.props.onUploadError(null);
+
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
+
+ var uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
+ var numUploads = 0;
+
+ // keep track of how many files have been too large
+ var tooLargeFiles = [];
- for (var i = 0; i < files.length && i < numToUpload; i++) {
+ for (let i = 0; i < files.length && numUploads < uploadsRemaining; i++) {
if (files[i].size > Constants.MAX_FILE_SIZE) {
- this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB');
+ tooLargeFiles.push(files[i]);
continue;
}
- // generate a unique id that can be used by other components to refer back to this file upload
+ // generate a unique id that can be used by other components to refer back to this upload
var clientId = utils.generateId();
- // Prepare data to be uploaded.
+ // prepare data to be uploaded
var formData = new FormData();
formData.append('channel_id', channelId);
formData.append('files', files[i], files[i].name);
formData.append('client_ids', clientId);
var request = client.uploadFile(formData,
- function(data) {
- var parsedData = $.parseJSON(data);
- this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
-
- var requests = this.state.requests;
- for (var j = 0; j < parsedData.client_ids.length; j++) {
- delete requests[parsedData.client_ids[j]];
- }
- this.setState({requests: requests});
- }.bind(this),
- function(err) {
- this.props.onUploadError(err, clientId);
- }.bind(this)
+ this.fileUploadSuccess.bind(this, channelId),
+ this.fileUploadFail.bind(this, clientId)
);
var requests = this.state.requests;
@@ -78,8 +71,26 @@ module.exports = React.createClass({
this.setState({requests: requests});
this.props.onUploadStart([clientId], channelId);
+
+ numUploads += 1;
}
+ if (files.length > uploadsRemaining) {
+ this.props.onUploadError(`Uploads limited to ${Constants.MAX_UPLOAD_FILES} files maximum. Please use additional posts for more files.`);
+ } else if (tooLargeFiles.length > 1) {
+ var tooLargeFilenames = tooLargeFiles.map((file) => file.name).join(', ');
+
+ this.props.onUploadError(`Files above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFilenames}`);
+ } else if (tooLargeFiles.length > 0) {
+ this.props.onUploadError(`File above ${Constants.MAX_FILE_SIZE / 1000000}MB could not be uploaded: ${tooLargeFiles[0].name}`);
+ }
+ }
+
+ handleChange() {
+ var element = $(React.findDOMNode(this.refs.fileInput));
+
+ this.uploadFiles(element.prop('files'));
+
// clear file input for all modern browsers
try {
element[0].value = '';
@@ -87,97 +98,56 @@ module.exports = React.createClass({
element[0].type = 'text';
element[0].type = 'file';
}
- } catch(e) {}
- },
- handleDrop: function(e) {
+ } catch(e) {
+ // Do nothing
+ }
+ }
+
+ handleDrop(e) {
this.props.onUploadError(null);
var files = e.originalEvent.dataTransfer.files;
- var channelId = this.props.channelId || ChannelStore.getCurrentId();
if (typeof files !== 'string' && files.length) {
- var numFiles = files.length;
-
- var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles);
-
- if (numFiles > numToUpload) {
- this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
- }
-
- for (var i = 0; i < files.length && i < numToUpload; i++) {
- if (files[i].size > Constants.MAX_FILE_SIZE) {
- this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB');
- continue;
- }
-
- // generate a unique id that can be used by other components to refer back to this file upload
- var clientId = utils.generateId();
-
- // Prepare data to be uploaded.
- var formData = new FormData();
- formData.append('channel_id', channelId);
- formData.append('files', files[i], files[i].name);
- formData.append('client_ids', clientId);
-
- var request = client.uploadFile(formData,
- function(data) {
- var parsedData = $.parseJSON(data);
- this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
-
- var requests = this.state.requests;
- for (var j = 0; j < parsedData.client_ids.length; j++) {
- delete requests[parsedData.client_ids[j]];
- }
- this.setState({requests: requests});
- }.bind(this),
- function(err) {
- this.props.onUploadError(err, clientId);
- }.bind(this)
- );
-
- var requests = this.state.requests;
- requests[clientId] = request;
- this.setState({requests: requests});
-
- this.props.onUploadStart([clientId], channelId);
- }
+ this.uploadFiles(files);
} else {
this.props.onUploadError('Invalid file upload', -1);
}
- },
- componentDidMount: function() {
- var inputDiv = this.refs.input.getDOMNode();
+ }
+
+ componentDidMount() {
+ var inputDiv = React.findDOMNode(this.refs.input);
var self = this;
if (this.props.postType === 'post') {
$('.row.main').dragster({
- enter: function() {
+ enter() {
$('.center-file-overlay').removeClass('hidden');
},
- leave: function() {
+ leave() {
$('.center-file-overlay').addClass('hidden');
},
- drop: function(dragsterEvent, e) {
+ drop(dragsterEvent, e) {
$('.center-file-overlay').addClass('hidden');
self.handleDrop(e);
}
});
} else if (this.props.postType === 'comment') {
$('.post-right__container').dragster({
- enter: function() {
+ enter() {
$('.right-file-overlay').removeClass('hidden');
},
- leave: function() {
+ leave() {
$('.right-file-overlay').addClass('hidden');
},
- drop: function(dragsterEvent, e) {
+ drop(dragsterEvent, e) {
$('.right-file-overlay').addClass('hidden');
self.handleDrop(e);
}
});
}
- document.addEventListener('paste', function(e) {
+ document.addEventListener('paste', function handlePaste(e) {
var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0];
if (textarea !== e.target && !$.contains(textarea, e.target)) {
@@ -191,7 +161,7 @@ module.exports = React.createClass({
var items = e.clipboardData.items;
var numItems = 0;
if (items) {
- for (var i = 0; i < items.length; i++) {
+ for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
var testExt = items[i].type.split('/')[1].toLowerCase();
@@ -245,19 +215,8 @@ module.exports = React.createClass({
formData.append('client_ids', clientId);
var request = client.uploadFile(formData,
- function(data) {
- var parsedData = $.parseJSON(data);
- self.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
-
- var requests = self.state.requests;
- for (var j = 0; j < parsedData.client_ids.length; j++) {
- delete requests[parsedData.client_ids[j]];
- }
- self.setState({requests: requests});
- },
- function(err) {
- self.props.onUploadError(err, clientId);
- }
+ self.fileUploadSuccess.bind(self, channelId),
+ self.fileUploadFail.bind(self, clientId)
);
var requests = self.state.requests;
@@ -269,8 +228,9 @@ module.exports = React.createClass({
}
}
});
- },
- cancelUpload: function(clientId) {
+ }
+
+ cancelUpload(clientId) {
var requests = this.state.requests;
var request = requests[clientId];
@@ -280,15 +240,33 @@ module.exports = React.createClass({
delete requests[clientId];
this.setState({requests: requests});
}
- },
- render: function() {
+ }
+
+ render() {
return (
- <span ref='input' className='btn btn-file'>
+ <span
+ ref='input'
+ className='btn btn-file'
+ >
<span>
<i className='glyphicon glyphicon-paperclip' />
</span>
- <input ref='fileInput' type='file' onChange={this.handleChange} multiple/>
+ <input
+ ref='fileInput'
+ type='file'
+ onChange={this.handleChange}
+ multiple='true'
+ />
</span>
);
}
-});
+}
+
+FileUpload.propTypes = {
+ onUploadError: React.PropTypes.func,
+ getFileCount: React.PropTypes.func,
+ onFileUpload: React.PropTypes.func,
+ onUploadStart: React.PropTypes.func,
+ channelId: React.PropTypes.string,
+ postType: React.PropTypes.string
+};
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
index f35556371..265924206 100644
--- a/web/react/components/file_upload_overlay.jsx
+++ b/web/react/components/file_upload_overlay.jsx
@@ -1,12 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: 'FileUploadOverlay',
- propTypes: {
- overlayType: React.PropTypes.string
- },
- render: function() {
+export default class FileUploadOverlay extends React.Component {
+ render() {
var overlayClass = 'file-overlay hidden';
if (this.props.overlayType === 'right') {
overlayClass += ' right-file-overlay';
@@ -23,4 +19,8 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+FileUploadOverlay.propTypes = {
+ overlayType: React.PropTypes.string
+};
diff --git a/web/react/components/find_team.jsx b/web/react/components/find_team.jsx
index d896a1f12..52988886c 100644
--- a/web/react/components/find_team.jsx
+++ b/web/react/components/find_team.jsx
@@ -1,53 +1,58 @@
// 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');
+import {strings} from '../utils/config.js';
+
+export default class FindTeam extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
-module.exports = React.createClass({
- handleSubmit: function(e) {
+ handleSubmit(e) {
e.preventDefault();
var state = { };
- var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
+ var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email || !utils.isEmail(email)) {
- state.email_error = "Please enter a valid email address";
+ state.email_error = 'Please enter a valid email address';
this.setState(state);
return;
}
- else {
- state.email_error = "";
- }
+
+ state.email_error = '';
client.findTeamsSendEmail(email,
- function(data) {
+ function success() {
state.sent = true;
this.setState(state);
}.bind(this),
- function(err) {
+ function fail(err) {
state.email_error = err.message;
this.setState(state);
}.bind(this)
);
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
+ }
- var email_error = this.state.email_error ? <label className='control-label'>{ this.state.email_error }</label> : null;
+ render() {
+ var emailError = null;
+ var emailErrorClass = 'form-group';
- var divStyle = {
- "marginTop": "50px",
+ if (this.state.email_error) {
+ emailError = <label className='control-label'>{this.state.email_error}</label>;
+ emailErrorClass = 'form-group has-error';
}
if (this.state.sent) {
return (
<div>
- <h4>{"Find Your " + utils.toTitleCase(strings.Team)}</h4>
- <p>{"An email was sent with links to any " + strings.TeamPlural + " to which you are a member."}</p>
+ <h4>{'Find Your ' + utils.toTitleCase(strings.Team)}</h4>
+ <p>{'An email was sent with links to any ' + strings.TeamPlural + ' to which you are a member.'}</p>
</div>
);
}
@@ -56,17 +61,28 @@ module.exports = React.createClass({
<div>
<h4>Find Your Team</h4>
<form onSubmit={this.handleSubmit}>
- <p>{"Get an email with links to any " + strings.TeamPlural + " to which you are a member."}</p>
- <div className="form-group">
+ <p>{'Get an email with links to any ' + strings.TeamPlural + ' to which you are a member.'}</p>
+ <div className='form-group'>
<label className='control-label'>Email</label>
- <div className={ email_error ? "form-group has-error" : "form-group" }>
- <input type="text" ref="email" className="form-control" placeholder="you@domain.com" maxLength="128" />
- { email_error }
+ <div className={emailErrorClass}>
+ <input
+ type='text'
+ ref='email'
+ className='form-control'
+ placeholder='you@domain.com'
+ maxLength='128'
+ />
+ {emailError}
</div>
</div>
- <button className="btn btn-md btn-primary" type="submit">Send</button>
+ <button
+ className='btn btn-md btn-primary'
+ type='submit'
+ >
+ Send
+ </button>
</form>
</div>
);
}
-});
+}
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index 3b10926f5..1f25ea0b7 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -2,69 +2,115 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
-var ZeroClipboardMixin = require('react-zeroclipboard-mixin');
+import {strings} from '../utils/config.js';
-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/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 5b6924891..c1cfa7800 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -6,11 +6,30 @@ var ConfigStore = require('../stores/config_store.jsx');
var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
+import {config} from '../utils/config.js';
-module.exports = React.createClass({
- componentDidMount: function() {
+export default class InviteMemberModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.addInviteFields = this.addInviteFields.bind(this);
+ this.clearFields = this.clearFields.bind(this);
+ this.removeInviteFields = this.removeInviteFields.bind(this);
+
+ this.state = {
+ inviteIds: [0],
+ idCount: 0,
+ emailErrors: {},
+ firstNameErrors: {},
+ lastNameErrors: {},
+ emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
+ };
+ }
+
+ componentDidMount() {
var self = this;
- $('#invite_member').on('hide.bs.modal', function(e) {
+ $('#invite_member').on('hide.bs.modal', function hide(e) {
if ($('#invite_member').attr('data-confirm') === 'true') {
$('#invite_member').attr('data-confirm', 'false');
return;
@@ -19,7 +38,7 @@ module.exports = React.createClass({
var notEmpty = false;
for (var i = 0; i < self.state.inviteIds.length; i++) {
var index = self.state.inviteIds[i];
- if (self.refs['email' + index].getDOMNode().value.trim() !== '') {
+ if (React.findDOMNode(self.refs['email' + index]).value.trim() !== '') {
notEmpty = true;
break;
}
@@ -31,11 +50,12 @@ module.exports = React.createClass({
}
});
- $('#invite_member').on('hidden.bs.modal', function() {
+ $('#invite_member').on('hidden.bs.modal', function show() {
self.clearFields();
});
- },
- handleSubmit: function(e) {
+ }
+
+ handleSubmit() {
if (!this.state.emailEnabled) {
return;
}
@@ -51,7 +71,7 @@ module.exports = React.createClass({
for (var i = 0; i < count; i++) {
var index = inviteIds[i];
var invite = {};
- invite.email = this.refs['email' + index].getDOMNode().value.trim();
+ invite.email = React.findDOMNode(this.refs['email' + index]).value.trim();
if (!invite.email || !utils.isEmail(invite.email)) {
emailErrors[index] = 'Please enter a valid email address';
valid = false;
@@ -60,7 +80,7 @@ module.exports = React.createClass({
}
if (config.AllowInviteNames) {
- invite.firstName = this.refs['first_name' + index].getDOMNode().value.trim();
+ invite.firstName = React.findDOMNode(this.refs['first_name' + index]).value.trim();
if (!invite.firstName && config.RequireInviteNames) {
firstNameErrors[index] = 'This is a required field';
valid = false;
@@ -68,7 +88,7 @@ module.exports = React.createClass({
firstNameErrors[index] = '';
}
- invite.lastName = this.refs['last_name' + index].getDOMNode().value.trim();
+ invite.lastName = React.findDOMNode(this.refs['last_name' + index]).value.trim();
if (!invite.lastName && config.RequireInviteNames) {
lastNameErrors[index] = 'This is a required field';
valid = false;
@@ -90,11 +110,11 @@ module.exports = React.createClass({
data.invites = invites;
Client.inviteMembers(data,
- function() {
- $(this.refs.modal.getDOMNode()).attr('data-confirm', 'true');
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function success() {
+ $(React.findDOMNode(this.refs.modal)).attr('data-confirm', 'true');
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
}.bind(this),
- function(err) {
+ function fail(err) {
if (err.message === 'This person is already on your team') {
emailErrors[err.detailed_error] = err.message;
this.setState({emailErrors: emailErrors});
@@ -103,26 +123,29 @@ module.exports = React.createClass({
}
}.bind(this)
);
- },
- componentDidUpdate: function() {
- $(this.refs.modalBody.getDOMNode()).css('max-height', $(window).height() - 200);
- $(this.refs.modalBody.getDOMNode()).css('overflow-y', 'scroll');
- },
- addInviteFields: function() {
+ }
+
+ componentDidUpdate() {
+ $(React.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 200);
+ $(React.findDOMNode(this.refs.modalBody)).css('overflow-y', 'scroll');
+ }
+
+ addInviteFields() {
var count = this.state.idCount + 1;
var inviteIds = this.state.inviteIds;
inviteIds.push(count);
this.setState({inviteIds: inviteIds, idCount: count});
- },
- clearFields: function() {
+ }
+
+ clearFields() {
var inviteIds = this.state.inviteIds;
for (var i = 0; i < inviteIds.length; i++) {
var index = inviteIds[i];
- this.refs['email' + index].getDOMNode().value = '';
+ React.findDOMNode(this.refs['email' + index]).value = '';
if (config.AllowInviteNames) {
- this.refs['first_name' + index].getDOMNode().value = '';
- this.refs['last_name' + index].getDOMNode().value = '';
+ React.findDOMNode(this.refs['first_name' + index]).value = '';
+ React.findDOMNode(this.refs['last_name' + index]).value = '';
}
}
@@ -133,8 +156,9 @@ module.exports = React.createClass({
firstNameErrors: {},
lastNameErrors: {}
});
- },
- removeInviteFields: function(index) {
+ }
+
+ removeInviteFields(index) {
var count = this.state.idCount;
var inviteIds = this.state.inviteIds;
var i = inviteIds.indexOf(index);
@@ -145,24 +169,10 @@ module.exports = React.createClass({
inviteIds.push(++count);
}
this.setState({inviteIds: inviteIds, idCount: count});
- },
- getInitialState: function() {
- return {
- inviteIds: [0],
- idCount: 0,
- emailErrors: {},
- firstNameErrors: {},
- lastNameErrors: {},
- emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
- };
- },
- render: function() {
- var currentUser = UserStore.getCurrentUser();
+ }
- var inputDisabled = '';
- if (!this.state.emailEnabled) {
- inputDisabled = 'disabled';
- }
+ render() {
+ var currentUser = UserStore.getCurrentUser();
if (currentUser != null) {
var inviteSections = [];
@@ -185,7 +195,13 @@ module.exports = React.createClass({
var removeButton = null;
if (index) {
removeButton = (<div>
- <button type='button' className='btn btn-link remove__member' onClick={this.removeInviteFields.bind(this, index)}><span className='fa fa-trash'></span></button>
+ <button
+ type='button'
+ className='btn btn-link remove__member'
+ onClick={this.removeInviteFields.bind(this, index)}
+ >
+ <span className='fa fa-trash'></span>
+ </button>
</div>);
}
var emailClass = 'form-group invite';
@@ -206,13 +222,27 @@ module.exports = React.createClass({
nameFields = (<div className='row--invite'>
<div className='col-sm-6'>
<div className={firstNameClass}>
- <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' disabled={!this.state.emailEnabled}/>
+ <input
+ type='text'
+ className='form-control'
+ ref={'first_name' + index}
+ placeholder='First name'
+ maxLength='64'
+ disabled={!this.state.emailEnabled}
+ />
{firstNameError}
</div>
</div>
<div className='col-sm-6'>
<div className={lastNameClass}>
- <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' disabled={!this.state.emailEnabled}/>
+ <input
+ type='text'
+ className='form-control'
+ ref={'last_name' + index}
+ placeholder='Last name'
+ maxLength='64'
+ disabled={!this.state.emailEnabled}
+ />
{lastNameError}
</div>
</div>
@@ -223,7 +253,15 @@ module.exports = React.createClass({
<div key={'key' + index}>
{removeButton}
<div className={emailClass}>
- <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' disabled={!this.state.emailEnabled}/>
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ type='text'
+ ref={'email' + index}
+ className='form-control'
+ placeholder='email@domain.com'
+ maxLength='64'
+ disabled={!this.state.emailEnabled}
+ />
{emailError}
</div>
{nameFields}
@@ -242,23 +280,44 @@ module.exports = React.createClass({
content = (
<div>
{serverError}
- <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.addInviteFields}
+ >Add another</button>
<br/>
<br/>
<span>People invited automatically join Town Square channel.</span>
</div>
);
- sendButton = <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button>
+ sendButton =
+ (
+ <button
+ onClick={this.handleSubmit}
+ type='button'
+ className='btn btn-primary'
+ >Send Invitations</button>
+ );
} else {
var teamInviteLink = null;
if (currentUser && this.props.teamType === 'O') {
var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id;
- var link = <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={linkUrl} onClick={
- function() {
- $('#invite_member').modal('hide');
- }
- }>Team Invite Link</a>;
+ var link =
+ (
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#get_link'
+ data-title='Team Invite'
+ data-value={linkUrl}
+ onClick={
+ function click() {
+ $('#invite_member').modal('hide');
+ }
+ }
+ >Team Invite Link</a>
+ );
teamInviteLink = (
<p>
@@ -277,22 +336,46 @@ module.exports = React.createClass({
return (
<div>
- <div className='modal fade' ref='modal' id='invite_member' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='invite_member'
+ 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' data-reactid='.5.0.0.0.0'><span aria-hidden='true' data-reactid='.5.0.0.0.0.0'>×</span></button>
- <h4 className='modal-title' id='myModalLabel'>Invite New Member</h4>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>×</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >Invite New Member</h4>
</div>
- <div ref='modalBody' className='modal-body'>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
<form role='form'>
{inviteSections}
</form>
{content}
</div>
<div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button>
- {sendButton}
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >Cancel</button>
+ {sendButton}
</div>
</div>
</div>
@@ -309,4 +392,8 @@ module.exports = React.createClass({
}
return <div/>;
}
-});
+}
+
+InviteMemberModal.propTypes = {
+ teamType: React.PropTypes.string
+};
diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx
index 5905e519b..b0f42ce86 100644
--- a/web/react/components/loading_screen.jsx
+++ b/web/react/components/loading_screen.jsx
@@ -1,24 +1,31 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: "LoadingScreen",
- propTypes: {
- position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
- },
- getDefaultProps: function() {
- return { position: 'relative' };
- },
- render: function() {
+export default class LoadingScreen extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
return (
- <div className="loading-screen" style={{position: this.props.position}}>
- <div className="loading__content">
+ <div
+ className='loading-screen'
+ style={{position: this.props.position}}
+ >
+ <div className='loading__content'>
<h3>Loading</h3>
- <div className="round round-1"></div>
- <div className="round round-2"></div>
- <div className="round round-3"></div>
+ <div className='round round-1'></div>
+ <div className='round round-2'></div>
+ <div className='round round-3'></div>
</div>
</div>
);
}
-});
+}
+
+LoadingScreen.defaultProps = {
+ position: 'relative'
+};
+LoadingScreen.propTypes = {
+ position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 28dd64c39..b20c62833 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -1,11 +1,12 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx');
-var Constants = require('../utils/constants.jsx');
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const BrowserStore = require('../stores/browser_store.jsx');
+const Constants = require('../utils/constants.jsx');
+import {config, strings} from '../utils/config.js';
export default class Login extends React.Component {
constructor(props) {
@@ -17,23 +18,23 @@ export default class Login extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
- var state = {};
+ let state = {};
- var name = this.props.teamName;
+ const name = this.props.teamName;
if (!name) {
state.serverError = 'Bad team name';
this.setState(state);
return;
}
- var email = this.refs.email.getDOMNode().value.trim();
+ const email = React.findDOMNode(this.refs.email).value.trim();
if (!email) {
state.serverError = 'An email is required';
this.setState(state);
return;
}
- var password = this.refs.password.getDOMNode().value.trim();
+ const password = React.findDOMNode(this.refs.password).value.trim();
if (!password) {
state.serverError = 'A password is required';
this.setState(state);
@@ -49,12 +50,12 @@ export default class Login extends React.Component {
state.serverError = '';
this.setState(state);
- client.loginByEmail(name, email, password,
+ Client.loginByEmail(name, email, password,
function loggedIn(data) {
UserStore.setCurrentUser(data);
UserStore.setLastEmail(email);
- var redirect = utils.getUrlParameter('redirect');
+ const redirect = Utils.getUrlParameter('redirect');
if (redirect) {
window.location.href = decodeURIComponent(redirect);
} else {
@@ -73,31 +74,31 @@ export default class Login extends React.Component {
);
}
render() {
- var serverError;
+ let serverError;
if (this.state.serverError) {
serverError = <label className='control-label'>{this.state.serverError}</label>;
}
- var priorEmail = UserStore.getLastEmail();
+ let priorEmail = UserStore.getLastEmail();
- var emailParam = utils.getUrlParameter('email');
+ const emailParam = Utils.getUrlParameter('email');
if (emailParam) {
priorEmail = decodeURIComponent(emailParam);
}
- var teamDisplayName = this.props.teamDisplayName;
- var teamName = this.props.teamName;
+ const teamDisplayName = this.props.teamDisplayName;
+ const teamName = this.props.teamName;
- var focusEmail = false;
- var focusPassword = false;
+ let focusEmail = false;
+ let focusPassword = false;
if (priorEmail !== '') {
focusPassword = true;
} else {
focusEmail = true;
}
- var authServices = JSON.parse(this.props.authServices);
+ const authServices = JSON.parse(this.props.authServices);
- var loginMessage = [];
+ let loginMessage = [];
if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) {
loginMessage.push(
<a
@@ -110,12 +111,12 @@ export default class Login extends React.Component {
);
}
- var errorClass = '';
+ let errorClass = '';
if (serverError) {
errorClass = ' has-error';
}
- var emailSignup;
+ let emailSignup;
if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) {
emailSignup = (
<div>
@@ -163,7 +164,7 @@ export default class Login extends React.Component {
);
}
- var forgotPassword;
+ let forgotPassword;
if (emailSignup) {
forgotPassword = (
<div className='form-group'>
diff --git a/web/react/components/member_list.jsx b/web/react/components/member_list.jsx
index 69da5cfc3..65495b569 100644
--- a/web/react/components/member_list.jsx
+++ b/web/react/components/member_list.jsx
@@ -3,32 +3,47 @@
var MemberListItem = require('./member_list_item.jsx');
-module.exports = React.createClass({
- render: function() {
+export default class MemberList extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ render() {
var members = [];
- if (this.props.memberList != null) {
+ if (this.props.memberList !== null) {
members = this.props.memberList;
}
- var message = "";
- if (members.length === 0)
+ var message = '';
+ if (members.length === 0) {
message = <span>No users to add.</span>;
+ }
return (
- <div className="member-list-holder">
- {members.map(function(member) {
- return <MemberListItem
- key={member.id}
- member={member}
- isAdmin={this.props.isAdmin}
- handleInvite={this.props.handleInvite}
- handleRemove={this.props.handleRemove}
- handleMakeAdmin={this.props.handleMakeAdmin}
- />;
+ <div className='member-list-holder'>
+ {members.map(function mymembers(member) {
+ return (
+ <MemberListItem
+ key={member.id}
+ member={member}
+ isAdmin={this.props.isAdmin}
+ handleInvite={this.props.handleInvite}
+ handleRemove={this.props.handleRemove}
+ handleMakeAdmin={this.props.handleMakeAdmin}
+ />
+ );
}, this)}
{message}
</div>
);
}
-});
+}
+
+MemberList.propTypes = {
+ memberList: React.PropTypes.array,
+ isAdmin: React.PropTypes.bool,
+ handleInvite: React.PropTypes.func,
+ handleRemove: React.PropTypes.func,
+ handleMakeAdmin: React.PropTypes.func
+};
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index a5279909b..9a31a2e30 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -1,64 +1,125 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- displayName: 'MemberListItem',
- handleInvite: function(e) {
+export default class MemberListItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleInvite = this.handleInvite.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+ this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
+ }
+ handleInvite(e) {
e.preventDefault();
this.props.handleInvite(this.props.member.id);
- },
- handleRemove: function(e) {
+ }
+ handleRemove(e) {
e.preventDefault();
this.props.handleRemove(this.props.member.id);
- },
- handleMakeAdmin: function(e) {
+ }
+ handleMakeAdmin(e) {
e.preventDefault();
this.props.handleMakeAdmin(this.props.member.id);
- },
- render: function() {
-
+ }
+ render() {
var member = this.props.member;
var isAdmin = this.props.isAdmin;
- var isMemberAdmin = member.roles.indexOf("admin") > -1;
+ var isMemberAdmin = member.roles.indexOf('admin') > -1;
var timestamp = UserStore.getCurrentUser().update_at;
var invite;
if (member.invited && this.props.handleInvite) {
- invite = <span className="member-role">Added</span>;
+ invite = <span className='member-role'>Added</span>;
} else if (this.props.handleInvite) {
- invite = <a onClick={this.handleInvite} className="btn btn-sm btn-primary member-invite"><i className="glyphicon glyphicon-envelope"/> Add</a>;
- } else if (isAdmin && !isMemberAdmin && (member.id != UserStore.getCurrentId())) {
+ invite = (
+ <a
+ onClick={this.handleInvite}
+ className='btn btn-sm btn-primary member-invite'
+ >
+ <i className='glyphicon glyphicon-envelope'/>
+ {' Add'}
+ </a>
+ );
+ } else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
var self = this;
+
+ let makeAdminOption = null;
+ if (this.props.handleMakeAdmin) {
+ makeAdminOption = (
+ <li role='presentation'>
+ <a
+ href=''
+ role='menuitem'
+ onClick={self.handleMakeAdmin}
+ >
+ Make Admin
+ </a>
+ </li>);
+ }
+
+ let handleRemoveOption = null;
+ if (this.props.handleRemove) {
+ handleRemoveOption = (
+ <li role='presentation'>
+ <a
+ href=''
+ role='menuitem'
+ onClick={self.handleRemove}
+ >
+ Remove Member
+ </a>
+ </li>);
+ }
+
invite = (
- <div className="dropdown member-drop">
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <span className="text-capitalize">{member.roles || 'Member'} </span>
- <span className="caret"></span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span className='text-capitalize'>{member.roles || 'Member'} </span>
+ <span className='caret'></span>
</a>
- <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
- { this.props.handleMakeAdmin ?
- <li role="presentation"><a href="" role="menuitem" onClick={self.handleMakeAdmin}>Make Admin</a></li>
- : null }
- { this.props.handleRemove ?
- <li role="presentation"><a href="" role="menuitem" onClick={self.handleRemove}>Remove Member</a></li>
- : null }
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdminOption}
+ {handleRemoveOption}
</ul>
</div>
);
} else {
- invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>;
+ invite = <div className='member-role text-capitalize'>{member.roles || 'Member'}<span className='caret hidden'></span></div>;
}
return (
- <div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" />
- <span className="member-name">{member.username}</span>
- <span className="member-email">{member.email}</span>
- { invite }
+ <div className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={'/api/v1/users/' + member.id + '/image?time=' + timestamp}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{member.username}</span>
+ <span className='member-email'>{member.email}</span>
+ {invite}
</div>
);
}
-});
+}
+
+MemberListItem.propTypes = {
+ handleInvite: React.PropTypes.func,
+ handleRemove: React.PropTypes.func,
+ handleMakeAdmin: React.PropTypes.func,
+ member: React.PropTypes.object,
+ isAdmin: React.PropTypes.bool
+};
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index cb48e5cc5..064330c8d 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -1,122 +1,27 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var utils = require('../utils/utils.jsx');
-
-var MemberListTeamItem = React.createClass({
- handleMakeMember: function() {
- var data = {};
- data["user_id"] = this.props.user.id;
- data["new_roles"] = "";
-
- Client.updateRoles(data,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeActive: function() {
- Client.updateActive(this.props.user.id, true,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeNotActive: function() {
- Client.updateActive(this.props.user.id, false,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- handleMakeAdmin: function() {
- var data = {};
- data["user_id"] = this.props.user.id;
- data["new_roles"] = "admin";
-
- Client.updateRoles(data,
- function(data) {
- AsyncClient.getProfiles();
- }.bind(this),
- function(err) {
- this.setState({ server_error: err.message });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
- var user = this.props.user;
- var currentRoles = "Member";
- var timestamp = UserStore.getCurrentUser().update_at;
-
- if (user.roles.length > 0) {
- currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
- }
-
- var email = user.email.length > 0 ? user.email : "";
- var showMakeMember = user.roles == "admin";
- var showMakeAdmin = user.roles == "";
- var showMakeActive = false;
- var showMakeNotActive = true;
-
- if (user.delete_at > 0) {
- currentRoles = "Inactive";
- showMakeMember = false;
- showMakeAdmin = false;
- showMakeActive = true;
- showMakeNotActive = false;
- }
+const MemberListTeamItem = require('./member_list_team_item.jsx');
+
+export default class MemberListTeam extends React.Component {
+ render() {
+ const memberList = this.props.users.map(function makeListItem(user) {
+ return (
+ <MemberListTeamItem
+ key={user.id}
+ user={user}
+ />
+ );
+ }, this);
return (
- <div className="row member-div">
- <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" />
- <span className="member-name">{utils.getDisplayName(user)}</span>
- <span className="member-email">{email}</span>
- <div className="dropdown member-drop">
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <span>{currentRoles} </span>
- <span className="caret"></span>
- </a>
- <ul className="dropdown-menu member-menu" role="menu" aria-labelledby="channel_header_dropdown">
- { showMakeAdmin ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeAdmin}>Make Admin</a></li> : "" }
- { showMakeMember ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeMember}>Make Member</a></li> : "" }
- { showMakeActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeActive}>Make Active</a></li> : "" }
- { showMakeNotActive ? <li role="presentation"><a role="menuitem" href="#" onClick={this.handleMakeNotActive}>Make Inactive</a></li> : "" }
- </ul>
- </div>
- { server_error }
+ <div className='member-list-holder'>
+ {memberList}
</div>
);
}
-});
+}
-
-module.exports = React.createClass({
- render: function() {
- return (
- <div className="member-list-holder">
- {
- this.props.users.map(function(user) {
- return <MemberListTeamItem key={user.id} user={user} />;
- }, this)
- }
- </div>
- );
- }
-});
+MemberListTeam.propTypes = {
+ users: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+};
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
new file mode 100644
index 000000000..b7e81f843
--- /dev/null
+++ b/web/react/components/member_list_team_item.jsx
@@ -0,0 +1,203 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const UserStore = require('../stores/user_store.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const Utils = require('../utils/utils.jsx');
+
+export default class MemberListTeamItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleMakeMember = this.handleMakeMember.bind(this);
+ this.handleMakeActive = this.handleMakeActive.bind(this);
+ this.handleMakeNotActive = this.handleMakeNotActive.bind(this);
+ this.handleMakeAdmin = this.handleMakeAdmin.bind(this);
+
+ this.state = {};
+ }
+ handleMakeMember() {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: ''
+ };
+
+ Client.updateRoles(data,
+ function handleMakeMemberSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeMemberError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeActive() {
+ Client.updateActive(this.props.user.id, true,
+ function handleMakeActiveSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeActiveError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeNotActive() {
+ Client.updateActive(this.props.user.id, false,
+ function handleMakeNotActiveSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeNotActiveError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ handleMakeAdmin() {
+ const data = {
+ user_id: this.props.user.id,
+ new_roles: 'admin'
+ };
+
+ Client.updateRoles(data,
+ function handleMakeAdminSuccess() {
+ AsyncClient.getProfiles();
+ },
+ function handleMakeAdmitError(err) {
+ this.setState({serverError: err.message});
+ }.bind(this)
+ );
+ }
+ render() {
+ let serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='has-error'>
+ <label className='has-error control-label'>{this.state.serverError}</label>
+ </div>
+ );
+ }
+
+ const user = this.props.user;
+ let currentRoles = 'Member';
+ const timestamp = UserStore.getCurrentUser().update_at;
+
+ if (user.roles.length > 0) {
+ currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
+ }
+
+ const email = user.email;
+ let showMakeMember = user.roles === 'admin';
+ let showMakeAdmin = user.roles === '';
+ let showMakeActive = false;
+ let showMakeNotActive = true;
+
+ if (user.delete_at > 0) {
+ currentRoles = 'Inactive';
+ showMakeMember = false;
+ showMakeAdmin = false;
+ showMakeActive = true;
+ showMakeNotActive = false;
+ }
+
+ let makeAdmin = null;
+ if (showMakeAdmin) {
+ makeAdmin = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeAdmin}
+ >
+ Make Admin
+ </a>
+ </li>
+ );
+ }
+
+ let makeMember = null;
+ if (showMakeMember) {
+ makeMember = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeMember}
+ >
+ Make Member
+ </a>
+ </li>
+ );
+ }
+
+ let makeActive = null;
+ if (showMakeActive) {
+ makeActive = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeActive}
+ >
+ Make Active
+ </a>
+ </li>
+ );
+ }
+
+ let makeNotActive = null;
+ if (showMakeNotActive) {
+ makeNotActive = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeNotActive}
+ >
+ Make Inactive
+ </a>
+ </li>
+ );
+ }
+
+ return (
+ <div className='row member-div'>
+ <img
+ className='post-profile-img pull-left'
+ src={`/api/v1/users/${user.id}/image?time=${timestamp}`}
+ height='36'
+ width='36'
+ />
+ <span className='member-name'>{Utils.getDisplayName(user)}</span>
+ <span className='member-email'>{email}</span>
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ id='channel_header_dropdown'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{currentRoles} </span>
+ <span className='caret'></span>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ aria-labelledby='channel_header_dropdown'
+ >
+ {makeAdmin}
+ {makeMember}
+ {makeActive}
+ {makeNotActive}
+ </ul>
+ </div>
+ {serverError}
+ </div>
+ );
+ }
+}
+
+MemberListTeamItem.propTypes = {
+ user: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index 114dc183f..3a09e843d 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -1,30 +1,60 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require("../stores/user_store.jsx");
+var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- handleClick: function() {
+export default class Mention extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+
+ this.state = null;
+ }
+ handleClick() {
this.props.handleClick(this.props.username);
- },
- getInitialState: function() {
- return null;
- },
- render: function() {
- var self = this;
+ }
+ render() {
var icon;
var timestamp = UserStore.getCurrentUser().update_at;
- if (this.props.id === "allmention" || this.props.id === "channelmention") {
- icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ if (this.props.id === 'allmention' || this.props.id === 'channelmention') {
+ icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
} else if (this.props.id != null) {
- icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>;
+ icon = (
+ <span>
+ <img
+ className='mention-img'
+ src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp}
+ />
+ </span>
+ );
} else {
- icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
}
return (
- <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}>
- <div className="pull-left">{icon}</div>
- <div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div>
+ <div
+ className={'mentions-name ' + this.props.isFocused}
+ id={this.props.id + '_mentions'}
+ onClick={this.handleClick}
+ onMouseEnter={this.props.handleMouseEnter}
+ >
+ <div className='pull-left'>{icon}</div>
+ <div className='pull-left mention-align'><span>@{this.props.username}</span><span className='mention-fullname'>{this.props.secondary_text}</span></div>
</div>
);
}
-});
+}
+
+Mention.defaultProps = {
+ username: '',
+ id: '',
+ isFocused: '',
+ secondary_text: ''
+};
+Mention.propTypes = {
+ handleClick: React.PropTypes.func.isRequired,
+ handleMouseEnter: React.PropTypes.func.isRequired,
+ username: React.PropTypes.string,
+ id: React.PropTypes.string,
+ isFocused: React.PropTypes.string,
+ secondary_text: React.PropTypes.string
+};
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index f562cfb29..46a9d76ae 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -14,54 +14,66 @@ var MAX_HEIGHT_LIST = 292;
var MAX_ITEMS_IN_LIST = 25;
var ITEM_HEIGHT = 36;
-module.exports = React.createClass({
- displayName: 'MentionList',
- componentDidMount: function() {
+export default class MentionList extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.getSelection = this.getSelection.bind(this);
+ this.addCurrentMention = this.addCurrentMention.bind(this);
+ this.addFirstMention = this.addFirstMention.bind(this);
+ this.isEmpty = this.isEmpty.bind(this);
+ this.scrollToMention = this.scrollToMention.bind(this);
+
+ this.state = {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
+ }
+ componentDidMount() {
PostStore.addMentionDataChangeListener(this.onListenerChange);
- var self = this;
- $('.post-right__scroll').scroll(function(){
- if($('.mentions--top').length){
- $('#reply_mention_tab .mentions--top').css({ bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top });
+ $('.post-right__scroll').scroll(function onScroll() {
+ if ($('.mentions--top').length) {
+ $('#reply_mention_tab .mentions--top').css({bottom: $(window).height() - $('.post-right__scroll #reply_textbox').offset().top});
}
});
$('body').on('keydown.mentionlist', '#' + this.props.id,
- function(e) {
- if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
+ function onMentionListKey(e) {
+ if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 13 || e.which === 9)) {
e.stopPropagation();
e.preventDefault();
- self.addCurrentMention();
- } else if (!self.isEmpty() && self.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
+ this.addCurrentMention();
+ } else if (!this.isEmpty() && this.state.mentionText !== '-1' && (e.which === 38 || e.which === 40)) {
e.stopPropagation();
e.preventDefault();
if (e.which === 38) {
- if (self.getSelection(self.state.selectedMention - 1)) {
- self.setState({selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username});
+ if (this.getSelection(this.state.selectedMention - 1)) {
+ this.setState({selectedMention: this.state.selectedMention - 1, selectedUsername: this.refs['mention' + (this.state.selectedMention - 1)].props.username});
}
} else if (e.which === 40) {
- if (self.getSelection(self.state.selectedMention + 1)) {
- self.setState({selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username});
+ if (this.getSelection(this.state.selectedMention + 1)) {
+ this.setState({selectedMention: this.state.selectedMention + 1, selectedUsername: this.refs['mention' + (this.state.selectedMention + 1)].props.username});
}
}
- self.scrollToMention(e.which);
+ this.scrollToMention(e.which);
}
- }
+ }.bind(this)
);
- $(document).click(function(e) {
- if (!($('#' + self.props.id).is(e.target) || $('#' + self.props.id).has(e.target).length ||
- ('mentionlist' in self.refs && $(self.refs.mentionlist.getDOMNode()).has(e.target).length))) {
- self.setState({mentionText: '-1'});
+ $(document).click(function onClick(e) {
+ if (!($('#' + this.props.id).is(e.target) || $('#' + this.props.id).has(e.target).length ||
+ ('mentionlist' in this.refs && $(React.findDOMNode(this.refs.mentionlist)).has(e.target).length))) {
+ this.setState({mentionText: '-1'});
}
- });
- },
- componentWillUnmount: function() {
+ }.bind(this));
+ }
+ componentWillUnmount() {
PostStore.removeMentionDataChangeListener(this.onListenerChange);
$('body').off('keydown.mentionlist', '#' + this.props.id);
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
if (this.state.mentionText !== '-1') {
if (this.state.selectedUsername !== '' && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
var tempSelectedMention = -1;
@@ -80,8 +92,8 @@ module.exports = React.createClass({
} else if (this.state.selectedMention !== 0) {
this.setState({selectedMention: 0, selectedUsername: ''});
}
- },
- onListenerChange: function(id, mentionText) {
+ }
+ onListenerChange(id, mentionText) {
if (id !== this.props.id) {
return;
}
@@ -92,8 +104,8 @@ module.exports = React.createClass({
}
this.setState(newState);
- },
- handleClick: function(name) {
+ }
+ handleClick(name) {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_ADD_MENTION,
id: this.props.id,
@@ -101,33 +113,33 @@ module.exports = React.createClass({
});
this.setState({mentionText: '-1'});
- },
- handleMouseEnter: function(listId) {
+ }
+ handleMouseEnter(listId) {
this.setState({selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username});
- },
- getSelection: function(listId) {
+ }
+ getSelection(listId) {
if (!this.refs['mention' + listId]) {
return false;
}
return true;
- },
- addCurrentMention: function() {
+ }
+ addCurrentMention() {
if (!this.getSelection(this.state.selectedMention)) {
this.addFirstMention();
} else {
this.refs['mention' + this.state.selectedMention].handleClick();
}
- },
- addFirstMention: function() {
+ }
+ addFirstMention() {
if (!this.refs.mention0) {
return;
}
this.refs.mention0.handleClick();
- },
- isEmpty: function() {
+ }
+ isEmpty() {
return (!this.refs.mention0);
- },
- scrollToMention: function(keyPressed) {
+ }
+ scrollToMention(keyPressed) {
var direction;
if (keyPressed === 38) {
direction = 'up';
@@ -145,12 +157,8 @@ module.exports = React.createClass({
$('#mentionsbox').animate({
scrollTop: scrollAmount
}, 75);
- },
- getInitialState: function() {
- return {excludeUsers: [], mentionText: '-1', selectedMention: 0, selectedUsername: ''};
- },
- render: function() {
- var self = this;
+ }
+ render() {
var mentionText = this.state.mentionText;
if (mentionText === '-1') {
return null;
@@ -158,8 +166,10 @@ module.exports = React.createClass({
var profiles = UserStore.getActiveOnlyProfiles();
var users = [];
- for (var id in profiles) {
- users.push(profiles[id]);
+ for (let id in profiles) {
+ if (profiles[id]) {
+ users.push(profiles[id]);
+ }
}
var all = {};
@@ -176,7 +186,7 @@ module.exports = React.createClass({
channel.id = 'channelmention';
users.push(channel);
- users.sort(function(a, b) {
+ users.sort(function sortByUsername(a, b) {
if (a.username < b.username) {
return -1;
}
@@ -185,29 +195,35 @@ module.exports = React.createClass({
}
return 0;
});
- var mentions = {};
+ var mentions = [];
var index = 0;
for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText, 0) === 0) ||
(users[i].last_name && users[i].last_name.lastIndexOf(mentionText, 0) === 0) ||
users[i].username.lastIndexOf(mentionText, 0) === 0) {
+ let isFocused = '';
+ if (this.state.selectedMention === index) {
+ isFocused = 'mentions-focus';
+ }
mentions[index] = (
<Mention
+ key={'mention_key_' + index}
ref={'mention' + index}
username={users[i].username}
secondary_text={Utils.getFullName(users[i])}
id={users[i].id}
listId={index}
- isFocused={this.state.selectedMention === index ? 'mentions-focus' : ''}
- handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
- handleClick={this.handleClick} />
+ isFocused={isFocused}
+ handleMouseEnter={this.handleMouseEnter.bind(this, index)}
+ handleClick={this.handleClick}
+ />
);
index++;
}
}
- var numMentions = Object.keys(mentions).length;
+ var numMentions = mentions.length;
if (numMentions < 1) {
return null;
@@ -223,11 +239,22 @@ module.exports = React.createClass({
};
return (
- <div className='mentions--top' style={style}>
- <div ref='mentionlist' className='mentions-box' id='mentionsbox'>
+ <div
+ className='mentions--top'
+ style={style}
+ >
+ <div
+ ref='mentionlist'
+ className='mentions-box'
+ id='mentionsbox'
+ >
{mentions}
</div>
</div>
);
}
-});
+}
+
+MentionList.propTypes = {
+ id: React.PropTypes.string
+};
diff --git a/web/react/components/message_wrapper.jsx b/web/react/components/message_wrapper.jsx
index 5fc88a61b..bce305853 100644
--- a/web/react/components/message_wrapper.jsx
+++ b/web/react/components/message_wrapper.jsx
@@ -1,17 +1,30 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- render: function() {
+export default class MessageWrapper extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
if (this.props.message) {
- var inner = utils.textToJsx(this.props.message, this.props.options);
+ var inner = Utils.textToJsx(this.props.message, this.props.options);
return (
<div>{inner}</div>
);
- } else {
- return <div/>
}
+
+ return <div/>;
}
-});
+}
+
+MessageWrapper.defaultProps = {
+ message: null,
+ options: null
+};
+MessageWrapper.propTypes = {
+ message: React.PropTypes.string,
+ options: React.PropTypes.object
+};
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index 2b2c8b68d..ba8be12b2 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -29,12 +29,12 @@ export default class MoreChannels extends React.Component {
}
componentDidMount() {
ChannelStore.addMoreChangeListener(this.onListenerChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function shown() {
+ $(React.findDOMNode(this.refs.modal)).on('shown.bs.modal', function shown() {
asyncClient.getMoreChannels(true);
});
var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function show(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
@@ -52,7 +52,7 @@ export default class MoreChannels extends React.Component {
this.setState({joiningChannel: channelIndex});
client.joinChannel(channel.id,
function joinSuccess() {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(channel.id);
utils.switchChannel(channel);
this.setState({joiningChannel: -1});
@@ -65,7 +65,7 @@ export default class MoreChannels extends React.Component {
);
}
handleNewChannel() {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
}
render() {
var serverError;
@@ -86,15 +86,21 @@ export default class MoreChannels extends React.Component {
{channels.map(function cMap(channel, index) {
var joinButton;
if (self.state.joiningChannel === index) {
- joinButton = (<img
- className='join-channel-loading-gif'
- src='/static/images/load.gif'
- />);
+ joinButton = (
+ <img
+ className='join-channel-loading-gif'
+ src='/static/images/load.gif'
+ />
+ );
} else {
- joinButton = (<button
- onClick={self.handleJoin.bind(self, channel, index)}
- className='btn btn-primary'>Join
- </button>);
+ joinButton = (
+ <button
+ onClick={self.handleJoin.bind(self, channel, index)}
+ className='btn btn-primary'
+ >
+ Join
+ </button>
+ );
}
return (
@@ -152,7 +158,9 @@ export default class MoreChannels extends React.Component {
data-channeltype={this.state.channelType}
type='button'
className='btn btn-primary channel-create-btn'
- onClick={this.handleNewChannel}>Create New Channel
+ onClick={this.handleNewChannel}
+ >
+ Create New Channel
</button>
</div>
<div className='modal-body'>
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index 11ddbcbd1..f27b09ecc 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -7,19 +7,22 @@ var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- displayName: 'MoreDirectChannels',
- componentDidMount: function() {
+export default class MoreDirectChannels extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {channels: [], loadingDMChannel: -1};
+ }
+
+ componentDidMount() {
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;
self.setState({channels: $(button).data('channels')});
});
- },
- getInitialState: function() {
- return {channels: [], loadingDMChannel: -1};
- },
- render: function() {
+ }
+
+ render() {
var self = this;
var directMessageItems = this.state.channels.map(function mapActivityToChannel(channel, index) {
@@ -41,14 +44,19 @@ module.exports = React.createClass({
handleClick = function clickHandler(e) {
e.preventDefault();
utils.switchChannel(channel, channel.teammate_username);
- $(self.refs.modal.getDOMNode()).modal('hide');
+ $(React.findDOMNode(self.refs.modal)).modal('hide');
};
} else {
// 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 === index) {
- badge = <img className='channel-loading-gif pull-right' src='/static/images/load.gif'/>;
+ badge = (
+ <img
+ className='channel-loading-gif pull-right'
+ src='/static/images/load.gif'
+ />
+ );
}
if (self.state.loadingDMChannel === -1) {
@@ -58,7 +66,7 @@ module.exports = React.createClass({
Client.createDirectChannel(channel, otherUserId,
function success(data) {
- $(self.refs.modal.getDOMNode()).modal('hide');
+ $(React.findDOMNode(self.refs.modal)).modal('hide');
self.setState({loadingDMChannel: -1});
AsyncClient.getChannel(data.id);
utils.switchChannel(data);
@@ -73,16 +81,36 @@ module.exports = React.createClass({
}
return (
- <li key={channel.name} className={active}><a className={'sidebar-channel ' + titleClass} href='#' onClick={handleClick}>{badge}{channel.display_name}</a></li>
+ <li
+ key={channel.name}
+ className={active}
+ >
+ <a
+ className={'sidebar-channel ' + titleClass}
+ href='#'
+ onClick={handleClick}
+ >{badge}{channel.display_name}</a>
+ </li>
);
});
return (
- <div className='modal fade' id='more_direct_channels' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ id='more_direct_channels'
+ ref='modal'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ >
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Close</span>
</button>
@@ -94,11 +122,15 @@ module.exports = React.createClass({
</ul>
</div>
<div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >Close</button>
</div>
</div>
</div>
</div>
);
}
-});
+}
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx
index aacb315dd..d9823c3cf 100644
--- a/web/react/components/msg_typing.jsx
+++ b/web/react/components/msg_typing.jsx
@@ -1,57 +1,70 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var SocketStore = require('../stores/socket_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- timer: null,
- lastTime: 0,
- componentDidMount: function() {
- SocketStore.addChangeListener(this._onChange);
- },
- componentWillReceiveProps: function(newProps) {
- if(this.props.channelId !== newProps.channelId) {
- this.setState({text:""});
+export default class MsgTyping extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.timer = null;
+ this.lastTime = 0;
+
+ this.onChange = this.onChange.bind(this);
+
+ this.state = {
+ text: ''
+ };
+ }
+
+ componentDidMount() {
+ SocketStore.addChangeListener(this.onChange);
+ }
+
+ componentWillReceiveProps(newProps) {
+ if (this.props.channelId !== newProps.channelId) {
+ this.setState({text: ''});
}
- },
- componentWillUnmount: function() {
- SocketStore.removeChangeListener(this._onChange);
- },
- _onChange: function(msg) {
- if (msg.action == "typing" &&
- this.props.channelId == msg.channel_id &&
- this.props.parentId == msg.props.parent_id) {
+ }
+
+ componentWillUnmount() {
+ SocketStore.removeChangeListener(this.onChange);
+ }
+ onChange(msg) {
+ if (msg.action === 'typing' &&
+ this.props.channelId === msg.channel_id &&
+ this.props.parentId === msg.props.parent_id) {
this.lastTime = new Date().getTime();
- var username = "Someone";
+ var username = 'Someone';
if (UserStore.hasProfile(msg.user_id)) {
username = UserStore.getProfile(msg.user_id).username;
}
- this.setState({ text: username + " is typing..." });
+ this.setState({text: username + ' is typing...'});
if (!this.timer) {
- var outer = this;
- outer.timer = setInterval(function() {
- if ((new Date().getTime() - outer.lastTime) > 8000) {
- outer.setState({ text: "" });
- }
- }, 3000);
+ this.timer = setInterval(function myTimer() {
+ if ((new Date().getTime() - this.lastTime) > 8000) {
+ this.setState({text: ''});
+ }
+ }.bind(this), 3000);
}
+ } else if (msg.action === 'posted' && msg.channel_id === this.props.channelId) {
+ this.setState({text: ''});
}
- else if (msg.action == "posted" && msg.channel_id === this.props.channelId) {
- this.setState({text:""})
- }
- },
- getInitialState: function() {
- return { text: "" };
- },
- render: function() {
+ }
+
+ render() {
return (
- <span className="msg-typing">{ this.state.text }</span>
+ <span className='msg-typing'>{this.state.text}</span>
);
}
-});
+}
+
+MsgTyping.propTypes = {
+ channelId: React.PropTypes.string,
+ parentId: React.PropTypes.string
+};
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index 06c373e5d..2258bf2b3 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,262 @@ 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
+ key='navbar-toggle-collapse'
+ 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
+ key='navbar-toggle-sidebar'
+ 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
+ key='navbar-toggle-menu'
+ 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 +353,46 @@ module.exports = React.createClass({
}
if (channel.description.length === 0) {
- popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
+ popoverContent = React.renderToString(
+ <div>
+ No channel description yet. <br/>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-desc={channel.description}
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ data-target='#edit_channel'
+ >
+ Click here
+ </a> to add one.</div>
+ );
}
}
- var navbarCollapseButton = null;
- if (currentId == null) {
- navbarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#navbar-collapse-1'>
- <span className='sr-only'>Toggle sidebar</span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- </button>);
- }
-
- var sidebarCollapseButton = null;
- if (currentId != null) {
- sidebarCollapseButton = (<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleLeftSidebar}>
- <span className='sr-only'>Toggle sidebar</span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <span className='icon-bar'></span>
- <NotifyCounts />
- </button>);
- }
-
- var rightSidebarCollapseButton = null;
- if (currentId != null) {
- rightSidebarCollapseButton = (<button type='button' className='navbar-toggle menu-toggle pull-right' data-toggle='collapse' data-target='#sidebar-nav' onClick={this.toggleRightSidebar}>
- <span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
- </button>);
- }
-
- var channelMenuDropdown = null;
- if (channel) {
- var viewInfoOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_info' data-channelid={channel.id} href='#'>View Info</a></li>
-
- var addMembersOption = null;
- if (!isDirect && !ChannelStore.isDefault(channel)) {
- addMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_invite' href='#'>Add Members</a></li>;
- }
-
- var manageMembersOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- manageMembersOption = <li role='presentation'><a role='menuitem' data-toggle='modal' data-target='#channel_members' href='#'>Manage Members</a></li>;
- }
-
- var setChannelDescriptionOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#edit_channel' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>;
-
- var notificationPreferenceOption = null;
- if (!isDirect) {
- notificationPreferenceOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#channel_notifications' data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>;
- }
-
- var renameChannelOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- renameChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#rename_channel' data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>;
- }
-
- var deleteChannelOption = null;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- deleteChannelOption = <li role='presentation'><a role='menuitem' href='#' data-toggle='modal' data-target='#delete_channel' data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>;
- }
+ var collapseButtons = this.createCollapseButtons(currentId);
- var leaveChannelOption = null;
- if (!isDirect && !ChannelStore.isDefault(channel)) {
- leaveChannelOption = <li role='presentation'><a role='menuitem' href='#' onClick={this.handleLeave}>Leave Channel</a></li>;
- }
-
- channelMenuDropdown = (<div className='navbar-brand'>
- <div className='dropdown'>
- <div data-toggle='popover' data-content={popoverContent} className='description info-popover'></div>
- <a href='#' className='dropdown-toggle theme' type='button' id='channel_header_dropdown' data-toggle='dropdown' aria-expanded='true'>
- <span className='heading'>{channelTitle} </span>
- <span className='glyphicon glyphicon-chevron-down header-dropdown__icon'></span>
- </a>
- <ul className='dropdown-menu' role='menu' aria-labelledby='channel_header_dropdown'>
- {viewInfoOption}
- {addMembersOption}
- {manageMembersOption}
- {setChannelDescriptionOption}
- {notificationPreferenceOption}
- {renameChannelOption}
- {deleteChannelOption}
- {leaveChannelOption}
- </ul>
- </div>
- </div>);
- } else {
- channelMenuDropdown = (<div className='navbar-brand'>
- <a href='/' className='heading'>{channelTitle}</a>
- </div>);
- }
+ var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
return (
- <nav className='navbar navbar-default navbar-fixed-top' role='navigation'>
+ <nav
+ className='navbar navbar-default navbar-fixed-top'
+ role='navigation'
+ >
<div className='container-fluid theme'>
<div className='navbar-header'>
- {navbarCollapseButton}
- {sidebarCollapseButton}
- {rightSidebarCollapseButton}
+ {collapseButtons}
{channelMenuDropdown}
</div>
</div>
</nav>
);
}
-});
+}
+
+Navbar.defaultProps = {
+ teamDisplayName: ''
+};
+Navbar.propTypes = {
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
new file mode 100644
index 000000000..99cdfa1ad
--- /dev/null
+++ b/web/react/components/navbar_dropdown.jsx
@@ -0,0 +1,216 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Utils = require('../utils/utils.jsx');
+var client = require('../utils/client.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var TeamStore = require('../stores/team_store.jsx');
+
+var Constants = require('../utils/constants.jsx');
+import {config} from '../utils/config.js';
+
+function getStateFromStores() {
+ return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()};
+}
+
+export default class NavbarDropdown extends React.Component {
+ constructor(props) {
+ super(props);
+ this.blockToggle = false;
+
+ this.handleLogoutClick = this.handleLogoutClick.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = getStateFromStores();
+ }
+ handleLogoutClick(e) {
+ e.preventDefault();
+ client.logout();
+ }
+ componentDidMount() {
+ UserStore.addTeamsChangeListener(this.onListenerChange);
+ TeamStore.addChangeListener(this.onListenerChange);
+
+ $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', function resetDropdown() {
+ this.blockToggle = true;
+ setTimeout(function blockTimeout() {
+ this.blockToggle = false;
+ }.bind(this), 100);
+ }.bind(this));
+ }
+ componentWillUnmount() {
+ UserStore.removeTeamsChangeListener(this.onListenerChange);
+ TeamStore.removeChangeListener(this.onListenerChange);
+
+ $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
+ }
+ onListenerChange() {
+ var newState = getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
+ }
+ }
+ render() {
+ var teamLink = '';
+ var inviteLink = '';
+ var manageLink = '';
+ var currentUser = UserStore.getCurrentUser();
+ var isAdmin = false;
+ var teamSettings = null;
+
+ if (currentUser != null) {
+ isAdmin = currentUser.roles.indexOf('admin') > -1;
+
+ inviteLink = (
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#invite_member'
+ >
+ Invite New Member
+ </a>
+ </li>
+ );
+
+ if (this.props.teamType === 'O') {
+ teamLink = (
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#get_link'
+ data-title='Team Invite'
+ data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
+ >
+ Get Team Invite Link
+ </a>
+ </li>
+ );
+ }
+ }
+
+ if (isAdmin) {
+ manageLink = (
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_members'
+ >
+ Manage Team
+ </a>
+ </li>
+ );
+ teamSettings = (<li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_settings'
+ >
+ Team Settings
+ </a>
+ </li>);
+ }
+
+ var teams = [];
+
+ teams.push(
+ <li
+ className='divider'
+ key='div'
+ >
+ </li>
+ );
+ if (this.state.teams.length > 1 && this.state.currentTeam) {
+ var curTeamName = this.state.currentTeam.name;
+ this.state.teams.forEach(function listTeams(teamName) {
+ if (teamName !== curTeamName) {
+ teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>);
+ }
+ });
+ }
+ teams.push(<li key='newTeam_li'>
+ <a
+ key='newTeam_a'
+ target='_blank'
+ href={Utils.getWindowLocationOrigin() + '/signup_team'}
+ >
+ Create a New Team
+ </a>
+ </li>);
+
+ return (
+ <ul className='nav navbar-nav navbar-right'>
+ <li
+ ref='dropdown'
+ className='dropdown'
+ >
+ <a
+ href='#'
+ className='dropdown-toggle'
+ data-toggle='dropdown'
+ role='button'
+ aria-expanded='false'
+ >
+ <span
+ className='dropdown__icon'
+ dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}}
+ />
+ </a>
+ <ul
+ className='dropdown-menu'
+ role='menu'
+ >
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#user_settings'
+ >
+ Account Settings
+ </a>
+ </li>
+ {teamSettings}
+ {inviteLink}
+ {teamLink}
+ {manageLink}
+ <li>
+ <a
+ href='#'
+ onClick={this.handleLogoutClick}
+ >
+ Logout
+ </a>
+ </li>
+ {teams}
+ <li className='divider'></li>
+ <li>
+ <a
+ target='_blank'
+ href={config.HelpLink}
+ >
+ Help
+ </a>
+ </li>
+ <li>
+ <a
+ target='_blank'
+ href={config.ReportProblemLink}
+ >
+ Report a Problem
+ </a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ );
+ }
+}
+
+NavbarDropdown.defaultProps = {
+ teamType: ''
+};
+NavbarDropdown.propTypes = {
+ teamType: React.PropTypes.string
+};
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index fc24a7cdc..1a11fc652 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -5,17 +5,24 @@ var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-module.exports = React.createClass({
- displayName: 'NewChannelModal',
- handleSubmit: function(e) {
+export default class NewChannelModal extends React.Component {
+ constructor() {
+ super();
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+
+ this.state = {channelType: ''};
+ }
+ handleSubmit(e) {
e.preventDefault();
var channel = {};
var state = {serverError: ''};
- channel.display_name = this.refs.display_name.getDOMNode().value.trim();
+ channel.display_name = React.findDOMNode(this.refs.display_name).value.trim();
if (!channel.display_name) {
state.displayNameError = 'This field is required';
state.inValid = true;
@@ -26,7 +33,7 @@ module.exports = React.createClass({
state.displayNameError = '';
}
- channel.name = this.refs.channel_name.getDOMNode().value.trim();
+ channel.name = React.findDOMNode(this.refs.channel_name).value.trim();
if (!channel.name) {
state.nameError = 'This field is required';
state.inValid = true;
@@ -52,54 +59,51 @@ module.exports = React.createClass({
var cu = UserStore.getCurrentUser();
channel.team_id = cu.team_id;
- channel.description = this.refs.channel_desc.getDOMNode().value.trim();
+ channel.description = React.findDOMNode(this.refs.channel_desc).value.trim();
channel.type = this.state.channelType;
client.createChannel(channel,
- function(data) {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function success(data) {
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
asyncClient.getChannel(data.id);
utils.switchChannel(data);
- this.refs.display_name.getDOMNode().value = '';
- this.refs.channel_name.getDOMNode().value = '';
- this.refs.channel_desc.getDOMNode().value = '';
+ React.findDOMNode(this.refs.display_name).value = '';
+ React.findDOMNode(this.refs.channel_name).value = '';
+ React.findDOMNode(this.refs.channel_desc).value = '';
}.bind(this),
- function(err) {
+ function error(err) {
state.serverError = err.message;
state.inValid = true;
this.setState(state);
}.bind(this)
);
- },
- displayNameKeyUp: function() {
- var displayName = this.refs.display_name.getDOMNode().value.trim();
+ }
+ displayNameKeyUp() {
+ var displayName = React.findDOMNode(this.refs.display_name).value.trim();
var channelName = utils.cleanUpUrlable(displayName);
- this.refs.channel_name.getDOMNode().value = channelName;
- },
- componentDidMount: function() {
+ React.findDOMNode(this.refs.channel_name).value = channelName;
+ }
+ componentDidMount() {
var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) {
var button = e.relatedTarget;
self.setState({channelType: $(button).attr('data-channeltype')});
});
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose);
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function clearForms() {
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ handleClose() {
+ $(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false});
- },
- getInitialState: function() {
- return {channelType: ''};
- },
- render: function() {
+ }
+ render() {
var displayNameError = null;
var nameError = null;
var serverError = null;
@@ -124,11 +128,22 @@ module.exports = React.createClass({
}
return (
- <div className='modal fade' id='new_channel' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div
+ className='modal fade'
+ id='new_channel'
+ ref='modal'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ >
<span aria-hidden='true'>&times;</span>
<span className='sr-only'>Cancel</span>
</button>
@@ -138,23 +153,54 @@ module.exports = React.createClass({
<div className='modal-body'>
<div className={displayNameClass}>
<label className='control-label'>Display Name</label>
- <input onKeyUp={this.displayNameKeyUp} type='text' ref='display_name' className='form-control' placeholder='Enter display name' maxLength='22' />
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ type='text'
+ ref='display_name'
+ className='form-control'
+ placeholder='Enter display name'
+ maxLength='22'
+ />
{displayNameError}
</div>
<div className={nameClass}>
<label className='control-label'>Handle</label>
- <input type='text' className='form-control' ref='channel_name' placeholder="lowercase alphanumeric's only" maxLength='22' />
+ <input
+ type='text'
+ className='form-control'
+ ref='channel_name'
+ placeholder="lowercase alphanumeric's only"
+ maxLength='22'
+ />
{nameError}
</div>
<div className='form-group'>
<label className='control-label'>Description</label>
- <textarea className='form-control no-resize' ref='channel_desc' rows='3' placeholder='Description' maxLength='1024'></textarea>
+ <textarea
+ className='form-control no-resize'
+ ref='channel_desc'
+ rows='3'
+ placeholder='Description'
+ maxLength='1024'
+ />
</div>
{serverError}
</div>
<div className='modal-footer'>
- <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button>
- <button onClick={this.handleSubmit} type='submit' className='btn btn-primary'>Create New {channelTerm}</button>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary'
+ >
+ Create New {channelTerm}
+ </button>
</div>
</form>
</div>
@@ -162,4 +208,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx
index ebc49882b..0b7c41b62 100644
--- a/web/react/components/notify_counts.jsx
+++ b/web/react/components/notify_counts.jsx
@@ -23,27 +23,30 @@ function getCountsStateFromStores() {
return {count: count};
}
-module.exports = React.createClass({
- displayName: 'NotifyCounts',
- componentDidMount: function() {
+export default class NotifyCounts extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = getCountsStateFromStores();
+ }
+ componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
- },
- onListenerChange: function() {
+ }
+ onListenerChange() {
var newState = getCountsStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- },
- getInitialState: function() {
- return getCountsStateFromStores();
- },
- render: function() {
+ }
+ render() {
if (this.state.count) {
return <span className='badge badge-notify'>{this.state.count}</span>;
}
return null;
}
-});
+}
diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx
index b2edea620..399d3b7b9 100644
--- a/web/react/components/password_reset.jsx
+++ b/web/react/components/password_reset.jsx
@@ -1,143 +1,47 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var UserStore = require('../stores/user_store.jsx');
+var PasswordResetSendLink = require('./password_reset_send_link.jsx');
+var PasswordResetForm = require('./password_reset_form.jsx');
-SendResetPasswordLink = React.createClass({
- handleSendLink: function(e) {
- e.preventDefault();
- var state = {};
+export default class PasswordReset extends React.Component {
+ constructor(props) {
+ super(props);
- var email = this.refs.email.getDOMNode().value.trim();
- if (!email) {
- state.error = "Please enter a valid email address."
- this.setState(state);
- return;
- }
-
- state.error = null;
- this.setState(state);
-
- data = {};
- data['email'] = email;
- data['name'] = this.props.teamName;
-
- client.sendPasswordReset(data,
- function(data) {
- this.setState({ error: null, update_text: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, more_update_text: "Please check your inbox." });
- $(this.refs.reset_form.getDOMNode()).hide();
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null, more_update_text: null });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var update_text = this.state.update_text ? <div className="reset-form alert alert-success">{this.state.update_text}{this.state.more_update_text}</div> : null;
- var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
-
- return (
- <div className="col-sm-12">
- <div className="signup-team__container">
- <h3>Password Reset</h3>
- { update_text }
- <form onSubmit={this.handleSendLink} ref="reset_form">
- <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}</p>
- <div className={error ? 'form-group has-error' : 'form-group'}>
- <input type="text" className="form-control" name="email" ref="email" placeholder="Email" />
- </div>
- { error }
- <button type="submit" className="btn btn-primary">Reset my password</button>
- </form>
- </div>
- </div>
- );
+ this.state = {};
}
-});
-
-ResetPassword = React.createClass({
- handlePasswordReset: function(e) {
- e.preventDefault();
- var state = {};
-
- var password = this.refs.password.getDOMNode().value.trim();
- if (!password || password.length < 5) {
- state.error = "Please enter at least 5 characters."
- this.setState(state);
- return;
- }
-
- state.error = null;
- this.setState(state);
-
- data = {};
- data['new_password'] = password;
- data['hash'] = this.props.hash;
- data['data'] = this.props.data;
- data['name'] = this.props.teamName;
-
- client.resetPassword(data,
- function(data) {
- this.setState({ error: null, update_text: "Your password has been updated successfully." });
- }.bind(this),
- function(err) {
- this.setState({ error: err.message, update_text: null });
- }.bind(this)
- );
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href={"/" + this.props.teamName + "/login"}>here</a> to log in.</label></div> : null;
- var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null;
-
- return (
- <div className="col-sm-12">
- <div className="signup-team__container">
- <h3>Password Reset</h3>
- <form onSubmit={this.handlePasswordReset}>
- <p>{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}</p>
- <div className={error ? 'form-group has-error' : 'form-group'}>
- <input type="password" className="form-control" name="password" ref="password" placeholder="Password" />
- </div>
- { error }
- <button type="submit" className="btn btn-primary">Change my password</button>
- { update_text }
- </form>
- </div>
- </div>
- );
- }
-});
-
-module.exports = React.createClass({
- getInitialState: function() {
- return {};
- },
- render: function() {
-
- if (this.props.isReset === "false") {
- return (
- <SendResetPasswordLink
- teamDisplayName={this.props.teamDisplayName}
- teamName={this.props.teamName}
- />
- );
- } else {
+ render() {
+ if (this.props.isReset === 'false') {
return (
- <ResetPassword
+ <PasswordResetSendLink
teamDisplayName={this.props.teamDisplayName}
teamName={this.props.teamName}
- hash={this.props.hash}
- data={this.props.data}
/>
);
}
+
+ return (
+ <PasswordResetForm
+ teamDisplayName={this.props.teamDisplayName}
+ teamName={this.props.teamName}
+ hash={this.props.hash}
+ data={this.props.data}
+ />
+ );
}
-});
+}
+
+PasswordReset.defaultProps = {
+ isReset: '',
+ teamName: '',
+ teamDisplayName: '',
+ hash: '',
+ data: ''
+};
+PasswordReset.propTypes = {
+ isReset: React.PropTypes.string,
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx
new file mode 100644
index 000000000..1b579efbc
--- /dev/null
+++ b/web/react/components/password_reset_form.jsx
@@ -0,0 +1,101 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var client = require('../utils/client.jsx');
+import {config} from '../utils/config.js';
+
+export default class PasswordResetForm extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handlePasswordReset = this.handlePasswordReset.bind(this);
+
+ this.state = {};
+ }
+ handlePasswordReset(e) {
+ e.preventDefault();
+ var state = {};
+
+ var password = React.findDOMNode(this.refs.password).value.trim();
+ if (!password || password.length < 5) {
+ state.error = 'Please enter at least 5 characters.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var data = {};
+ data.new_password = password;
+ data.hash = this.props.hash;
+ data.data = this.props.data;
+ data.name = this.props.teamName;
+
+ client.resetPassword(data,
+ function resetSuccess() {
+ this.setState({error: null, updateText: 'Your password has been updated successfully.'});
+ }.bind(this),
+ function resetFailure(err) {
+ this.setState({error: err.message, updateText: null});
+ }.bind(this)
+ );
+ }
+ render() {
+ var updateText = null;
+ if (this.state.updateText) {
+ updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>;
+ }
+
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>Password Reset</h3>
+ <form onSubmit={this.handlePasswordReset}>
+ <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + config.SiteName + ' account.'}</p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder='Password'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ Change my password
+ </button>
+ {updateText}
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+PasswordResetForm.defaultProps = {
+ teamName: '',
+ teamDisplayName: '',
+ hash: '',
+ data: ''
+};
+PasswordResetForm.propTypes = {
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx
new file mode 100644
index 000000000..1e6cc3607
--- /dev/null
+++ b/web/react/components/password_reset_send_link.jsx
@@ -0,0 +1,98 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var client = require('../utils/client.jsx');
+
+export default class PasswordResetSendLink extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSendLink = this.handleSendLink.bind(this);
+
+ this.state = {};
+ }
+ handleSendLink(e) {
+ e.preventDefault();
+ var state = {};
+
+ var email = React.findDOMNode(this.refs.email).value.trim();
+ if (!email) {
+ state.error = 'Please enter a valid email address.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var data = {};
+ data.email = email;
+ data.name = this.props.teamName;
+
+ client.sendPasswordReset(data,
+ function passwordResetSent() {
+ this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'});
+ $(React.findDOMNode(this.refs.reset_form)).hide();
+ }.bind(this),
+ function passwordResetFailedToSend(err) {
+ this.setState({error: err.message, update_text: null, moreUpdateText: null});
+ }.bind(this)
+ );
+ }
+ render() {
+ var updateText = null;
+ if (this.state.updateText) {
+ updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>;
+ }
+
+ var error = null;
+ if (this.state.error) {
+ error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
+ }
+
+ var formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>Password Reset</h3>
+ {updateText}
+ <form
+ onSubmit={this.handleSendLink}
+ ref='reset_form'
+ >
+ <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p>
+ <div className={formClass}>
+ <input
+ type='text'
+ className='form-control'
+ name='email'
+ ref='email'
+ placeholder='Email'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ Reset my password
+ </button>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+PasswordResetSendLink.defaultProps = {
+ teamName: '',
+ teamDisplayName: ''
+};
+PasswordResetSendLink.propTypes = {
+ teamName: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
new file mode 100644
index 000000000..fb9522afb
--- /dev/null
+++ b/web/react/components/popover_list_members.jsx
@@ -0,0 +1,80 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class PopoverListMembers extends React.Component {
+ componentDidMount() {
+ const originalLeave = $.fn.popover.Constructor.prototype.leave;
+ $.fn.popover.Constructor.prototype.leave = function onLeave(obj) {
+ let selfObj;
+ if (obj instanceof this.constructor) {
+ selfObj = obj;
+ } else {
+ selfObj = $(obj.currentTarget)[this.type](this.getDelegateOptions()).data(`bs.${this.type}`);
+ }
+ originalLeave.call(this, obj);
+
+ if (obj.currentTarget && selfObj.$tip) {
+ selfObj.$tip.one('mouseenter', function onMouseEnter() {
+ clearTimeout(selfObj.timeout);
+ selfObj.$tip.one('mouseleave', function onMouseLeave() {
+ $.fn.popover.Constructor.prototype.leave.call(selfObj, selfObj);
+ });
+ });
+ }
+ };
+
+ $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
+ $('body').on('click', function onClick(e) {
+ if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
+ $('#member_popover').popover('hide');
+ }
+ });
+ }
+ render() {
+ let popoverHtml = '';
+ const members = this.props.members;
+ let count;
+ if (members.length > 20) {
+ count = '20+';
+ } else {
+ count = members.length || '-';
+ }
+
+ if (members) {
+ members.sort(function compareByLocal(a, b) {
+ return a.username.localeCompare(b.username);
+ });
+
+ members.forEach(function addMemberElement(m) {
+ popoverHtml += `<div class='text--nowrap'>${m.username}</div>`;
+ });
+ }
+
+ return (
+ <div
+ id='member_popover'
+ data-toggle='popover'
+ data-content={popoverHtml}
+ data-original-title='Members'
+ >
+ <div
+ id='member_tooltip'
+ data-placement='left'
+ data-toggle='tooltip'
+ title='View Channel Members'
+ >
+ {count}
+ <span
+ className='glyphicon glyphicon-user'
+ aria-hidden='true'
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+PopoverListMembers.propTypes = {
+ members: React.PropTypes.array.isRequired,
+ channelId: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index acc2b51d2..37de4ecc0 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -15,9 +15,17 @@ var utils = require('../utils/utils.jsx');
var PostInfo = require('./post_info.jsx');
-module.exports = React.createClass({
- displayName: 'Post',
- handleCommentClick: function(e) {
+export default class Post extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleCommentClick = this.handleCommentClick.bind(this);
+ this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
+ this.retryPost = this.retryPost.bind(this);
+
+ this.state = {};
+ }
+ handleCommentClick(e) {
e.preventDefault();
var data = {};
@@ -33,31 +41,31 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_SEARCH,
results: null
});
- },
- forceUpdateInfo: function() {
+ }
+ forceUpdateInfo() {
this.refs.info.forceUpdate();
this.refs.header.forceUpdate();
- },
- retryPost: function(e) {
+ }
+ retryPost(e) {
e.preventDefault();
var post = this.props.post;
client.createPost(post, post.channel_id,
- function(data) {
+ function success(data) {
AsyncClient.getPosts();
var channel = ChannelStore.get(post.channel_id);
var member = ChannelStore.getMember(post.channel_id);
member.msg_count = channel.total_msg_count;
- member.last_viewed_at = (new Date).getTime();
+ member.last_viewed_at = utils.getTimestamp();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POST,
post: data
});
- }.bind(this),
- function(err) {
+ },
+ function error() {
post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post);
this.forceUpdate();
@@ -67,18 +75,15 @@ module.exports = React.createClass({
post.state = Constants.POST_LOADING;
PostStore.updatePendingPost(post);
this.forceUpdate();
- },
- shouldComponentUpdate: function(nextProps) {
+ }
+ shouldComponentUpdate(nextProps) {
if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
}
return false;
- },
- getInitialState: function() {
- return { };
- },
- render: function() {
+ }
+ render() {
var post = this.props.post;
var parentPost = this.props.parentPost;
var posts = this.props.posts;
@@ -89,19 +94,27 @@ module.exports = React.createClass({
}
var commentCount = 0;
- var commentRootId = parentPost ? post.root_id : post.id;
+ var commentRootId;
+ if (parentPost) {
+ commentRootId = post.root_id;
+ } else {
+ commentRootId = post.id;
+ }
for (var postId in posts) {
- if (posts[postId].root_id == commentRootId) {
+ if (posts[postId].root_id === commentRootId) {
commentCount += 1;
}
}
- var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
-
- var rootUser = this.props.sameRoot ? 'same--root' : 'other--root';
+ var rootUser;
+ if (this.props.sameRoot) {
+ rootUser = 'same--root';
+ } else {
+ rootUser = 'other--root';
+ }
var postType = '';
- if (type != 'Post'){
+ if (type !== 'Post') {
postType = 'post--comment';
}
@@ -122,21 +135,65 @@ module.exports = React.createClass({
sameUserClass = 'same--user';
}
+ var profilePic = null;
+ if (!this.props.hideProfilePic) {
+ profilePic = (
+ <div className='post-profile-img__container'>
+ <img
+ className='post-profile-img'
+ src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
+ height='36'
+ width='36'
+ />
+ </div>
+ );
+ }
+
return (
<div>
- <div id={post.id} className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}>
- { !this.props.hideProfilePic ?
- <div className='post-profile-img__container'>
- <img className='post-profile-img' src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} height='36' width='36' />
- </div>
- : null }
+ <div
+ id={post.id}
+ className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}
+ >
+ {profilePic}
<div className='post__content'>
- <PostHeader ref='header' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
- <PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} retryPost={this.retryPost} />
- <PostInfo ref='info' post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} allowReply='true' />
+ <PostHeader
+ ref='header'
+ post={post}
+ sameRoot={this.props.sameRoot}
+ commentCount={commentCount}
+ handleCommentClick={this.handleCommentClick}
+ isLastComment={this.props.isLastComment}
+ />
+ <PostBody
+ post={post}
+ sameRoot={this.props.sameRoot}
+ parentPost={parentPost}
+ posts={posts}
+ handleCommentClick={this.handleCommentClick}
+ retryPost={this.retryPost}
+ />
+ <PostInfo
+ ref='info'
+ post={post}
+ sameRoot={this.props.sameRoot}
+ commentCount={commentCount}
+ handleCommentClick={this.handleCommentClick}
+ allowReply='true'
+ />
</div>
</div>
</div>
);
}
-});
+}
+
+Post.propTypes = {
+ post: React.PropTypes.object,
+ posts: React.PropTypes.object,
+ parentPost: React.PropTypes.object,
+ sameUser: React.PropTypes.bool,
+ sameRoot: React.PropTypes.bool,
+ hideProfilePic: React.PropTypes.bool,
+ isLastComment: React.PropTypes.bool
+};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index e5ab5b624..d9b8f20ce 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -1,95 +1,152 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var FileAttachmentList = require('./file_attachment_list.jsx');
-var UserStore = require('../stores/user_store.jsx');
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-
-module.exports = React.createClass({
- componentWillReceiveProps: function(nextProps) {
- var linkData = utils.extractLinks(nextProps.post.message);
+const FileAttachmentList = require('./file_attachment_list.jsx');
+const UserStore = require('../stores/user_store.jsx');
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const twemoji = require('twemoji');
+
+export default class PostBody extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.parseEmojis = this.parseEmojis.bind(this);
+
+ const linkData = Utils.extractLinks(this.props.post.message);
+ this.state = {links: linkData.links, message: linkData.text};
+ }
+ parseEmojis() {
+ twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
+ }
+ componentDidMount() {
+ this.parseEmojis();
+ }
+ componentDidUpdate() {
+ this.parseEmojis();
+ }
+ componentWillReceiveProps(nextProps) {
+ const linkData = Utils.extractLinks(nextProps.post.message);
this.setState({links: linkData.links, message: linkData.text});
- },
- getInitialState: function() {
- var linkData = utils.extractLinks(this.props.post.message);
- return {links: linkData.links, message: linkData.text};
- },
- render: function() {
- var post = this.props.post;
- var filenames = this.props.post.filenames;
- var parentPost = this.props.parentPost;
- var inner = utils.textToJsx(this.state.message);
-
- var comment = '';
- var reply = '';
- var postClass = '';
+ }
+ render() {
+ const post = this.props.post;
+ const filenames = this.props.post.filenames;
+ const parentPost = this.props.parentPost;
+ const inner = Utils.textToJsx(this.state.message);
+
+ let comment = '';
+ let postClass = '';
if (parentPost) {
- var profile = UserStore.getProfile(parentPost.user_id);
- var apostrophe = '';
- var name = '...';
+ const profile = UserStore.getProfile(parentPost.user_id);
+
+ let apostrophe = '';
+ let name = '...';
if (profile != null) {
if (profile.username.slice(-1) === 's') {
apostrophe = '\'';
} else {
apostrophe = '\'s';
}
- name = <a className='theme' onClick={function searchName() { utils.searchForTerm(profile.username); }}>{profile.username}</a>;
+ name = (
+ <a
+ className='theme'
+ onClick={Utils.searchForTerm.bind(null, profile.username)}
+ >
+ {profile.username}
+ </a>
+ );
}
- var message = '';
+ let message = '';
if (parentPost.message) {
- message = utils.replaceHtmlEntities(parentPost.message);
+ message = Utils.replaceHtmlEntities(parentPost.message);
} else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
message += ' plus 1 other file';
} else if (parentPost.filenames.length > 2) {
- message += ' plus ' + (parentPost.filenames.length - 1) + ' other files';
+ message += ` plus ${parentPost.filenames.length - 1} other files`;
}
}
comment = (
<p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span>
+ <span>
+ Commented on {name}{apostrophe} message:
+ <a
+ className='theme'
+ onClick={this.props.handleCommentClick}
+ >
+ {message}
+ </a>
+ </span>
</p>
);
postClass += ' post-comment';
}
- var loading;
+ let loading;
if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail';
- loading = <a className='theme post-retry pull-right' href='#' onClick={this.props.retryPost}>Retry</a>;
+ loading = (
+ <a
+ className='theme post-retry pull-right'
+ href='#'
+ onClick={this.props.retryPost}
+ >
+ Retry
+ </a>
+ );
} else if (post.state === Constants.POST_LOADING) {
postClass += ' post-waiting';
- loading = <img className='post-loading-gif pull-right' src='/static/images/load.gif'/>;
+ loading = (
+ <img
+ className='post-loading-gif pull-right'
+ src='/static/images/load.gif'
+ />
+ );
}
- var embed;
+ let embed;
if (filenames.length === 0 && this.state.links) {
- embed = utils.getEmbed(this.state.links[0]);
+ embed = Utils.getEmbed(this.state.links[0]);
}
- var fileAttachmentHolder = '';
+ let fileAttachmentHolder = '';
if (filenames && filenames.length > 0) {
- fileAttachmentHolder = (<FileAttachmentList
- filenames={filenames}
- modalId={'view_image_modal_' + post.id}
- channelId={post.channel_id}
- userId={post.user_id} />);
+ fileAttachmentHolder = (
+ <FileAttachmentList
+ filenames={filenames}
+ modalId={`view_image_modal_${post.id}`}
+ channelId={post.channel_id}
+ userId={post.user_id}
+ />
+ );
}
return (
<div className='post-body'>
{comment}
- <p key={post.id + '_message'} className={postClass}>{loading}<span>{inner}</span></p>
+ <p
+ key={`${post.id}_message`}
+ className={postClass}
+ >
+ {loading}<span>{inner}</span>
+ </p>
{fileAttachmentHolder}
{embed}
</div>
);
}
-});
+}
+
+PostBody.propTypes = {
+ post: React.PropTypes.object.isRequired,
+ parentPost: React.PropTypes.object,
+ retryPost: React.PropTypes.func.isRequired,
+ handleCommentClick: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index 83b007bad..d284a9d1b 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -3,34 +3,61 @@
var UserStore = require('../stores/user_store.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { };
- },
- render: function() {
- var currentUser = UserStore.getCurrentUser()
+export default class PostDeletedModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+ render() {
+ var currentUser = UserStore.getCurrentUser();
if (currentUser != null) {
return (
- <div className="modal fade" ref="modal" id="post_deleted" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">Comment could not be posted</h4>
- </div>
- <div className="modal-body">
- <p>Someone deleted the message on which you tried to post a comment.</p>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='post_deleted'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >
+ Comment could not be posted
+ </h4>
+ </div>
+ <div className='modal-body'>
+ <p>Someone deleted the message on which you tried to post a comment.</p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-primary'
+ data-dismiss='modal'
+ >
+ Okay
+ </button>
+ </div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button>
- </div>
- </div>
- </div>
+ </div>
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-});
+}
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index 129db6d14..9dc525e03 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -1,23 +1,42 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserProfile = require( './user_profile.jsx' );
+var UserProfile = require('./user_profile.jsx');
var PostInfo = require('./post_info.jsx');
-module.exports = React.createClass({
- getInitialState: function() {
- return { };
- },
- render: function() {
+export default class PostHeader extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+ render() {
var post = this.props.post;
return (
- <ul className="post-header post-header-post">
- <li className="post-header-col post-header__name"><strong><UserProfile userId={post.user_id} /></strong></li>
- <li className="post-info--hidden">
- <PostInfo post={post} commentCount={this.props.commentCount} handleCommentClick={this.props.handleCommentClick} allowReply="true" isLastComment={this.props.isLastComment} />
+ <ul className='post-header post-header-post'>
+ <li className='post-header-col post-header__name'><strong><UserProfile userId={post.user_id} /></strong></li>
+ <li className='post-info--hidden'>
+ <PostInfo
+ post={post}
+ commentCount={this.props.commentCount}
+ handleCommentClick={this.props.handleCommentClick}
+ allowReply='true'
+ isLastComment={this.props.isLastComment}
+ />
</li>
</ul>
);
}
-});
+}
+
+PostHeader.defaultProps = {
+ post: null,
+ commentCount: 0,
+ isLastComment: false
+};
+PostHeader.propTypes = {
+ post: React.PropTypes.object,
+ commentCount: React.PropTypes.number,
+ isLastComment: React.PropTypes.bool,
+ handleCommentClick: React.PropTypes.func
+};
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 73e897f62..c80b287a3 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -43,13 +43,16 @@ export default class PostInfo extends React.Component {
if (isOwner) {
dropdownContents.push(
- <li role='presentation'>
+ <li
+ key='editPost'
+ role='presentation'
+ >
<a
href='#'
role='menuitem'
data-toggle='modal'
data-target='#edit_post'
- data-refoucsid="#post_textbox"
+ data-refoucsid='#post_textbox'
data-title={type}
data-message={post.message}
data-postid={post.id}
@@ -64,7 +67,10 @@ export default class PostInfo extends React.Component {
if (isOwner || isAdmin) {
dropdownContents.push(
- <li role='presentation'>
+ <li
+ key='deletePost'
+ role='presentation'
+ >
<a
href='#'
role='menuitem'
@@ -83,7 +89,10 @@ export default class PostInfo extends React.Component {
if (this.props.allowReply === 'true') {
dropdownContents.push(
- <li role='presentation'>
+ <li
+ key='replyLink'
+ role='presentation'
+ >
<a
className='reply-link theme'
href='#'
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 5b0b1f79a..6fa87ca4a 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -15,6 +15,8 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
+import {strings} from '../utils/config.js';
+
export default class PostList extends React.Component {
constructor() {
super();
@@ -51,8 +53,10 @@ export default class PostList extends React.Component {
if (deletedPosts && Object.keys(deletedPosts).length > 0) {
for (var pid in deletedPosts) {
- postList.posts[pid] = deletedPosts[pid];
- postList.order.unshift(pid);
+ if (deletedPosts.hasOwnProperty(pid)) {
+ postList.posts[pid] = deletedPosts[pid];
+ postList.order.unshift(pid);
+ }
}
postList.order.sort(function postSort(a, b) {
@@ -71,7 +75,9 @@ export default class PostList extends React.Component {
if (pendingPostList) {
postList.order = pendingPostList.order.concat(postList.order);
for (var ppid in pendingPostList.posts) {
- postList.posts[ppid] = pendingPostList.posts[ppid];
+ if (pendingPostList.posts.hasOwnProperty(ppid)) {
+ postList.posts[ppid] = pendingPostList.posts[ppid];
+ }
}
}
}
@@ -267,7 +273,6 @@ export default class PostList extends React.Component {
}
}
onSocketChange(msg) {
- var postList;
var post;
if (msg.action === 'posted' || msg.action === 'post_edited') {
post = JSON.parse(msg.props.post);
@@ -280,7 +285,6 @@ export default class PostList extends React.Component {
}
post = JSON.parse(msg.props.post);
- postList = this.state.postList;
PostStore.storeUnseenDeletedPost(post);
PostStore.removePost(post, true);
@@ -401,6 +405,14 @@ export default class PostList extends React.Component {
>
<i className='fa fa-pencil'></i>Set a description
</a>
+ <a
+ className='intro-links'
+ href='#'
+ data-toggle='modal'
+ data-target='#channel_invite'
+ >
+ <i className='fa fa-user-plus'></i>Invite others to this channel
+ </a>
</div>
);
}
@@ -506,7 +518,7 @@ export default class PostList extends React.Component {
var postCtl = (
<Post
- key={post.id}
+ key={post.id + 'postKey'}
ref={post.id}
sameUser={sameUser}
sameRoot={sameRoot}
@@ -547,7 +559,7 @@ export default class PostList extends React.Component {
>
<hr
className='separator__hr'
- />
+ />
<div className='separator__text'>New Messages</div>
</div>
);
@@ -567,14 +579,14 @@ export default class PostList extends React.Component {
var order = this.state.postList.order;
var channelId = this.state.channel.id;
- $(this.refs.loadmore.getDOMNode()).text('Retrieving more messages...');
+ $(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...');
Client.getPostsPage(
channelId,
order.length,
Constants.POST_CHUNK_SIZE,
function success(data) {
- $(this.refs.loadmore.getDOMNode()).text('Load more messages');
+ $(React.findDOMNode(this.refs.loadmore)).text('Load more messages');
this.gotMorePosts = true;
this.setState({numToDisplay: this.state.numToDisplay + Constants.POST_CHUNK_SIZE});
@@ -599,7 +611,7 @@ export default class PostList extends React.Component {
Client.getProfiles();
}.bind(this),
function fail(err) {
- $(this.refs.loadmore.getDOMNode()).text('Load more messages');
+ $(React.findDOMNode(this.refs.loadmore)).text('Load more messages');
AsyncClient.dispatchError(err, 'getPosts');
}.bind(this)
);
@@ -636,11 +648,18 @@ export default class PostList extends React.Component {
if (posts && this.state.isFirstLoadComplete) {
postCtls = this.createPosts(posts, order);
} else {
- postCtls.push(<LoadingScreen position='absolute' />);
+ postCtls.push(
+ <LoadingScreen
+ position='absolute'
+ key='loading'
+ />);
}
return (
- <div ref='postlist' className='post-list-holder-by-time'>
+ <div
+ ref='postlist'
+ className='post-list-holder-by-time'
+ >
<div className='post-list__table'>
<div className='post-list__content'>
{moreMessages}
diff --git a/web/react/components/removed_from_channel_modal.jsx b/web/react/components/removed_from_channel_modal.jsx
index 4a49e1c98..b7ec85457 100644
--- a/web/react/components/removed_from_channel_modal.jsx
+++ b/web/react/components/removed_from_channel_modal.jsx
@@ -3,62 +3,99 @@
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx')
+var BrowserStore = require('../stores/browser_store.jsx');
var utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- handleShow: function() {
- var newState = {};
- if(BrowserStore.getItem("channel-removed-state")) {
- newState = BrowserStore.getItem("channel-removed-state");
- BrowserStore.removeItem("channel-removed-state");
- }
+export default class RemovedFromChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
- this.setState(newState);
- },
- handleClose: function() {
- var townSquare = ChannelStore.getByName("town-square");
- utils.switchChannel(townSquare);
+ this.handleShow = this.handleShow.bind(this);
+ this.handleClose = this.handleClose.bind(this);
- this.setState({channelName: "", remover: ""});
- },
- componentDidMount: function() {
- $(this.getDOMNode()).on('show.bs.modal',this.handleShow);
- $(this.getDOMNode()).on('hidden.bs.modal',this.handleClose);
- },
- componentWillUnmount: function() {
- $(this.getDOMNode()).off('show.bs.modal',this.handleShow);
- $(this.getDOMNode()).off('hidden.bs.modal',this.handleClose);
- },
- getInitialState: function() {
- return {channelName: "", remover: ""}
- },
- render: function() {
+ this.state = {
+ channelName: '',
+ remover: ''
+ };
+ }
+
+ handleShow() {
+ var newState = {};
+ if (BrowserStore.getItem('channel-removed-state')) {
+ newState = BrowserStore.getItem('channel-removed-state');
+ BrowserStore.removeItem('channel-removed-state');
+ }
+
+ this.setState(newState);
+ }
+
+ handleClose() {
+ var townSquare = ChannelStore.getByName('town-square');
+ utils.switchChannel(townSquare);
+
+ this.setState({channelName: '', remover: ''});
+ }
+
+ componentDidMount() {
+ $(React.findDOMNode(this)).on('show.bs.modal', this.handleShow);
+ $(React.findDOMNode(this)).on('hidden.bs.modal', this.handleClose);
+ }
+
+ componentWillUnmount() {
+ $(React.findDOMNode(this)).off('show.bs.modal', this.handleShow);
+ $(React.findDOMNode(this)).off('hidden.bs.modal', this.handleClose);
+ }
+
+ render() {
var currentUser = UserStore.getCurrentUser();
- var channelName = this.state.channelName ? this.state.channelName : "the channel"
- var remover = this.state.remover ? this.state.remover : "Someone"
+
+ var channelName = 'the channel';
+ if (this.state.channelName) {
+ channelName = this.state.channelName;
+ }
+
+ var remover = 'Someone';
+ if (this.state.remover) {
+ remover = this.state.remover;
+ }
if (currentUser != null) {
return (
- <div className='modal fade' ref='modal' id='removed_from_channel' tabIndex='-1' role='dialog' aria-hidden='true'>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4>
- </div>
- <div className='modal-body'>
- <p>{remover} removed you from {channelName}</p>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='removed_from_channel'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ ><span aria-hidden='true'>&times;</span></button>
+ <h4 className='modal-title'>Removed from <span className='name'>{channelName}</span></h4>
+ </div>
+ <div className='modal-body'>
+ <p>{remover} removed you from {channelName}</p>
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-primary'
+ data-dismiss='modal'
+ >Okay</button>
+ </div>
</div>
- <div className='modal-footer'>
- <button type='button' className='btn btn-primary' data-dismiss='modal'>Okay</button>
- </div>
- </div>
- </div>
+ </div>
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-}); \ No newline at end of file
+} \ No newline at end of file
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 2fe6dd96b..37958b649 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -1,147 +1,217 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+const ChannelStore = require('../stores/channel_store.jsx');
-var utils = require('../utils/utils.jsx');
-var Client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
-var Constants = require('../utils/constants.jsx');
+export default class RenameChannelModal extends React.Component {
+ constructor(props) {
+ super(props);
-module.exports = React.createClass({
- handleSubmit: function(e) {
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.onNameChange = this.onNameChange.bind(this);
+ this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
+ this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ displayName: '',
+ channelName: '',
+ channelId: '',
+ serverError: '',
+ nameError: '',
+ displayNameError: '',
+ invalid: false
+ };
+ }
+ handleSubmit(e) {
e.preventDefault();
- if (this.state.channel_id.length !== 26) return;
+ if (this.state.channelId.length !== 26) {
+ return;
+ }
- var channel = ChannelStore.get(this.state.channel_id);
- var oldName = channel.name
- var oldDisplayName = channel.display_name
- var state = { server_error: "" };
+ let channel = ChannelStore.get(this.state.channelId);
+ const oldName = channel.name;
+ const oldDisplayName = channel.displayName;
+ let state = {serverError: ''};
- channel.display_name = this.state.display_name.trim();
+ channel.display_name = this.state.displayName.trim();
if (!channel.display_name) {
- state.display_name_error = "This field is required";
- state.inValid = true;
- }
- else if (channel.display_name.length > 22) {
- state.display_name_error = "This field must be less than 22 characters";
- state.inValid = true;
- }
- else {
- state.display_name_error = "";
+ state.displayNameError = 'This field is required';
+ state.invalid = true;
+ } else if (channel.display_name.length > 22) {
+ state.displayNameError = 'This field must be less than 22 characters';
+ state.invalid = true;
+ } else {
+ state.displayNameError = '';
}
- channel.name = this.state.channel_name.trim();
+ channel.name = this.state.channelName.trim();
if (!channel.name) {
- state.name_error = "This field is required";
- state.inValid = true;
- }
- else if(channel.name.length > 22){
- state.name_error = "This field must be less than 22 characters";
- state.inValid = true;
- }
- else {
- var cleaned_name = utils.cleanUpUrlable(channel.name);
- if (cleaned_name != channel.name) {
- state.name_error = "Must be lowercase alphanumeric characters";
- state.inValid = true;
- }
- else {
- state.name_error = "";
+ state.nameError = 'This field is required';
+ state.invalid = true;
+ } else if (channel.name.length > 22) {
+ state.nameError = 'This field must be less than 22 characters';
+ state.invalid = true;
+ } else {
+ let cleanedName = Utils.cleanUpUrlable(channel.name);
+ if (cleanedName !== channel.name) {
+ state.nameError = 'Must be lowercase alphanumeric characters';
+ state.invalid = true;
+ } else {
+ state.nameError = '';
}
}
this.setState(state);
- if (state.inValid)
- return;
-
- if (oldName == channel.name && oldDisplayName == channel.display_name)
+ if (state.invalid || (oldName === channel.name && oldDisplayName === channel.display_name)) {
return;
+ }
Client.updateChannel(channel,
- function(data, text, req) {
- $(this.refs.modal.getDOMNode()).modal('hide');
+ function handleUpdateSuccess() {
+ $(React.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
- utils.updateTabTitle(channel.display_name);
- utils.updateAddressBar(channel.name);
+ Utils.updateTabTitle(channel.display_name);
+ Utils.updateAddressBar(channel.name);
- this.refs.display_name.getDOMNode().value = "";
- this.refs.channel_name.getDOMNode().value = "";
+ React.findDOMNode(this.refs.displayName).value = '';
+ React.findDOMNode(this.refs.channelName).value = '';
}.bind(this),
- function(err) {
- state.server_error = err.message;
- state.inValid = true;
+ function handleUpdateError(err) {
+ state.serverError = err.message;
+ state.invalid = true;
this.setState(state);
}.bind(this)
);
- },
- onNameChange: function() {
- this.setState({ channel_name: this.refs.channel_name.getDOMNode().value })
- },
- onDisplayNameChange: function() {
- this.setState({ display_name: this.refs.display_name.getDOMNode().value })
- },
- displayNameKeyUp: function(e) {
- var display_name = this.refs.display_name.getDOMNode().value.trim();
- var channel_name = utils.cleanUpUrlable(display_name);
- this.refs.channel_name.getDOMNode().value = channel_name;
- this.setState({ channel_name: channel_name })
- },
- handleClose: function() {
- this.setState({display_name: "", channel_name: "", display_name_error: "", server_error: "", name_error: ""});
- },
- componentDidMount: function() {
- var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- var button = $(e.relatedTarget);
- self.setState({ display_name: button.attr('data-display'), channel_name: button.attr('data-name'), channel_id: button.attr('data-channelid') });
- });
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
- $(this.refs.modal.getDOMNode()).off('hidden.bs.modal', this.handleClose);
- },
- getInitialState: function() {
- return { display_name: "", channel_name: "", channel_id: "" };
- },
- render: function() {
-
- var display_name_error = this.state.display_name_error ? <label className='control-label'>{ this.state.display_name_error }</label> : null;
- var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null;
- var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ }
+ onNameChange() {
+ this.setState({channelName: React.findDOMNode(this.refs.channelName).value});
+ }
+ onDisplayNameChange() {
+ this.setState({displayName: React.findDOMNode(this.refs.displayName).value});
+ }
+ displayNameKeyUp() {
+ const displayName = React.findDOMNode(this.refs.displayName).value.trim();
+ const channelName = Utils.cleanUpUrlable(displayName);
+ React.findDOMNode(this.refs.channelName).value = channelName;
+ this.setState({channelName: channelName});
+ }
+ handleClose() {
+ this.state = {
+ displayName: '',
+ channelName: '',
+ channelId: '',
+ serverError: '',
+ nameError: '',
+ displayNameError: '',
+ invalid: false
+ };
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function handleShow(e) {
+ const button = $(e.relatedTarget);
+ this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
+ }.bind(this));
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
+ }
+ componentWillUnmount() {
+ $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
+ }
+ render() {
+ let displayNameError = null;
+ let displayNameClass = 'form-group';
+ if (this.state.displayNameError) {
+ displayNameError = <label className='control-label'>{this.state.displayNameError}</label>;
+ displayNameClass += ' has-error';
+ }
+
+ let nameError = null;
+ let nameClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameClass += ' has-error';
+ }
+
+ let serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
return (
- <div className="modal fade" ref="modal" id="rename_channel" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal">
- <span aria-hidden="true">&times;</span>
- <span className="sr-only">Close</span>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='rename_channel'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ >
+ <span aria-hidden='true'>&times;</span>
+ <span className='sr-only'>Close</span>
</button>
- <h4 className="modal-title">Rename Channel</h4>
+ <h4 className='modal-title'>Rename Channel</h4>
</div>
- <form role="form">
- <div className="modal-body">
- <div className={ this.state.display_name_error ? "form-group has-error" : "form-group" }>
+ <form role='form'>
+ <div className='modal-body'>
+ <div className={displayNameClass}>
<label className='control-label'>Display Name</label>
- <input onKeyUp={this.displayNameKeyUp} onChange={this.onDisplayNameChange} type="text" ref="display_name" className="form-control" placeholder="Enter display name" value={this.state.display_name} maxLength="64" />
- { display_name_error }
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ onChange={this.onDisplayNameChange}
+ type='text'
+ ref='displayName'
+ className='form-control'
+ placeholder='Enter display name'
+ value={this.state.displayName}
+ maxLength='64'
+ />
+ {displayNameError}
</div>
- <div className={ this.state.name_error ? "form-group has-error" : "form-group" }>
+ <div className={nameClass}>
<label className='control-label'>Handle</label>
- <input onChange={this.onNameChange} type="text" className="form-control" ref="channel_name" placeholder="lowercase alphanumeric's only" value={this.state.channel_name} maxLength="64" />
- { name_error }
+ <input
+ onChange={this.onNameChange}
+ type='text'
+ className='form-control'
+ ref='channelName'
+ placeholder='lowercase alphanumeric&#39;s only'
+ value={this.state.channelName}
+ maxLength='64'
+ />
+ {nameError}
</div>
- { server_error }
+ {serverError}
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
- <button onClick={this.handleSubmit} type="submit" className="btn btn-primary">Save</button>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >
+ Cancel
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary'
+ >
+ Save
+ </button>
</div>
</form>
</div>
@@ -149,4 +219,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 7df2fed9e..f1a90102c 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -6,18 +6,20 @@ var ChannelStore = require('../stores/channel_store.jsx');
var UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var ActionTypes = Constants.ActionTypes;
+var twemoji = require('twemoji');
export default class RhsComment extends React.Component {
constructor(props) {
super(props);
this.retryComment = this.retryComment.bind(this);
+ this.parseEmojis = this.parseEmojis.bind(this);
this.state = {};
}
@@ -25,7 +27,7 @@ export default class RhsComment extends React.Component {
e.preventDefault();
var post = this.props.post;
- client.createPost(post, post.channel_id,
+ Client.createPost(post, post.channel_id,
function success(data) {
AsyncClient.getPosts(post.channel_id);
@@ -51,13 +53,22 @@ export default class RhsComment extends React.Component {
PostStore.updatePendingPost(post);
this.forceUpdate();
}
+ parseEmojis() {
+ twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
+ }
+ componentDidMount() {
+ this.parseEmojis();
+ }
shouldComponentUpdate(nextProps) {
- if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ if (!Utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
}
return false;
}
+ componentDidUpdate() {
+ this.parseEmojis();
+ }
render() {
var post = this.props.post;
@@ -73,7 +84,7 @@ export default class RhsComment extends React.Component {
type = 'Comment';
}
- var message = utils.textToJsx(post.message);
+ var message = Utils.textToJsx(post.message);
var timestamp = UserStore.getCurrentUser().update_at;
var loading;
@@ -161,7 +172,8 @@ export default class RhsComment extends React.Component {
filenames={post.filenames}
modalId={'rhs_comment_view_image_modal_' + post.id}
channelId={post.channel_id}
- userId={post.user_id} />
+ userId={post.user_id}
+ />
);
}
@@ -182,7 +194,7 @@ export default class RhsComment extends React.Component {
</li>
<li className='post-header-col'>
<time className='post-right-comment-time'>
- {utils.displayCommentDateTime(post.create_at)}
+ {Utils.displayCommentDateTime(post.create_at)}
</time>
</li>
<li className='post-header-col post-header__reply'>
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index 4cf4231e9..5156ec4d7 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -1,9 +1,9 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
export default class RhsHeaderPost extends React.Component {
constructor(props) {
@@ -43,7 +43,7 @@ export default class RhsHeaderPost extends React.Component {
});
}
render() {
- var back;
+ let back;
if (this.props.fromSearch) {
back = (
<a
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index a407e6470..83b57b955 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -6,12 +6,23 @@ var UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
+var twemoji = require('twemoji');
+var Constants = require('../utils/constants.jsx');
export default class RhsRootPost extends React.Component {
constructor(props) {
super(props);
+
+ this.parseEmojis = this.parseEmojis.bind(this);
+
this.state = {};
}
+ parseEmojis() {
+ twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
+ }
+ componentDidMount() {
+ this.parseEmojis();
+ }
shouldComponentUpdate(nextProps) {
if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
return true;
@@ -19,6 +30,9 @@ export default class RhsRootPost extends React.Component {
return false;
}
+ componentDidUpdate() {
+ this.parseEmojis();
+ }
render() {
var post = this.props.post;
var message = utils.textToJsx(post.message);
@@ -99,7 +113,8 @@ export default class RhsRootPost extends React.Component {
filenames={post.filenames}
modalId={'rhs_view_image_modal_' + post.id}
channelId={post.channel_id}
- userId={post.user_id} />
+ userId={post.user_id}
+ />
);
}
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index adddeccf0..2f23d80d9 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -18,7 +18,6 @@ export default class RhsThread extends React.Component {
this.onChange = this.onChange.bind(this);
this.onChangeAll = this.onChangeAll.bind(this);
- this.onTimeChange = this.onTimeChange.bind(this);
this.state = this.getStateFromStores();
}
@@ -33,7 +32,9 @@ export default class RhsThread extends React.Component {
if (pendingPostList) {
for (var pid in pendingPostList.posts) {
- postList.posts[pid] = pendingPostList.posts[pid];
+ if (pendingPostList.posts.hasOwnProperty(pid)) {
+ postList.posts[pid] = pendingPostList.posts[pid];
+ }
}
}
@@ -42,7 +43,6 @@ export default class RhsThread extends React.Component {
componentDidMount() {
PostStore.addSelectedPostChangeListener(this.onChange);
PostStore.addChangeListener(this.onChangeAll);
- UserStore.addStatusesChangeListener(this.onTimeChange);
this.resize();
$(window).resize(function resize() {
this.resize();
@@ -56,7 +56,6 @@ export default class RhsThread extends React.Component {
componentWillUnmount() {
PostStore.removeSelectedPostChangeListener(this.onChange);
PostStore.removeChangeListener(this.onChangeAll);
- UserStore.removeStatusesChangeListener(this.onTimeChange);
}
onChange() {
var newState = this.getStateFromStores();
@@ -81,7 +80,9 @@ export default class RhsThread extends React.Component {
if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) {
currentSelected.posts = {};
for (var postId in currentPosts.posts) {
- currentSelected.posts[postId] = currentPosts.posts[postId];
+ if (currentPosts.posts.hasOwnProperty(postId)) {
+ currentSelected.posts[postId] = currentPosts.posts[postId];
+ }
}
PostStore.storeSelectedPost(currentSelected);
@@ -92,14 +93,6 @@ export default class RhsThread extends React.Component {
this.setState(newState);
}
}
- onTimeChange() {
- for (var id in this.state.postList.posts) {
- if (!this.refs[id]) {
- continue;
- }
- this.refs[id].forceUpdate();
- }
- }
resize() {
var height = $(window).height() - $('#error_bar').outerHeight() - 100;
$('.post-right__scroll').css('height', height + 'px');
@@ -128,9 +121,11 @@ export default class RhsThread extends React.Component {
var postsArray = [];
for (var postId in postList.posts) {
- var cpost = postList.posts[postId];
- if (cpost.root_id === rootPost.id) {
- postsArray.push(cpost);
+ if (postList.posts.hasOwnProperty(postId)) {
+ var cpost = postList.posts[postId];
+ if (cpost.root_id === rootPost.id) {
+ postsArray.push(cpost);
+ }
}
}
@@ -167,8 +162,7 @@ export default class RhsThread extends React.Component {
return (
<div className='post-right__container'>
- <FileUploadOverlay
- overlayType='right' />
+ <FileUploadOverlay overlayType='right' />
<div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
<div className='sidebar-right__body'>
<RhsHeaderPost
@@ -185,7 +179,7 @@ export default class RhsThread extends React.Component {
return (
<Comment
ref={comPost.id}
- key={comPost.id}
+ key={comPost.id + 'commentKey'}
post={comPost}
selected={(comPost.id === selectedPost.id)}
/>
@@ -209,6 +203,7 @@ RhsThread.defaultProps = {
fromSearch: '',
isMentionSearch: false
};
+
RhsThread.propTypes = {
fromSearch: React.PropTypes.string,
isMentionSearch: React.PropTypes.bool
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index b11b39e9e..006d15459 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var PostStore = require('../stores/post_store.jsx');
@@ -10,36 +9,47 @@ var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-function getSearchTermStateFromStores() {
- var term = PostStore.getSearchTerm() || '';
- return {
- search_term: term
- };
-}
+export default class SearchBar extends React.Component {
+ constructor() {
+ super();
+ this.mounted = false;
-module.exports = React.createClass({
- displayName: 'SearchBar',
- componentDidMount: function() {
- PostStore.addSearchTermChangeListener(this._onChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSearchTermChangeListener(this._onChange);
- },
- _onChange: function(doSearch, isMentionSearch) {
- if (this.isMounted()) {
- var newState = getSearchTermStateFromStores();
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.handleUserInput = this.handleUserInput.bind(this);
+ this.performSearch = this.performSearch.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = this.getSearchTermStateFromStores();
+ }
+ getSearchTermStateFromStores() {
+ var term = PostStore.getSearchTerm() || '';
+ return {
+ searchTerm: term
+ };
+ }
+ componentDidMount() {
+ PostStore.addSearchTermChangeListener(this.onListenerChange);
+ this.mounted = true;
+ }
+ componentWillUnmount() {
+ PostStore.removeSearchTermChangeListener(this.onListenerChange);
+ this.mounted = false;
+ }
+ onListenerChange(doSearch, isMentionSearch) {
+ if (this.mounted) {
+ var newState = this.getSearchTermStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
if (doSearch) {
- this.performSearch(newState.search_term, isMentionSearch);
+ this.performSearch(newState.searchTerm, isMentionSearch);
}
}
- },
- clearFocus: function(e) {
+ }
+ clearFocus() {
$('.search-bar__container').removeClass('focused');
- },
- handleClose: function(e) {
+ }
+ handleClose(e) {
e.preventDefault();
AppDispatcher.handleServerAction({
@@ -58,23 +68,23 @@ module.exports = React.createClass({
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
- },
- handleUserInput: function(e) {
+ }
+ handleUserInput(e) {
var term = e.target.value;
PostStore.storeSearchTerm(term);
PostStore.emitSearchTermChange(false);
- this.setState({ search_term: term });
- },
- handleUserFocus: function(e) {
+ this.setState({searchTerm: term});
+ }
+ handleUserFocus(e) {
e.target.select();
$('.search-bar__container').addClass('focused');
- },
- performSearch: function(terms, isMentionSearch) {
+ }
+ performSearch(terms, isMentionSearch) {
if (terms.length) {
this.setState({isSearching: true});
client.search(
terms,
- function(data) {
+ function success(data) {
this.setState({isSearching: false});
if (utils.isMobile()) {
React.findDOMNode(this.refs.search).value = '';
@@ -86,38 +96,54 @@ module.exports = React.createClass({
is_mention_search: isMentionSearch
});
}.bind(this),
- function(err) {
+ function error(err) {
this.setState({isSearching: false});
- AsyncClient.dispatchError(err, "search");
+ AsyncClient.dispatchError(err, 'search');
}.bind(this)
);
}
- },
- handleSubmit: function(e) {
+ }
+ handleSubmit(e) {
e.preventDefault();
- this.performSearch(this.state.search_term.trim());
- },
- getInitialState: function() {
- return getSearchTermStateFromStores();
- },
- render: function() {
+ this.performSearch(this.state.searchTerm.trim());
+ }
+ render() {
+ var isSearching = null;
+ if (this.state.isSearching) {
+ isSearching = <span className={'glyphicon glyphicon-refresh glyphicon-refresh-animate'}></span>;
+ }
return (
<div>
- <div className="sidebar__collapse" onClick={this.handleClose}><span className="fa fa-angle-left"></span></div>
- <span onClick={this.clearFocus} className="search__clear">Cancel</span>
- <form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
- <span className="glyphicon glyphicon-search sidebar__search-icon"></span>
+ <div
+ className='sidebar__collapse'
+ onClick={this.handleClose}
+ >
+ <span className='fa fa-angle-left'></span>
+ </div>
+ <span
+ className='search__clear'
+ onClick={this.clearFocus}
+ >
+ Cancel
+ </span>
+ <form
+ role='form'
+ className='search__form relative-div'
+ onSubmit={this.handleSubmit}
+ >
+ <span className='glyphicon glyphicon-search sidebar__search-icon' />
<input
- type="text"
- ref="search"
- className="form-control search-bar"
- placeholder="Search"
- value={this.state.search_term}
+ type='text'
+ ref='search'
+ className='form-control search-bar'
+ placeholder='Search'
+ value={this.state.searchTerm}
onFocus={this.handleUserFocus}
- onChange={this.handleUserInput} />
- {this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null}
+ onChange={this.handleUserInput}
+ />
+ {isSearching}
</form>
</div>
);
}
-});
+}
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index b1efd7685..6ee817171 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -1,181 +1,109 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var PostStore = require('../stores/post_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
-var UserProfile = require( './user_profile.jsx' );
-var SearchBox =require('./search_bar.jsx');
+var SearchBox = require('./search_bar.jsx');
var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
-
-var RhsHeaderSearch = React.createClass({
- handleClose: function(e) {
- e.preventDefault();
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH,
- results: null
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH_TERM,
- term: null,
- do_search: false,
- is_mention_search: false
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POST_SELECTED,
- results: null
- });
- },
- render: function() {
- var title = this.props.isMentionSearch ? "Recent Mentions" : "Search Results";
- return (
- <div className="sidebar--right__header">
- <span className="sidebar--right__title">{title}</span>
- <button type="button" className="sidebar--right__close" aria-label="Close" title="Close" onClick={this.handleClose}></button>
- </div>
- );
- }
-});
-
-var SearchItem = React.createClass({
- handleClick: function(e) {
- e.preventDefault();
+var SearchResultsHeader = require('./search_results_header.jsx');
+var SearchResultsItem = require('./search_results_item.jsx');
- var self = this;
-
- client.getPost(
- this.props.post.channel_id,
- this.props.post.id,
- function(data) {
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_POST_SELECTED,
- post_list: data,
- from_search: PostStore.getSearchTerm()
- });
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH,
- results: null,
- is_mention_search: self.props.isMentionSearch
- });
- },
- function(err) {
- AsyncClient.dispatchError(err, "getPost");
- }
- );
-
- var postChannel = ChannelStore.get(this.props.post.channel_id);
- var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : "";
-
- utils.switchChannel(postChannel, teammate);
- },
+function getStateFromStores() {
+ return {results: PostStore.getSearchResults()};
+}
- render: function() {
+export default class SearchResults extends React.Component {
+ constructor(props) {
+ super(props);
- var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch});
- var channelName = "";
- var channel = ChannelStore.get(this.props.post.channel_id);
- var timestamp = UserStore.getCurrentUser().update_at;
+ this.mounted = false;
- if (channel) {
- channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
- }
+ this.onChange = this.onChange.bind(this);
+ this.resize = this.resize.bind(this);
- return (
- <div className="search-item-container post" onClick={this.handleClick}>
- <div className="search-channel__name">{ channelName }</div>
- <div className="post-profile-img__container">
- <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
- </div>
- <div className="post__content">
- <ul className="post-header">
- <li className="post-header-col"><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
- <li className="post-header-col">
- <time className="search-item-time">
- { utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at) }
- </time>
- </li>
- </ul>
- <div className="search-item-snippet"><span>{message}</span></div>
- </div>
- </div>
- );
+ this.state = getStateFromStores();
}
-});
-
-function getStateFromStores() {
- return { results: PostStore.getSearchResults() };
-}
-
-module.exports = React.createClass({
- displayName: 'SearchResults',
- componentDidMount: function() {
- PostStore.addSearchChangeListener(this._onChange);
+ componentDidMount() {
+ this.mounted = true;
+ PostStore.addSearchChangeListener(this.onChange);
this.resize();
var self = this;
- $(window).resize(function(){
+ $(window).resize(function resize() {
self.resize();
});
- },
- componentDidUpdate: function() {
+ }
+
+ componentDidUpdate() {
this.resize();
- },
- componentWillUnmount: function() {
- PostStore.removeSearchChangeListener(this._onChange);
- },
- _onChange: function() {
- if (this.isMounted()) {
+ }
+
+ componentWillUnmount() {
+ PostStore.removeSearchChangeListener(this.onChange);
+ this.mounted = false;
+ }
+
+ onChange() {
+ if (this.mounted) {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
}
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- resize: function() {
+ }
+
+ resize() {
var height = $(window).height() - $('#error_bar').outerHeight() - 100;
- $("#search-items-container").css("height", height + "px");
- $("#search-items-container").scrollTop(0);
- $("#search-items-container").perfectScrollbar();
- },
- render: function() {
+ $('#search-items-container').css('height', height + 'px');
+ $('#search-items-container').scrollTop(0);
+ $('#search-items-container').perfectScrollbar();
+ }
+ render() {
var results = this.state.results;
var currentId = UserStore.getCurrentId();
- var searchForm = currentId ? <SearchBox /> : null;
+ var searchForm = null;
+ if (currentId) {
+ searchForm = <SearchBox />;
+ }
var noResults = (!results || !results.order || !results.order.length);
var searchTerm = PostStore.getSearchTerm();
- return (
- <div className="sidebar--right__content">
- <div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
- <div className="sidebar-right__body">
- <RhsHeaderSearch isMentionSearch={this.props.isMentionSearch} />
- <div id="search-items-container" className="search-items-container">
-
- { noResults ? <div className="sidebar--right__subheader">No results</div>
- : results.order.map(function(id) {
- var post = results.posts[id];
- return <SearchItem key={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
- }, this)
- }
+ var ctls = null;
+
+ if (noResults) {
+ ctls = <div className='sidebar--right__subheader'>No results</div>;
+ } else {
+ ctls = results.order.map(function mymap(id) {
+ var post = results.posts[id];
+ return (
+ <SearchResultsItem
+ key={post.id}
+ post={post}
+ term={searchTerm}
+ isMentionSearch={this.props.isMentionSearch}
+ />
+ );
+ }, this);
+ }
+ return (
+ <div className='sidebar--right__content'>
+ <div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
+ <div className='sidebar-right__body'>
+ <SearchResultsHeader isMentionSearch={this.props.isMentionSearch} />
+ <div
+ id='search-items-container'
+ className='search-items-container'
+ >
+ {ctls}
</div>
</div>
</div>
);
}
-});
+}
+
+SearchResults.propTypes = {
+ isMentionSearch: React.PropTypes.bool
+};
diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx
new file mode 100644
index 000000000..694f0c55d
--- /dev/null
+++ b/web/react/components/search_results_header.jsx
@@ -0,0 +1,61 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+export default class SearchResultsHeader extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClose = this.handleClose.bind(this);
+ }
+
+ handleClose(e) {
+ e.preventDefault();
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH,
+ results: null
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH_TERM,
+ term: null,
+ do_search: false,
+ is_mention_search: false
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST_SELECTED,
+ results: null
+ });
+ }
+
+ render() {
+ var title = 'Search Results';
+
+ if (this.props.isMentionSearch) {
+ title = 'Recent Mentions';
+ }
+
+ return (
+ <div className='sidebar--right__header'>
+ <span className='sidebar--right__title'>{title}</span>
+ <button
+ type='button'
+ className='sidebar--right__close'
+ aria-label='Close'
+ title='Close'
+ onClick={this.handleClose}
+ >
+ </button>
+ </div>
+ );
+ }
+}
+
+SearchResultsHeader.propTypes = {
+ isMentionSearch: React.PropTypes.bool
+}; \ No newline at end of file
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
new file mode 100644
index 000000000..aa56f1174
--- /dev/null
+++ b/web/react/components/search_results_item.jsx
@@ -0,0 +1,105 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var PostStore = require('../stores/post_store.jsx');
+var ChannelStore = require('../stores/channel_store.jsx');
+var UserStore = require('../stores/user_store.jsx');
+var UserProfile = require('./user_profile.jsx');
+var utils = require('../utils/utils.jsx');
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+export default class SearchResultsItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick(e) {
+ e.preventDefault();
+
+ var self = this;
+
+ client.getPost(
+ this.props.post.channel_id,
+ this.props.post.id,
+ function success(data) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_POST_SELECTED,
+ post_list: data,
+ from_search: PostStore.getSearchTerm()
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_SEARCH,
+ results: null,
+ is_mention_search: self.props.isMentionSearch
+ });
+ },
+ function success(err) {
+ AsyncClient.dispatchError(err, 'getPost');
+ }
+ );
+
+ var postChannel = ChannelStore.get(this.props.post.channel_id);
+ var teammate = '';
+
+ if (postChannel.type === 'D') {
+ teammate = utils.getDirectTeammate(this.props.post.channel_id).username;
+ }
+
+ utils.switchChannel(postChannel, teammate);
+ }
+
+ render() {
+ var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch});
+ var channelName = '';
+ var channel = ChannelStore.get(this.props.post.channel_id);
+ var timestamp = UserStore.getCurrentUser().update_at;
+
+ if (channel) {
+ channelName = channel.display_name;
+ if (channel.type === 'D') {
+ channelName = 'Private Message';
+ }
+ }
+
+ return (
+ <div
+ className='search-item-container post'
+ onClick={this.handleClick}
+ >
+ <div className='search-channel__name'>{channelName}</div>
+ <div className='post-profile-img__container'>
+ <img
+ className='post-profile-img'
+ src={'/api/v1/users/' + this.props.post.user_id + '/image?time=' + timestamp}
+ height='36'
+ width='36'
+ />
+ </div>
+ <div className='post__content'>
+ <ul className='post-header'>
+ <li className='post-header-col'><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
+ <li className='post-header-col'>
+ <time className='search-item-time'>
+ {utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at)}
+ </time>
+ </li>
+ </ul>
+ <div className='search-item-snippet'><span>{message}</span></div>
+ </div>
+ </div>
+ );
+ }
+}
+
+SearchResultsItem.propTypes = {
+ post: React.PropTypes.object,
+ isMentionSearch: React.PropTypes.bool,
+ term: React.PropTypes.string
+}; \ No newline at end of file
diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx
index 1599041b0..b1bab1d48 100644
--- a/web/react/components/setting_item_max.jsx
+++ b/web/react/components/setting_item_max.jsx
@@ -1,31 +1,73 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- render: function() {
- var clientError = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null;
- var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null;
+export default class SettingItemMax extends React.Component {
+ render() {
+ var clientError = null;
+ if (this.props.client_error) {
+ clientError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.client_error}</label></div>);
+ }
+
+ var serverError = null;
+ if (this.props.server_error) {
+ serverError = (<div className='form-group'><label className='col-sm-12 has-error'>{this.props.server_error}</label></div>);
+ }
+
+ var extraInfo = null;
+ if (this.props.extraInfo) {
+ extraInfo = (<div className='setting-list__hint'>{this.props.extraInfo}</div>);
+ }
+
+ var submit = '';
+ if (this.props.submit) {
+ submit = (
+ <a
+ className='btn btn-sm btn-primary'
+ href='#'
+ onClick={this.props.submit}
+ >
+ Submit
+ </a>
+ );
+ }
var inputs = this.props.inputs;
return (
- <ul className="section-max form-horizontal">
- <li className="col-sm-12 section-title">{this.props.title}</li>
- <li className="col-sm-9 col-sm-offset-3">
- <ul className="setting-list">
- <li className="setting-list-item">
+ <ul className='section-max form-horizontal'>
+ <li className='col-sm-12 section-title'>{this.props.title}</li>
+ <li className='col-sm-9 col-sm-offset-3'>
+ <ul className='setting-list'>
+ <li className='setting-list-item'>
{inputs}
+ {extraInfo}
</li>
- <li className="setting-list-item">
+ <li className='setting-list-item'>
<hr />
- { server_error }
- { clientError }
- { this.props.submit ? <a className="btn btn-sm btn-primary" href="#" onClick={this.props.submit}>Submit</a> : "" }
- <a className="btn btn-sm theme" href="#" onClick={this.props.updateSection}>Cancel</a>
+ {serverError}
+ {clientError}
+ {submit}
+ <a
+ className='btn btn-sm theme'
+ href='#'
+ onClick={this.props.updateSection}
+ >
+ Cancel
+ </a>
</li>
</ul>
</li>
</ul>
);
}
-});
+}
+
+SettingItemMax.propTypes = {
+ inputs: React.PropTypes.array,
+ client_error: React.PropTypes.string,
+ server_error: React.PropTypes.string,
+ extraInfo: React.PropTypes.element,
+ updateSection: React.PropTypes.func,
+ submit: React.PropTypes.func,
+ title: React.PropTypes.string
+};
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 3c87e416e..098729a4f 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -1,19 +1,23 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: 'SettingsItemMin',
- propTypes: {
- title: React.PropTypes.string,
- disableOpen: React.PropTypes.bool,
- updateSection: React.PropTypes.func,
- describe: React.PropTypes.string
- },
- render: function() {
- var editButton = '';
+export default class SettingItemMin extends React.Component {
+ render() {
+ let editButton = null;
if (!this.props.disableOpen) {
- editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>;
+ editButton = (
+ <li className='col-sm-2 section-edit'>
+ <a
+ className='section-edit theme'
+ href='#'
+ onClick={this.props.updateSection}
+ >
+ Edit
+ </a>
+ </li>
+ );
}
+
return (
<ul className='section-min'>
<li className='col-sm-10 section-title'>{this.props.title}</li>
@@ -22,4 +26,11 @@ module.exports = React.createClass({
</ul>
);
}
-});
+}
+
+SettingItemMin.propTypes = {
+ title: React.PropTypes.string,
+ disableOpen: React.PropTypes.bool,
+ updateSection: React.PropTypes.func,
+ describe: React.PropTypes.string
+};
diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx
index 5b12ad7e9..a53112651 100644
--- a/web/react/components/setting_picture.jsx
+++ b/web/react/components/setting_picture.jsx
@@ -1,25 +1,35 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- setPicture: function(file) {
+import {config} from '../utils/config.js';
+
+export default class SettingPicture extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.setPicture = this.setPicture.bind(this);
+ }
+
+ setPicture(file) {
if (file) {
var reader = new FileReader();
- var img = this.refs.image.getDOMNode();
- reader.onload = function(e) {
+ var img = React.findDOMNode(this.refs.image);
+ reader.onload = function load(e) {
$(img).attr('src', e.target.result);
};
reader.readAsDataURL(file);
}
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+
+ componentWillReceiveProps(nextProps) {
if (nextProps.picture) {
this.setPicture(nextProps.picture);
}
- },
- render: function() {
+ }
+
+ render() {
var clientError = null;
if (this.props.client_error) {
clientError = <div className='form-group has-error'><label className='control-label'>{this.props.client_error}</label></div>;
@@ -31,14 +41,31 @@ module.exports = React.createClass({
var img = null;
if (this.props.picture) {
- img = (<img ref='image' className='profile-img' src=''/>);
+ img = (
+ <img
+ ref='image'
+ className='profile-img'
+ src=''
+ />
+ );
} else {
- img = (<img ref='image' className='profile-img' src={this.props.src}/>);
+ img = (
+ <img
+ ref='image'
+ className='profile-img'
+ src={this.props.src}
+ />
+ );
}
var confirmButton;
if (this.props.loadingPicture) {
- confirmButton = <img className='spinner' src='/static/images/load.gif'/>;
+ confirmButton = (
+ <img
+ className='spinner'
+ src='/static/images/load.gif'
+ />
+ );
} else {
var confirmButtonClass = 'btn btn-sm';
if (this.props.submitActive) {
@@ -46,9 +73,15 @@ module.exports = React.createClass({
} else {
confirmButtonClass += ' btn-inactive disabled';
}
- confirmButton = <a className={confirmButtonClass} onClick={this.props.submit}>Save</a>;
+
+ confirmButton = (
+ <a
+ className={confirmButtonClass}
+ onClick={this.props.submit}
+ >Save</a>
+ );
}
- var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + config.ProfileWidth + 'px in width and ' + config.ProfileHeight + 'px height.'
+ var helpText = 'Upload a profile picture in either JPG or PNG format, at least ' + config.ProfileWidth + 'px in width and ' + config.ProfileHeight + 'px height.';
var self = this;
return (
@@ -65,13 +98,37 @@ module.exports = React.createClass({
<li className='setting-list-item'>
{serverError}
{clientError}
- <span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span>
+ <span className='btn btn-sm btn-primary btn-file sel-btn'>
+ Select
+ <input
+ ref='input'
+ accept='.jpg,.png,.bmp'
+ type='file'
+ onChange={this.props.pictureChange}
+ />
+ </span>
{confirmButton}
- <a className='btn btn-sm theme' href='#' onClick={self.props.updateSection}>Cancel</a>
+ <a
+ className='btn btn-sm theme'
+ href='#'
+ onClick={self.props.updateSection}
+ >Cancel</a>
</li>
</ul>
</li>
</ul>
);
}
-});
+}
+
+SettingPicture.propTypes = {
+ client_error: React.PropTypes.string,
+ server_error: React.PropTypes.string,
+ src: React.PropTypes.string,
+ picture: React.PropTypes.object,
+ loadingPicture: React.PropTypes.bool,
+ submitActive: React.PropTypes.bool,
+ submit: React.PropTypes.func,
+ title: React.PropTypes.string,
+ pictureChange: React.PropTypes.func
+};
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index 596324308..5979091c4 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.jsx
@@ -1,53 +1,55 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-module.exports = React.createClass({
- displayName: 'Setting Upload',
- propTypes: {
- title: React.PropTypes.string.isRequired,
- submit: React.PropTypes.func.isRequired,
- fileTypesAccepted: React.PropTypes.string.isRequired,
- clientError: React.PropTypes.string,
- serverError: React.PropTypes.string,
- helpText: React.PropTypes.string
- },
- getInitialState: function() {
- return {
+export default class SettingsUpload extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.doFileSelect = this.doFileSelect.bind(this);
+ this.doSubmit = this.doSubmit.bind(this);
+ this.onFileSelect = this.onFileSelect.bind(this);
+
+ this.state = {
clientError: this.props.clientError,
serverError: this.props.serverError
};
- },
- componentWillReceiveProps: function() {
+ }
+
+ componentWillReceiveProps() {
this.setState({
clientError: this.props.clientError,
serverError: this.props.serverError
});
- },
- doFileSelect: function(e) {
+ }
+
+ doFileSelect(e) {
e.preventDefault();
this.setState({
clientError: '',
serverError: ''
});
- },
- doSubmit: function(e) {
+ }
+
+ doSubmit(e) {
e.preventDefault();
- var inputnode = this.refs.uploadinput.getDOMNode();
+ var inputnode = React.findDOMNode(this.refs.uploadinput);
if (inputnode.files && inputnode.files[0]) {
this.props.submit(inputnode.files[0]);
} else {
this.setState({clientError: 'No file selected.'});
}
- },
- onFileSelect: function(e) {
+ }
+
+ onFileSelect(e) {
var filename = $(e.target).val();
if (filename.substring(3, 11) === 'fakepath') {
filename = filename.substring(12);
}
$(e.target).closest('li').find('.file-status').addClass('hide');
$(e.target).closest('li').find('.file-name').removeClass('hide').html(filename);
- },
- render: function() {
+ }
+
+ render() {
var clientError = null;
if (this.state.clientError) {
clientError = (
@@ -67,10 +69,19 @@ module.exports = React.createClass({
<li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'>
<li className='setting-list-item'>
- <span className='btn btn-sm btn-primary btn-file sel-btn'>Select file<input ref='uploadinput' accept={this.props.fileTypesAccepted} type='file' onChange={this.onFileSelect}/></span>
+ <span className='btn btn-sm btn-primary btn-file sel-btn'>
+ Select file
+ <input
+ ref='uploadinput'
+ accept={this.props.fileTypesAccepted}
+ type='file'
+ onChange={this.onFileSelect}
+ />
+ </span>
<a
className={'btn btn-sm btn-primary'}
- onClick={this.doSubmit}>
+ onClick={this.doSubmit}
+ >
Import
</a>
<div className='file-status file-name hide'></div>
@@ -82,4 +93,13 @@ module.exports = React.createClass({
</ul>
);
}
-});
+}
+
+SettingsUpload.propTypes = {
+ title: React.PropTypes.string.isRequired,
+ submit: React.PropTypes.func.isRequired,
+ fileTypesAccepted: React.PropTypes.string.isRequired,
+ clientError: React.PropTypes.string,
+ serverError: React.PropTypes.string,
+ helpText: React.PropTypes.object
+};
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index d8091ec28..e5cbd6e92 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -1,24 +1,56 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
+export default class SettingsSidebar extends React.Component {
+ constructor(props) {
+ super(props);
-module.exports = React.createClass({
- displayName:'SettingsSidebar',
- updateTab: function(tab) {
- this.props.updateTab(tab);
+ this.handleClick = this.handleClick.bind(this);
+ }
+ handleClick(tab) {
+ this.props.updateTab(tab.name);
$('.settings-modal').addClass('display--content');
- },
- render: function() {
- var self = this;
+ }
+ render() {
+ let tabList = this.props.tabs.map(function makeTab(tab) {
+ let key = `${tab.name}_li`;
+ let className = '';
+ if (this.props.activeTab === tab.name) {
+ className = 'active';
+ }
+
+ return (
+ <li
+ key={key}
+ className={className}
+ >
+ <a
+ href='#'
+ onClick={this.handleClick.bind(null, tab)}
+ >
+ <i className={tab.icon} />
+ {tab.uiName}
+ </a>
+ </li>
+ );
+ }.bind(this));
+
return (
- <div className="">
- <ul className="nav nav-pills nav-stacked">
- {this.props.tabs.map(function(tab) {
- return <li key={tab.name+'_li'} className={self.props.activeTab == tab.name ? 'active' : ''}><a key={tab.name + '_a'} href="#" onClick={function(){self.updateTab(tab.name);}}><i key={tab.name+'_i'} className={tab.icon}></i>{tab.uiName}</a></li>
- })}
+ <div>
+ <ul className='nav nav-pills nav-stacked'>
+ {tabList}
</ul>
</div>
);
}
-});
+}
+
+SettingsSidebar.propTypes = {
+ tabs: React.PropTypes.arrayOf(React.PropTypes.shape({
+ name: React.PropTypes.string.isRequired,
+ uiName: React.PropTypes.string.isRequired,
+ icon: React.PropTypes.string.isRequired
+ })).isRequired,
+ activeTab: React.PropTypes.string,
+ updateTab: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 5b74165f3..983260187 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);
- };
- } 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;
- }
- );
- };
- }
+ this.setState({loadingDMChannel: index});
+
+ Client.createDirectChannel(channel, otherUserId,
+ function success(data) {
+ this.setState({loadingDMChannel: -1});
+ AsyncClient.getChannel(data.id);
+ Utils.switchChannel(data);
+ }.bind(this),
+ function error() {
+ this.setState({loadingDMChannel: -1});
+ window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }.bind(this)
+ );
+ }.bind(this);
}
-
- return (
- <li key={channel.name} ref={channel.name} className={linkClass}>
- <a className={'sidebar-channel ' + titleClass} href={href} onClick={handleClick}>
- {status}
- {channel.display_name}
- {badge}
- </a>
- </li>
- );
}
+ return (
+ <li
+ key={channel.name}
+ ref={channel.name}
+ className={linkClass}
+ >
+ <a
+ className={'sidebar-channel ' + titleClass}
+ href={href}
+ onClick={handleClick}
+ >
+ {status}
+ {channel.display_name}
+ {badge}
+ </a>
+ </li>
+ );
+ }
+ render() {
+ this.badgesActive = false;
+
+ // keep track of the first and last unread channels so we can use them to set the unread indicators
+ this.firstUnreadChannel = null;
+ this.lastUnreadChannel = null;
+
// create elements for all 3 types of channels
var channelItems = this.state.channels.filter(
function filterPublicChannels(channel) {
return channel.type === 'O';
}
- ).map(createChannelElement);
+ ).map(this.createChannelElement);
var privateChannelItems = this.state.channels.filter(
function filterPrivateChannels(channel) {
return channel.type === 'P';
}
- ).map(createChannelElement);
+ ).map(this.createChannelElement);
- var directMessageItems = this.state.showDirectChannels.map(createChannelElement);
+ var directMessageItems = this.state.showDirectChannels.map(this.createChannelElement);
// update the favicon to show if there are any notifications
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.id = 'favicon';
- if (badgesActive) {
+ if (this.badgesActive) {
link.href = '/static/images/redfavicon.ico';
} else {
link.href = '/static/images/favicon.ico';
@@ -438,7 +458,13 @@ module.exports = React.createClass({
if (this.state.hideDirectChannels.length > 0) {
directMessageMore = (
<li>
- <a href='#' data-toggle='modal' className='nav-more' data-target='#more_direct_channels' data-channels={JSON.stringify(this.state.hideDirectChannels)}>
+ <a
+ href='#'
+ data-toggle='modal'
+ className='nav-more'
+ data-target='#more_direct_channels'
+ data-channels={JSON.stringify(this.state.hideDirectChannels)}
+ >
{'More (' + this.state.hideDirectChannels.length + ')'}
</a>
</li>
@@ -447,21 +473,76 @@ module.exports = React.createClass({
return (
<div>
- <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} />
+ <SidebarHeader
+ teamDisplayName={this.props.teamDisplayName}
+ teamType={this.props.teamType}
+ />
<SearchBox />
- <div ref='topUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-top' style={{display: 'none'}}>Unread post(s) above</div>
- <div ref='bottomUnreadIndicator' className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom' style={{display: 'none'}}>Unread post(s) below</div>
+ <div
+ ref='topUnreadIndicator'
+ className='nav-pills__unread-indicator nav-pills__unread-indicator-top'
+ style={{display: 'none'}}
+ >
+ Unread post(s) above
+ </div>
+ <div
+ ref='bottomUnreadIndicator'
+ className='nav-pills__unread-indicator nav-pills__unread-indicator-bottom'
+ style={{display: 'none'}}
+ >
+ Unread post(s) below
+ </div>
- <div ref='container' className='nav-pills__container' onScroll={this.onScroll}>
+ <div
+ ref='container'
+ className='nav-pills__container'
+ onScroll={this.onScroll}
+ >
<ul className='nav nav-pills nav-stacked'>
- <li><h4>Channels<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='O'>+</a></h4></li>
+ <li>
+ <h4>
+ Channels
+ <a
+ className='add-channel-btn'
+ href='#'
+ data-toggle='modal'
+ data-target='#new_channel'
+ data-channeltype='O'
+ >
+ +
+ </a>
+ </h4>
+ </li>
{channelItems}
- <li><a href='#' data-toggle='modal' className='nav-more' data-target='#more_channels' data-channeltype='O'>More...</a></li>
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ className='nav-more'
+ data-target='#more_channels'
+ data-channeltype='O'
+ >
+ More...
+ </a>
+ </li>
</ul>
<ul className='nav nav-pills nav-stacked'>
- <li><h4>Private Groups<a className='add-channel-btn' href='#' data-toggle='modal' data-target='#new_channel' data-channeltype='P'>+</a></h4></li>
+ <li>
+ <h4>
+ Private Groups
+ <a
+ className='add-channel-btn'
+ href='#'
+ data-toggle='modal'
+ data-target='#new_channel'
+ data-channeltype='P'
+ >
+ +
+ </a>
+ </h4>
+ </li>
{privateChannelItems}
</ul>
<ul className='nav nav-pills nav-stacked'>
@@ -473,4 +554,13 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+Sidebar.defaultProps = {
+ teamType: '',
+ teamDisplayName: ''
+};
+Sidebar.propTypes = {
+ teamType: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index d5d16816f..0056d7a2f 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -1,133 +1,26 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var NavbarDropdown = require('./navbar_dropdown.jsx');
var UserStore = require('../stores/user_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
+import {config} from '../utils/config.js';
-var Constants = require('../utils/constants.jsx');
+export default class SidebarHeader extends React.Component {
+ constructor(props) {
+ super(props);
-function getStateFromStores() {
- return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()};
-}
-
-var NavbarDropdown = React.createClass({
- handleLogoutClick: function(e) {
- e.preventDefault();
- client.logout();
- },
- blockToggle: false,
- componentDidMount: function() {
- UserStore.addTeamsChangeListener(this.onListenerChange);
- TeamStore.addChangeListener(this.onListenerChange);
-
- var self = this;
- $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function() {
- self.blockToggle = true;
- setTimeout(function() {
- self.blockToggle = false;
- }, 100);
- });
- },
- componentWillUnmount: function() {
- UserStore.removeTeamsChangeListener(this.onListenerChange);
- TeamStore.removeChangeListener(this.onListenerChange);
-
- $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown');
- },
- onListenerChange: function() {
- if (this.isMounted()) {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- var teamLink = '';
- var inviteLink = '';
- var manageLink = '';
- var currentUser = UserStore.getCurrentUser();
- var isAdmin = false;
- var teamSettings = null;
-
- if (currentUser != null) {
- isAdmin = currentUser.roles.indexOf('admin') > -1;
-
- inviteLink = (<li> <a href='#' data-toggle='modal' data-target='#invite_member'>Invite New Member</a> </li>);
-
- if (this.props.teamType === 'O') {
- teamLink = (
- <li>
- <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}>Get Team Invite Link</a>
- </li>
- );
- }
- }
-
- if (isAdmin) {
- manageLink = (<li> <a href='#' data-toggle='modal' data-target='#team_members'>Manage Team</a> </li>);
- teamSettings = (<li> <a href='#' data-toggle='modal' data-target='#team_settings'>Team Settings</a> </li>);
- }
+ this.toggleDropdown = this.toggleDropdown.bind(this);
- var teams = [];
-
- teams.push(<li className='divider' key='div'></li>);
- if (this.state.teams.length > 1 && this.state.currentTeam) {
- var curTeamName = this.state.currentTeam.name;
- this.state.teams.forEach(function(teamName) {
- if (teamName !== curTeamName) {
- teams.push(<li key={teamName}><a href={utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>);
- }
- });
- }
- teams.push(<li key='newTeam_li'><a key='newTeam_a' href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>);
-
- return (
- <ul className='nav navbar-nav navbar-right'>
- <li ref='dropdown' className='dropdown'>
- <a href='#' className='dropdown-toggle' data-toggle='dropdown' role='button' aria-expanded='false'>
- <span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
- </a>
- <ul className='dropdown-menu' role='menu'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li>
- {teamSettings}
- {inviteLink}
- {teamLink}
- {manageLink}
- <li><a href='#' onClick={this.handleLogoutClick}>Logout</a></li>
- {teams}
- <li className='divider'></li>
- <li><a target='_blank' href={config.HelpLink}>Help</a></li>
- <li><a target='_blank' href={config.ReportProblemLink}>Report a Problem</a></li>
- </ul>
- </li>
- </ul>
- );
+ this.state = {};
}
-});
-
-module.exports = React.createClass({
- displayName: 'SidebarHeader',
- getDefaultProps: function() {
- return {
- teamDisplayName: config.SiteName
- };
- },
-
- toggleDropdown: function() {
+ toggleDropdown() {
if (this.refs.dropdown.blockToggle) {
this.refs.dropdown.blockToggle = false;
return;
}
$('.team__header').find('.dropdown-toggle').dropdown('toggle');
- },
-
- render: function() {
+ }
+ render() {
var me = UserStore.getCurrentUser();
var profilePicture = null;
@@ -136,20 +29,40 @@ module.exports = React.createClass({
}
if (me.last_picture_update) {
- profilePicture = (<img className='user__picture' src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} />);
+ profilePicture = (
+ <img
+ className='user__picture'
+ src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
+ />
+ );
}
return (
<div className='team__header theme'>
- <a href='#' onClick={this.toggleDropdown}>
+ <a
+ href='#'
+ onClick={this.toggleDropdown}
+ >
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
- <div className='team__name'>{this.props.teamDisplayName }</div>
+ <div className='team__name'>{this.props.teamDisplayName}</div>
</div>
</a>
- <NavbarDropdown ref='dropdown' teamType={this.props.teamType} />
+ <NavbarDropdown
+ ref='dropdown'
+ teamType={this.props.teamType}
+ />
</div>
);
}
-});
+}
+
+SidebarHeader.defaultProps = {
+ teamDisplayName: config.SiteName,
+ teamType: ''
+};
+SidebarHeader.propTypes = {
+ teamDisplayName: React.PropTypes.string,
+ teamType: React.PropTypes.string
+};
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index df75e3adf..913715154 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -4,47 +4,49 @@
var SearchResults = require('./search_results.jsx');
var RhsThread = require('./rhs_thread.jsx');
var PostStore = require('../stores/post_store.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-function getStateFromStores(from_search) {
- return { search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch() };
+function getStateFromStores() {
+ return {search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch()};
}
-module.exports = React.createClass({
- componentDidMount: function() {
- PostStore.addSearchChangeListener(this._onSearchChange);
- PostStore.addSelectedPostChangeListener(this._onSelectedChange);
- },
- componentWillUnmount: function() {
- PostStore.removeSearchChangeListener(this._onSearchChange);
- PostStore.removeSelectedPostChangeListener(this._onSelectedChange);
- },
- _onSelectedChange: function(from_search) {
- if (this.isMounted()) {
- var newState = getStateFromStores(from_search);
- newState.from_search = from_search;
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
+export default class SidebarRight extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onSelectedChange = this.onSelectedChange.bind(this);
+ this.onSearchChange = this.onSearchChange.bind(this);
+ this.resize = this.resize.bind(this);
+
+ this.state = getStateFromStores();
+ }
+ componentDidMount() {
+ PostStore.addSearchChangeListener(this.onSearchChange);
+ PostStore.addSelectedPostChangeListener(this.onSelectedChange);
+ }
+ componentWillUnmount() {
+ PostStore.removeSearchChangeListener(this.onSearchChange);
+ PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
+ }
+ onSelectedChange(fromSearch) {
+ var newState = getStateFromStores(fromSearch);
+ newState.from_search = fromSearch;
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
}
- },
- _onSearchChange: function() {
- if (this.isMounted()) {
- var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
+ }
+ onSearchChange() {
+ var newState = getStateFromStores();
+ if (!Utils.areStatesEqual(newState, this.state)) {
+ this.setState(newState);
}
- },
- resize: function() {
+ }
+ resize() {
var postHolder = $('.post-list-holder-by-time');
postHolder[0].scrollTop = postHolder[0].scrollHeight - 224;
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- if (! (this.state.search_visible || this.state.post_right_visible)) {
+ }
+ render() {
+ if (!(this.state.search_visible || this.state.post_right_visible)) {
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
this.resize();
@@ -58,25 +60,29 @@ module.exports = React.createClass({
$('.sidebar--right').addClass('move--left');
$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
this.resize();
- setTimeout(function(){
- $('.sidebar__overlay').fadeOut("200", function(){
+ setTimeout(function overlayTimer() {
+ $('.sidebar__overlay').fadeOut('200', function fadeOverlay() {
$(this).remove();
});
- },500)
+ }, 500);
- var content = "";
+ var content = '';
if (this.state.search_visible) {
content = <SearchResults isMentionSearch={this.state.is_mention_search} />;
- }
- else if (this.state.post_right_visible) {
- content = <RhsThread fromSearch={this.state.from_search} isMentionSearch={this.state.is_mention_search} />;
+ } else if (this.state.post_right_visible) {
+ content = (
+ <RhsThread
+ fromSearch={this.state.from_search}
+ isMentionSearch={this.state.is_mention_search}
+ />
+ );
}
return (
- <div className="sidebar-right-container">
- { content }
+ <div className='sidebar-right-container'>
+ {content}
</div>
);
}
-});
+}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 615bc4ef2..bd10a6ef1 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -4,13 +4,21 @@
var UserStore = require('../stores/user_store.jsx');
var client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx');
+import {config} from '../utils/config.js';
-module.exports = React.createClass({
- handleLogoutClick: function(e) {
+export default class SidebarRightMenu extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleLogoutClick = this.handleLogoutClick.bind(this);
+ }
+
+ handleLogoutClick(e) {
e.preventDefault();
client.logout();
- },
- render: function() {
+ }
+
+ render() {
var teamLink = '';
var inviteLink = '';
var teamSettingsLink = '';
@@ -23,14 +31,22 @@ module.exports = React.createClass({
inviteLink = (
<li>
- <a href='#' data-toggle='modal' data-target='#invite_member'><i className='glyphicon glyphicon-user'></i>Invite New Member</a>
+ <a href='#'
+ data-toggle='modal'
+ data-target='#invite_member'
+ ><i className='glyphicon glyphicon-user'></i>Invite New Member</a>
</li>
);
if (this.props.teamType === 'O') {
teamLink = (
<li>
- <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin()+'/signup_user_complete/?id='+currentUser.team_id}><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
+ <a href='#'
+ data-toggle='modal'
+ data-target='#get_link'
+ data-title='Team Invite'
+ data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}
+ ><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
</li>
);
}
@@ -39,12 +55,21 @@ module.exports = React.createClass({
if (isAdmin) {
teamSettingsLink = (
<li>
- <a href='#' data-toggle='modal' data-target='#team_settings'><i className='glyphicon glyphicon-globe'></i>Team Settings</a>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_settings'
+ ><i className='glyphicon glyphicon-globe'></i>Team Settings</a>
</li>
);
manageLink = (
<li>
- <a href='#' data-toggle='modal' data-target='#team_members'><i className='glyphicon glyphicon-wrench'></i>Manage Team</a>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#team_members'
+ >
+ <i className='glyphicon glyphicon-wrench'></i>Manage Team</a>
</li>
);
}
@@ -61,23 +86,48 @@ module.exports = React.createClass({
return (
<div>
<div className='team__header theme'>
- <a className='team__name' href='/channels/town-square'>{teamDisplayName}</a>
+ <a
+ className='team__name'
+ href='/channels/town-square'
+ >{teamDisplayName}</a>
</div>
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ <li>
+ <a
+ href='#'
+ data-toggle='modal'
+ data-target='#user_settings'
+ ><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
{teamSettingsLink}
{inviteLink}
{teamLink}
{manageLink}
- <li><a href='#' onClick={this.handleLogoutClick}><i className='glyphicon glyphicon-log-out'></i>Logout</a></li>
+ <li>
+ <a
+ href='#'
+ onClick={this.handleLogoutClick}
+ ><i className='glyphicon glyphicon-log-out'></i>Logout</a></li>
<li className='divider'></li>
- <li><a target='_blank' href='/static/help/configure_links.html'><i className='glyphicon glyphicon-question-sign'></i>Help</a></li>
- <li><a target='_blank' href='/static/help/configure_links.html'><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
+ <li>
+ <a
+ target='_blank'
+ href='/static/help/configure_links.html'
+ ><i className='glyphicon glyphicon-question-sign'></i>Help</a></li>
+ <li>
+ <a
+ target='_blank'
+ href='/static/help/configure_links.html'
+ ><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
</ul>
</div>
</div>
);
}
-});
+}
+
+SidebarRightMenu.propTypes = {
+ teamType: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 13640b1e5..bf08e6508 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -1,10 +1,10 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChoosePage = require('./team_signup_choose_auth.jsx');
-var EmailSignUpPage = require('./team_signup_with_email.jsx');
-var SSOSignupPage = require('./team_signup_with_sso.jsx');
-var Constants = require('../utils/constants.jsx');
+const ChoosePage = require('./team_signup_choose_auth.jsx');
+const EmailSignUpPage = require('./team_signup_with_email.jsx');
+const SSOSignupPage = require('./team_signup_with_sso.jsx');
+const Constants = require('../utils/constants.jsx');
export default class TeamSignUp extends React.Component {
constructor(props) {
@@ -30,14 +30,14 @@ export default class TeamSignUp extends React.Component {
return <EmailSignUpPage />;
} else if (this.state.page === 'service' && this.state.service !== '') {
return <SSOSignupPage service={this.state.service} />;
- } else {
- return (
- <ChoosePage
- services={this.props.services}
- updatePage={this.updatePage}
- />
- );
}
+
+ return (
+ <ChoosePage
+ services={this.props.services}
+ updatePage={this.updatePage}
+ />
+ );
}
}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 756aae638..dc0d1d376 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -10,69 +10,113 @@ var UsernamePage = require('./team_signup_username_page.jsx');
var PasswordPage = require('./team_signup_password_page.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
-module.exports = React.createClass({
- displayName: 'SignupTeamComplete',
- propTypes: {
- hash: React.PropTypes.string,
- email: React.PropTypes.string,
- data: React.PropTypes.string
- },
- updateParent: function(state, skipSet) {
+export default class SignupTeamComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateParent = this.updateParent.bind(this);
+
+ var initialState = BrowserStore.getGlobalItem(props.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.team = {};
+ initialState.team.email = this.props.email;
+ initialState.team.allowed_domains = '';
+ initialState.invites = [];
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.user = {};
+ initialState.hash = this.props.hash;
+ initialState.data = this.props.data;
+ }
+
+ this.state = initialState;
+ }
+ updateParent(state, skipSet) {
BrowserStore.setGlobalItem(this.props.hash, state);
if (!skipSet) {
this.setState(state);
}
- },
- getInitialState: function() {
- var props = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!props) {
- props = {};
- props.wizard = 'welcome';
- props.team = {};
- props.team.email = this.props.email;
- props.team.allowed_domains = '';
- props.invites = [];
- props.invites.push('');
- props.invites.push('');
- props.invites.push('');
- props.user = {};
- props.hash = this.props.hash;
- props.data = this.props.data;
- }
-
- return props;
- },
- render: function() {
+ }
+ render() {
if (this.state.wizard === 'welcome') {
- return <WelcomePage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <WelcomePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'team_display_name') {
- return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <TeamDisplayNamePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'team_url') {
- return <TeamURLPage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <TeamURLPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'allowed_domains') {
- return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <AllowedDomainsPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'send_invites') {
- return <SendInivtesPage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <SendInivtesPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'username') {
- return <UsernamePage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <UsernamePage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
if (this.state.wizard === 'password') {
- return <PasswordPage state={this.state} updateParent={this.updateParent} />;
+ return (
+ <PasswordPage
+ state={this.state}
+ updateParent={this.updateParent}
+ />
+ );
}
return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>);
}
-});
+}
+
+SignupTeamComplete.defaultProps = {
+ hash: '',
+ email: '',
+ data: ''
+};
+SignupTeamComplete.propTypes = {
+ hash: React.PropTypes.string,
+ email: React.PropTypes.string,
+ data: React.PropTypes.string
+};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 2080cc191..f078f6169 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -6,12 +6,33 @@ var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
var Constants = require('../utils/constants.jsx');
+import {config} from '../utils/config.js';
-module.exports = React.createClass({
- handleSubmit: function(e) {
+export default class SignupUserComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ var initialState = BrowserStore.getGlobalItem(this.props.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.user = {};
+ initialState.user.team_id = this.props.teamId;
+ initialState.user.email = this.props.email;
+ initialState.hash = this.props.hash;
+ initialState.data = this.props.data;
+ initialState.original_email = this.props.email;
+ }
+
+ this.state = initialState;
+ }
+ handleSubmit(e) {
e.preventDefault();
- this.state.user.username = this.refs.name.getDOMNode().value.trim();
+ this.state.user.username = React.findDOMNode(this.refs.name).value.trim();
if (!this.state.user.username) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return;
@@ -31,14 +52,14 @@ module.exports = React.createClass({
return;
}
- this.state.user.email = this.refs.email.getDOMNode().value.trim();
+ this.state.user.email = React.findDOMNode(this.refs.email).value.trim();
if (!this.state.user.email) {
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
return;
}
- this.state.user.password = this.refs.password.getDOMNode().value.trim();
- if (!this.state.user.password || this.state.user.password .length < 5) {
+ this.state.user.password = React.findDOMNode(this.refs.password).value.trim();
+ if (!this.state.user.password || this.state.user.password .length < 5) {
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});
return;
}
@@ -48,11 +69,11 @@ module.exports = React.createClass({
this.state.user.allow_marketing = true;
client.createUser(this.state.user, this.state.data, this.state.hash,
- function(data) {
+ function createUserSuccess() {
client.track('signup', 'signup_user_02_complete');
client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password,
- function(data) {
+ function emailLoginSuccess(data) {
UserStore.setLastEmail(this.state.user.email);
UserStore.setCurrentUser(data);
if (this.props.hash > 0) {
@@ -60,7 +81,7 @@ module.exports = React.createClass({
}
window.location.href = '/';
}.bind(this),
- function(err) {
+ function emailLoginFailure(err) {
if (err.message === 'Login failed because email address has not been verified') {
window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName);
} else {
@@ -69,28 +90,12 @@ module.exports = React.createClass({
}.bind(this)
);
}.bind(this),
- function(err) {
+ function createUserFailure(err) {
this.setState({serverError: err.message});
}.bind(this)
);
- },
- getInitialState: function() {
- var state = BrowserStore.getGlobalItem(this.props.hash);
-
- if (!state) {
- state = {};
- state.wizard = 'welcome';
- state.user = {};
- state.user.team_id = this.props.teamId;
- state.user.email = this.props.email;
- state.hash = this.props.hash;
- state.data = this.props.data;
- state.original_email = this.props.email;
- }
-
- return state;
- },
- render: function() {
+ }
+ render() {
client.track('signup', 'signup_user_01_welcome');
if (this.state.wizard === 'finished') {
@@ -134,16 +139,24 @@ module.exports = React.createClass({
yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>;
}
- var emailContainerStyle = "margin--extra";
+ var emailContainerStyle = 'margin--extra';
if (this.state.original_email) {
- emailContainerStyle = "hidden";
+ emailContainerStyle = 'hidden';
}
var email = (
<div className={emailContainerStyle}>
<h5><strong>What's your email address?</strong></h5>
<div className={emailDivStyle}>
- <input type='email' ref='email' className='form-control' defaultValue={this.state.user.email} placeholder='' maxLength='128' autoFocus={true} />
+ <input
+ type='email'
+ ref='email'
+ className='form-control'
+ defaultValue={this.state.user.email}
+ placeholder=''
+ maxLength='128'
+ autoFocus={true}
+ />
{emailError}
</div>
</div>
@@ -155,7 +168,10 @@ module.exports = React.createClass({
var signupMessage = [];
if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) {
signupMessage.push(
- <a className='btn btn-custom-login gitlab' href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}>
+ <a
+ className='btn btn-custom-login gitlab'
+ href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
+ >
<span className='icon' />
<span>with GitLab</span>
</a>
@@ -172,7 +188,13 @@ module.exports = React.createClass({
<div className='margin--extra'>
<h5><strong>Choose your username</strong></h5>
<div className={nameDivStyle}>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' />
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ />
{nameError}
<p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p>
</div>
@@ -180,12 +202,26 @@ module.exports = React.createClass({
<div className='margin--extra'>
<h5><strong>Choose your password</strong></h5>
<div className={passwordDivStyle}>
- <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
+ <input
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ />
{passwordError}
</div>
</div>
</div>
- <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p>
+ <p className='margin--extra'>
+ <button
+ type='submit'
+ onClick={this.handleSubmit}
+ className='btn-primary btn'
+ >
+ Create Account
+ </button>
+ </p>
</div>
);
}
@@ -209,7 +245,10 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h5 className='margin--less'>Welcome to:</h5>
<h2 className='signup-team__name'>{this.props.teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>on {config.SiteName}</h2>
@@ -222,6 +261,23 @@ module.exports = React.createClass({
</div>
);
}
-});
-
+}
+SignupUserComplete.defaultProps = {
+ teamName: '',
+ hash: '',
+ teamId: '',
+ email: '',
+ data: null,
+ authServices: '',
+ teamDisplayName: ''
+};
+SignupUserComplete.propTypes = {
+ teamName: React.PropTypes.string,
+ hash: React.PropTypes.string,
+ teamId: React.PropTypes.string,
+ email: React.PropTypes.string,
+ data: React.PropTypes.string,
+ authServices: React.PropTypes.string,
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/team_feature_tab.jsx b/web/react/components/team_feature_tab.jsx
index 4f8f0b2cf..3251746b8 100644
--- a/web/react/components/team_feature_tab.jsx
+++ b/web/react/components/team_feature_tab.jsx
@@ -4,65 +4,71 @@
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.setupInitialState = this.setupInitialState.bind(this);
+
+ this.state = this.setupInitialState();
+ }
+ 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) {
- var state = this.getInitialState();
+ function fail(err) {
+ var state = this.setupInitialState();
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';
+ React.findDOMNode(this.refs.wrapper).focus();
+ }
+ onUpdateSection(e) {
+ e.preventDefault();
+ if (this.props.activeSection === 'valet') {
+ this.props.updateSection('');
+ } else {
+ this.props.updateSection('valet');
}
-
- this.setState({allowValet: allowValet});
- },
- getInitialState: function() {
+ }
+ setupInitialState() {
+ var allowValet;
var team = this.props.team;
- var allowValet = 'false';
if (team && team.allow_valet) {
allowValet = 'true';
+ } else {
+ allowValet = 'false';
}
return {allowValet: allowValet};
- },
- onUpdateSection: function(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 +79,6 @@ module.exports = React.createClass({
}
var valetSection;
- var self = this;
if (this.props.activeSection === 'valet') {
var valetActive = [false, false];
@@ -92,7 +97,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 +109,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 +150,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 +177,14 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+FeatureTab.defaultProps = {
+ team: {},
+ activeSection: ''
+};
+FeatureTab.propTypes = {
+ updateSection: React.PropTypes.func.isRequired,
+ team: React.PropTypes.object.isRequired,
+ activeSection: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index fd2a22731..25139bb95 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -1,11 +1,12 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingItemMin = require('./setting_item_min.jsx');
-var SettingItemMax = require('./setting_item_max.jsx');
+const SettingItemMin = require('./setting_item_min.jsx');
+const SettingItemMax = require('./setting_item_max.jsx');
-var client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+import {strings} from '../utils/config.js';
export default class GeneralTab extends React.Component {
constructor(props) {
@@ -21,10 +22,10 @@ export default class GeneralTab extends React.Component {
handleNameSubmit(e) {
e.preventDefault();
- var state = {serverError: '', clientError: ''};
- var valid = true;
+ let state = {serverError: '', clientError: ''};
+ let valid = true;
- var name = this.state.name.trim();
+ const name = this.state.name.trim();
if (!name) {
state.clientError = 'This field is required';
valid = false;
@@ -41,10 +42,10 @@ export default class GeneralTab extends React.Component {
return;
}
- var data = {};
+ let data = {};
data.new_name = name;
- client.updateTeamDisplayName(data,
+ Client.updateTeamDisplayName(data,
function nameChangeSuccess() {
this.props.updateSection('');
$('#team_settings').modal('hide');
@@ -84,8 +85,8 @@ export default class GeneralTab extends React.Component {
this.setState({name: e.target.value});
}
render() {
- var clientError = null;
- var serverError = null;
+ let clientError = null;
+ let serverError = null;
if (this.state.clientError) {
clientError = this.state.clientError;
}
@@ -93,18 +94,21 @@ export default class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
- var nameSection;
+ let nameSection;
if (this.props.activeSection === 'name') {
let inputs = [];
- let teamNameLabel = utils.toTitleCase(strings.Team) + ' Name';
- if (utils.isMobile()) {
+ let teamNameLabel = Utils.toTitleCase(strings.Team) + ' Name';
+ if (Utils.isMobile()) {
teamNameLabel = '';
}
inputs.push(
- <div key='teamNameSetting' className='form-group'>
+ <div
+ key='teamNameSetting'
+ className='form-group'
+ >
<label className='col-sm-5 control-label'>{teamNameLabel}</label>
<div className='col-sm-7'>
<input
@@ -119,7 +123,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMax
- title={utils.toTitleCase(strings.Team) + ' Name'}
+ title={`${Utils.toTitleCase(strings.Team)} Name`}
inputs={inputs}
submit={this.handleNameSubmit}
server_error={serverError}
@@ -132,7 +136,7 @@ export default class GeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title={utils.toTitleCase(strings.Team) + ' Name'}
+ title={`${Utils.toTitleCase(strings.Team)} Name`}
describe={describe}
updateSection={this.onUpdateSection}
/>
diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx
index e3415d7f4..1ab348465 100644
--- a/web/react/components/team_import_tab.jsx
+++ b/web/react/components/team_import_tab.jsx
@@ -4,26 +4,38 @@
var utils = require('../utils/utils.jsx');
var SettingUpload = require('./setting_upload.jsx');
-module.exports = React.createClass({
- displayName: 'Import Tab',
- getInitialState: function() {
- return {status: 'ready', link: ''};
- },
- onImportFailure: function() {
+export default class TeamImportTab extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onImportFailure = this.onImportFailure.bind(this);
+ this.onImportSuccess = this.onImportSuccess.bind(this);
+ this.doImportSlack = this.doImportSlack.bind(this);
+
+ this.state = {
+ status: 'ready',
+ link: ''
+ };
+ }
+
+ onImportFailure() {
this.setState({status: 'fail', link: ''});
- },
- onImportSuccess: function(data) {
+ }
+
+ onImportSuccess(data) {
this.setState({status: 'done', link: 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(data)});
- },
- doImportSlack: function(file) {
+ }
+
+ doImportSlack(file) {
this.setState({status: 'in-progress', link: ''});
utils.importSlack(file, this.onImportSuccess, this.onImportFailure);
- },
- render: function() {
+ }
+
+ render() {
var uploadHelpText = (
<div>
<br/>
- Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.
+ Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.
<br/><br/>
The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.
<br/><br/>
@@ -34,27 +46,47 @@ module.exports = React.createClass({
title='Import from Slack'
submit={this.doImportSlack}
helpText={uploadHelpText}
- fileTypesAccepted='.zip'/>
+ fileTypesAccepted='.zip'
+ />
);
var messageSection;
switch (this.state.status) {
- case 'ready':
- messageSection = '';
+
+ case 'ready':
+ messageSection = '';
break;
- case 'in-progress':
- messageSection = (
- <p className="confirm-import alert alert-warning"><i className="fa fa-spinner fa-pulse"></i> Importing...</p>
+ case 'in-progress':
+ messageSection = (
+ <p className='confirm-import alert alert-warning'><i className='fa fa-spinner fa-pulse'></i> Importing...</p>
);
break;
- case 'done':
- messageSection = (
- <p className="confirm-import alert alert-success"><i className="fa fa-check"></i> Import successful: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p>
- );
+ case 'done':
+ messageSection = (
+ <p className='confirm-import alert alert-success'>
+ <i className='fa fa-check' />
+ {' Import successful: '}
+ <a
+ href={this.state.link}
+ download='MattermostImportSummary.txt'
+ >
+ {'View Summary'}
+ </a>
+ </p>
+ );
break;
- case 'fail':
- messageSection = (
- <p className="confirm-import alert alert-warning"><i className="fa fa-warning"></i> Import failure: <a href={this.state.link} download='MattermostImportSummary.txt'>View Summary</a></p>
+ case 'fail':
+ messageSection = (
+ <p className='confirm-import alert alert-warning'>
+ <i className='fa fa-warning' />
+ {' Import failure: '}
+ <a
+ href={this.state.link}
+ download='MattermostImportSummary.txt'
+ >
+ {'View Summary'}
+ </a>
+ </p>
);
break;
}
@@ -62,10 +94,22 @@ 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>Import</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>Import</h4>
</div>
- <div ref='wrapper' className='user-settings'>
+ <div
+ ref='wrapper'
+ className='user-settings'
+ >
<h3 className='tab-header'>Import</h3>
<div className='divider-dark first'/>
{uploadSection}
@@ -75,4 +119,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index 616fd2c99..0cd22437e 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -2,77 +2,131 @@
// See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
-var ChannelStore = require('../stores/channel_store.jsx');
var MemberListTeam = require('./member_list_team.jsx');
-var Client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx');
function getStateFromStores() {
var users = UserStore.getProfiles();
- var member_list = [];
- for (var id in users) member_list.push(users[id]);
+ var memberList = [];
+ for (var id in users) {
+ if (users.hasOwnProperty(id)) {
+ memberList.push(users[id]);
+ }
+ }
+
+ memberList.sort(function sort(a, b) {
+ if (a.username < b.username) {
+ return -1;
+ }
+
+ if (a.username > b.username) {
+ return 1;
+ }
- member_list.sort(function(a,b) {
- if (a.username < b.username) return -1;
- if (a.username > b.username) return 1;
return 0;
});
return {
- member_list: member_list
+ member_list: memberList
};
}
-module.exports = React.createClass({
- componentDidMount: function() {
- UserStore.addChangeListener(this._onChange);
+export default class TeamMembers extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+
+ this.state = getStateFromStores();
+ }
+
+ componentDidMount() {
+ UserStore.addChangeListener(this.onChange);
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ render_members: false });
+ $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', function show() {
+ self.setState({render_members: false});
});
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
- self.setState({ render_members: true });
+ $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function hide() {
+ self.setState({render_members: true});
});
- },
- componentWillUnmount: function() {
- UserStore.removeChangeListener(this._onChange);
- },
- _onChange: function() {
+ }
+
+ componentWillUnmount() {
+ UserStore.removeChangeListener(this.onChange);
+ }
+
+ onChange() {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
this.setState(newState);
}
- },
- getInitialState: function() {
- return getStateFromStores();
- },
- render: function() {
- var server_error = this.state.server_error ? <label className='has-error control-label'>{this.state.server_error}</label> : null;
+ }
+
+ render() {
+ var serverError = null;
+
+ if (this.state.server_error) {
+ serverError = <label className='has-error control-label'>{this.state.server_error}</label>;
+ }
+
+ var renderMembers = '';
+
+ if (this.state.render_members) {
+ renderMembers = <MemberListTeam users={this.state.member_list} />;
+ }
return (
- <div className="modal fade" ref="modal" id="team_members" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close" data-reactid=".5.0.0.0.0"><span aria-hidden="true" data-reactid=".5.0.0.0.0.0">×</span></button>
- <h4 className="modal-title" id="myModalLabel">{this.props.teamDisplayName + " Members"}</h4>
- </div>
- <div ref="modalBody" className="modal-body">
- <div className="channel-settings">
- <div className="team-member-list">
- { this.state.render_members ? <MemberListTeam users={this.state.member_list} /> : "" }
+ <div
+ className='modal fade'
+ ref='modal'
+ id='team_members'
+ tabIndex='-1'
+ role='dialog'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>×</span>
+ </button>
+ <h4
+ className='modal-title'
+ id='myModalLabel'
+ >{this.props.teamDisplayName + ' Members'}</h4>
+ </div>
+ <div
+ ref='modalBody'
+ className='modal-body'
+ >
+ <div className='channel-settings'>
+ <div className='team-member-list'>
+ {renderMembers}
+ </div>
+ {serverError}
</div>
- { server_error }
+ </div>
+ <div className='modal-footer'>
+ <button
+ type='button'
+ className='btn btn-default'
+ data-dismiss='modal'
+ >Close</button>
</div>
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
- </div>
- </div>
</div>
</div>
);
}
-});
+}
+
+TeamMembers.propTypes = {
+ teamDisplayName: React.PropTypes.string
+};
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 1a79eef1d..53855fe1c 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -5,66 +5,83 @@ var TeamStore = require('../stores/team_store.jsx');
var ImportTab = require('./team_import_tab.jsx');
var FeatureTab = require('./team_feature_tab.jsx');
var GeneralTab = require('./team_general_tab.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
-module.exports = React.createClass({
- displayName: 'Team Settings',
- propTypes: {
- activeTab: React.PropTypes.string.isRequired,
- activeSection: React.PropTypes.string.isRequired,
- updateSection: React.PropTypes.func.isRequired,
- teamDisplayName: React.PropTypes.string.isRequired
- },
- componentDidMount: function() {
+export default class TeamSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+
+ this.state = {team: TeamStore.getCurrent()};
+ }
+ componentDidMount() {
TeamStore.addChangeListener(this.onChange);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
TeamStore.removeChangeListener(this.onChange);
- },
- onChange: function() {
+ }
+ onChange() {
var team = TeamStore.getCurrent();
- if (!utils.areStatesEqual(this.state.team, team)) {
+ if (!Utils.areStatesEqual(this.state.team, team)) {
this.setState({team: team});
}
- },
- getInitialState: function() {
- return {team: TeamStore.getCurrent()};
- },
- render: function() {
+ }
+ render() {
var result;
switch (this.props.activeTab) {
- case 'general':
- result = (
- <div>
- <GeneralTab
- team={this.state.team}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- teamDisplayName={this.props.teamDisplayName}
- />
- </div>
- );
- break;
- case 'feature':
- result = (
- <div>
- <FeatureTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
- </div>
- );
- break;
- case 'import':
- result = (
- <div>
- <ImportTab team={this.state.team} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
- </div>
- );
- break;
- default:
- result = (
- <div/>
- );
- break;
+ case 'general':
+ result = (
+ <div>
+ <GeneralTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ </div>
+ );
+ break;
+ case 'feature':
+ result = (
+ <div>
+ <FeatureTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
+ </div>
+ );
+ break;
+ case 'import':
+ result = (
+ <div>
+ <ImportTab
+ team={this.state.team}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
+ </div>
+ );
+ break;
+ default:
+ result = (
+ <div/>
+ );
+ break;
}
return result;
}
-});
+}
+
+TeamSettings.defaultProps = {
+ activeTab: '',
+ activeSection: '',
+ teamDisplayName: ''
+};
+TeamSettings.propTypes = {
+ activeTab: React.PropTypes.string.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx
index 7e65e8cab..668bf76cf 100644
--- a/web/react/components/team_settings_modal.jsx
+++ b/web/react/components/team_settings_modal.jsx
@@ -1,70 +1,96 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SettingsSidebar = require('./settings_sidebar.jsx');
-var TeamSettings = require('./team_settings.jsx');
+const SettingsSidebar = require('./settings_sidebar.jsx');
+const TeamSettings = require('./team_settings.jsx');
-module.exports = React.createClass({
- displayName: 'Team Settings Modal',
- propTypes: {
- teamDisplayName: React.PropTypes.string.isRequired
- },
- componentDidMount: function() {
- $('body').on('click', '.modal-back', function onClick() {
+export default class TeamSettingsModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateTab = this.updateTab.bind(this);
+ this.updateSection = this.updateSection.bind(this);
+
+ this.state = {
+ activeTab: 'general',
+ activeSection: ''
+ };
+ }
+ componentDidMount() {
+ $('body').on('click', '.modal-back', function handleBackClick() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function onClick() {
+ $('body').on('click', '.modal-header .close', function handleCloseClick() {
setTimeout(function removeContent() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
- },
- updateTab: function(tab) {
+ }
+ updateTab(tab) {
this.setState({activeTab: tab, activeSection: ''});
- },
- updateSection: function(section) {
+ }
+ updateSection(section) {
this.setState({activeSection: section});
- },
- getInitialState: function() {
- return {activeTab: 'general', activeSection: ''};
- },
- render: function() {
- var tabs = [];
+ }
+ render() {
+ let tabs = [];
tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'});
tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'});
return (
- <div className='modal fade' ref='modal' id='team_settings' role='dialog' tabIndex='-1' aria-hidden='true'>
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
- <h4 className='modal-title' ref='title'>Team Settings</h4>
- </div>
- <div className='modal-body'>
- <div className='settings-table'>
- <div className='settings-links'>
- <SettingsSidebar
- tabs={tabs}
- activeTab={this.state.activeTab}
- updateTab={this.updateTab}
- />
+ <div
+ className='modal fade'
+ ref='modal'
+ id='team_settings'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog settings-modal'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Team Settings
+ </h4>
</div>
- <div className='settings-content minimize-settings'>
- <TeamSettings
- activeTab={this.state.activeTab}
- activeSection={this.state.activeSection}
- updateSection={this.updateSection}
- teamDisplayName={this.props.teamDisplayName}
- />
+ <div className='modal-body'>
+ <div className='settings-table'>
+ <div className='settings-links'>
+ <SettingsSidebar
+ tabs={tabs}
+ activeTab={this.state.activeTab}
+ updateTab={this.updateTab}
+ />
+ </div>
+ <div className='settings-content minimize-settings'>
+ <TeamSettings
+ activeTab={this.state.activeTab}
+ activeSection={this.state.activeSection}
+ updateSection={this.updateSection}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ </div>
+ </div>
</div>
</div>
- </div>
</div>
- </div>
</div>
);
}
-});
+}
+TeamSettingsModal.propTypes = {
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/team_signup_allowed_domains_page.jsx b/web/react/components/team_signup_allowed_domains_page.jsx
index 90c7ff668..721fa142a 100644
--- a/web/react/components/team_signup_allowed_domains_page.jsx
+++ b/web/react/components/team_signup_allowed_domains_page.jsx
@@ -1,31 +1,35 @@
// 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');
+import {strings} from '../utils/config.js';
-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 +50,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 +64,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 +88,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 +106,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_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx
index 92ade5d24..acce6ab49 100644
--- a/web/react/components/team_signup_choose_auth.jsx
+++ b/web/react/components/team_signup_choose_auth.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
var Constants = require('../utils/constants.jsx');
+import {strings} from '../utils/config.js';
export default class ChooseAuthPage extends React.Component {
constructor(props) {
@@ -21,7 +22,7 @@ export default class ChooseAuthPage extends React.Component {
this.props.updatePage('service', Constants.GITLAB_SERVICE);
}.bind(this)
}
- >
+ >
<span className='icon' />
<span>Create new {strings.Team} with GitLab Account</span>
</a>
@@ -39,7 +40,7 @@ export default class ChooseAuthPage extends React.Component {
this.props.updatePage('email', '');
}.bind(this)
}
- >
+ >
<span className='fa fa-envelope' />
<span>Create new {strings.Team} with email address</span>
</a>
diff --git a/web/react/components/team_signup_display_name_page.jsx b/web/react/components/team_signup_display_name_page.jsx
index b5e93de1b..1849f8222 100644
--- a/web/react/components/team_signup_display_name_page.jsx
+++ b/web/react/components/team_signup_display_name_page.jsx
@@ -3,22 +3,26 @@
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
+import {strings} from '../utils/config.js';
-module.exports = React.createClass({
- displayName: 'TeamSignupDisplayNamePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupDisplayNamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'welcome';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var displayName = this.refs.name.getDOMNode().value.trim();
+ var displayName = React.findDOMNode(this.refs.name).value.trim();
if (!displayName) {
this.setState({nameError: 'This field is required'});
return;
@@ -28,15 +32,12 @@ module.exports = React.createClass({
this.props.state.team.display_name = displayName;
this.props.state.team.name = utils.cleanUpUrlable(displayName);
this.props.updateParent(this.props.state);
- },
- getInitialState: function() {
- return {};
- },
- handleFocus: function(e) {
+ }
+ handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
- },
- render: function() {
+ }
+ render() {
client.track('signup', 'signup_team_02_name');
var nameError = null;
@@ -49,24 +50,53 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
-
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-9'>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.display_name}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ />
</div>
</div>
{nameError}
</div>
- <div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <div>
+ {'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}
+ </div>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ Next<i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupDisplayNamePage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_email_item.jsx b/web/react/components/team_signup_email_item.jsx
index 11cd17e74..10bb2d69e 100644
--- a/web/react/components/team_signup_email_item.jsx
+++ b/web/react/components/team_signup_email_item.jsx
@@ -1,28 +1,28 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-
-module.exports = React.createClass({
- displayName: 'TeamSignupEmailItem',
- propTypes: {
- focus: React.PropTypes.bool,
- email: React.PropTypes.string
- },
- getInitialState: function() {
- return {};
- },
- getValue: function() {
- return this.refs.email.getDOMNode().value.trim();
- },
- validate: function(teamEmail) {
- var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
+const Utils = require('../utils/utils.jsx');
+
+export default class TeamSignupEmailItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getValue = this.getValue.bind(this);
+ this.validate = this.validate.bind(this);
+
+ this.state = {};
+ }
+ getValue() {
+ return React.findDOMNode(this.refs.email).value.trim();
+ }
+ validate(teamEmail) {
+ const email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) {
return true;
}
- if (!utils.isEmail(email)) {
+ if (!Utils.isEmail(email)) {
this.state.emailError = 'Please enter a valid email address';
this.setState(this.state);
return false;
@@ -31,13 +31,14 @@ module.exports = React.createClass({
this.setState(this.state);
return false;
}
+
this.state.emailError = '';
this.setState(this.state);
return true;
- },
- render: function() {
- var emailError = null;
- var emailDivClass = 'form-group';
+ }
+ render() {
+ let emailError = null;
+ let emailDivClass = 'form-group';
if (this.state.emailError) {
emailError = <label className='control-label'>{this.state.emailError}</label>;
emailDivClass += ' has-error';
@@ -45,9 +46,22 @@ module.exports = React.createClass({
return (
<div className={emailDivClass}>
- <input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' />
+ <input
+ autoFocus={this.props.focus}
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder='Email Address'
+ defaultValue={this.props.email}
+ maxLength='128'
+ />
{emailError}
</div>
);
}
-});
+}
+
+TeamSignupEmailItem.propTypes = {
+ focus: React.PropTypes.bool,
+ email: React.PropTypes.string
+};
diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx
index 18cf05dad..aa402846b 100644
--- a/web/react/components/team_signup_password_page.jsx
+++ b/web/react/components/team_signup_password_page.jsx
@@ -1,24 +1,29 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
+var BrowserStore = require('../stores/browser_store.jsx');
+var UserStore = require('../stores/user_store.jsx');
+import {strings, config} from '../utils/config.js';
-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 +36,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(data) {
UserStore.setLastEmail(teamSignup.team.email);
UserStore.setCurrentUser(data);
if (this.props.hash > 0) {
@@ -52,7 +56,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 +71,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 +90,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 +103,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 +119,37 @@ module.exports = React.createClass({
</div>
</div>
<div className='form-group'>
- <button type='submit' className='btn btn-primary margin--extra' id='finish-button' data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ id='finish-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'}
+ onClick={this.submitNext}
+ >
+ Finish
+ </button>
</div>
<p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupPasswordPage.defaultProps = {
+ state: {},
+ hash: ''
+};
+TeamSignupPasswordPage.propTypes = {
+ state: React.PropTypes.object,
+ hash: React.PropTypes.string,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx
index 646a742ba..11a9980d7 100644
--- a/web/react/components/team_signup_send_invites_page.jsx
+++ b/web/react/components/team_signup_send_invites_page.jsx
@@ -2,9 +2,10 @@
// See License.txt for license information.
var EmailItem = require('./team_signup_email_item.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
var ConfigStore = require('../stores/config_store.jsx');
-var client = require('../utils/client.jsx');
+var Client = require('../utils/client.jsx');
+import {strings, config} from '../utils/config.js';
export default class TeamSignupSendInvitesPage extends React.Component {
constructor(props) {
@@ -17,6 +18,11 @@ export default class TeamSignupSendInvitesPage extends React.Component {
this.state = {
emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
};
+
+ if (!this.state.emailEnabled) {
+ this.props.state.wizard = 'username';
+ this.props.updateParent(this.props.state);
+ }
}
submitBack(e) {
e.preventDefault();
@@ -71,13 +77,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
}
keySubmit(e) {
if (e && e.keyCode === 13) {
- this.submitNext(e)
- }
- }
- componentWillMount() {
- if (!this.state.emailEnabled) {
- this.props.state.wizard = 'username';
- this.props.updateParent(this.props.state);
+ this.submitNext(e);
}
}
componentDidMount() {
@@ -92,7 +92,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
}
}
render() {
- client.track('signup', 'signup_team_05_send_invites');
+ Client.track('signup', 'signup_team_05_send_invites');
var content = null;
var bottomContent = null;
@@ -165,7 +165,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
className='signup-team-logo'
src='/static/images/logo.png'
/>
- <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2>
+ <h2>{'Invite ' + Utils.toTitleCase(strings.Team) + ' Members'}</h2>
{content}
<div className='form-group'>
<button
@@ -190,6 +190,7 @@ export default class TeamSignupSendInvitesPage extends React.Component {
);
}
}
+
TeamSignupSendInvitesPage.propTypes = {
state: React.PropTypes.object.isRequired,
updateParent: React.PropTypes.func.isRequired
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index beef725e2..ffe9d9fe8 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -1,33 +1,38 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
-var constants = require('../utils/constants.jsx');
-
-module.exports = React.createClass({
- displayName: 'TeamSignupURLPage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+const Constants = require('../utils/constants.jsx');
+import {strings, config} from '../utils/config.js';
+
+export default class TeamSignupUrlPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+
+ this.state = {nameError: ''};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var name = this.refs.name.getDOMNode().value.trim();
+ const name = React.findDOMNode(this.refs.name).value.trim();
if (!name) {
this.setState({nameError: 'This field is required'});
return;
}
- var cleanedName = utils.cleanUpUrlable(name);
+ const cleanedName = Utils.cleanUpUrlable(name);
- var urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
+ const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
if (cleanedName !== name || !urlRegex.test(name)) {
this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
return;
@@ -36,14 +41,14 @@ module.exports = React.createClass({
return;
}
- for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) {
- if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) {
- this.setState({nameError: 'This team name is unavailable'});
+ for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
+ if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
+ this.setState({nameError: 'URL is taken or contains a reserved word'});
return;
}
}
- client.findTeamByName(name,
+ Client.findTeamByName(name,
function success(data) {
if (!data) {
if (config.AllowSignupDomainsWizard) {
@@ -65,55 +70,88 @@ module.exports = React.createClass({
this.setState(this.state);
}.bind(this)
);
- },
- getInitialState: function() {
- return {};
- },
- handleFocus: function(e) {
+ }
+ handleFocus(e) {
e.preventDefault();
e.currentTarget.select();
- },
- render: function() {
+ }
+ render() {
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
- client.track('signup', 'signup_team_03_url');
+ Client.track('signup', 'signup_team_03_url');
- var nameError = null;
- var nameDivClass = 'form-group';
+ let nameError = null;
+ let nameDivClass = 'form-group';
if (this.state.nameError) {
nameError = <label className='control-label'>{this.state.nameError}</label>;
nameDivClass += ' has-error';
}
+ const title = `${Utils.getWindowLocationOrigin()}/`;
+
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
- <h2>{utils.toTitleCase(strings.Team) + ' URL'}</h2>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2>{`${Utils.toTitleCase(strings.Team)} URL`}</h2>
<div className={nameDivClass}>
<div className='row'>
<div className='col-sm-11'>
<div className='input-group input-group--limit'>
- <span data-toggle='tooltip' title={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span>
- <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/>
+ <span
+ data-toggle='tooltip'
+ title={title}
+ className='input-group-addon'
+ >
+ {title}
+ </span>
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.name}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ />
</div>
</div>
</div>
{nameError}
</div>
- <p>{'Choose the web address of your new ' + strings.Team + ':'}</p>
+ <p>{`Choose the web address of your new ${strings.Team}:`}</p>
<ul className='color--light'>
<li>Short and memorable is best</li>
<li>Use lowercase letters, numbers and dashes</li>
<li>Must start with a letter and can't end in a dash</li>
</ul>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ Next<i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupUrlPage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx
index 56882e6a1..b5c8b14df 100644
--- a/web/react/components/team_signup_username_page.jsx
+++ b/web/react/components/team_signup_username_page.jsx
@@ -1,26 +1,30 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
+import {strings} from '../utils/config.js';
-module.exports = React.createClass({
- displayName: 'TeamSignupUsernamePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitBack: function(e) {
+export default class TeamSignupUsernamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
e.preventDefault();
this.props.state.wizard = 'send_invites';
this.props.updateParent(this.props.state);
- },
- submitNext: function(e) {
+ }
+ submitNext(e) {
e.preventDefault();
- var name = this.refs.name.getDOMNode().value.trim();
+ var name = React.findDOMNode(this.refs.name).value.trim();
- var usernameError = utils.isValidUsername(name);
+ var usernameError = Utils.isValidUsername(name);
if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({nameError: 'This username is reserved, please choose a new one.'});
return;
@@ -32,12 +36,9 @@ module.exports = React.createClass({
this.props.state.wizard = 'password';
this.props.state.user.username = name;
this.props.updateParent(this.props.state);
- },
- getInitialState: function() {
- return {};
- },
- render: function() {
- client.track('signup', 'signup_team_06_username');
+ }
+ render() {
+ Client.track('signup', 'signup_team_06_username');
var nameError = null;
var nameDivClass = 'form-group';
@@ -49,7 +50,10 @@ module.exports = React.createClass({
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h2 className='margin--less'>Your username</h2>
<h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5>
<div className='inner__content margin--extra'>
@@ -57,19 +61,47 @@ module.exports = React.createClass({
<div className='row'>
<div className='col-sm-11'>
<h5><strong>Choose your username</strong></h5>
- <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' />
+ <input
+ autoFocus={true}
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ defaultValue={this.props.state.user.username}
+ maxLength='128'
+ />
<div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div>
</div>
</div>
{nameError}
</div>
</div>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ Next
+ <i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
<div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ Back to previous step
+ </a>
</div>
</form>
</div>
);
}
-});
+}
+
+TeamSignupUsernamePage.defaultProps = {
+ state: null
+};
+TeamSignupUsernamePage.propTypes = {
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx
index f0c680bd8..43b7aea0e 100644
--- a/web/react/components/team_signup_welcome_page.jsx
+++ b/web/react/components/team_signup_welcome_page.jsx
@@ -1,17 +1,25 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+var Utils = require('../utils/utils.jsx');
+var Client = require('../utils/client.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
+import {config} from '../utils/config.js';
-module.exports = React.createClass({
- displayName: 'TeamSignupWelcomePage',
- propTypes: {
- state: React.PropTypes.object,
- updateParent: React.PropTypes.func
- },
- submitNext: function(e) {
+export default class TeamSignupWelcomePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitNext = this.submitNext.bind(this);
+ this.handleDiffEmail = this.handleDiffEmail.bind(this);
+ this.handleDiffSubmit = this.handleDiffSubmit.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+
+ this.state = {useDiff: false};
+
+ document.addEventListener('keyup', this.handleKeyPress, false);
+ }
+ submitNext(e) {
if (!BrowserStore.isLocalStorageSupported()) {
this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'});
return;
@@ -19,18 +27,18 @@ module.exports = React.createClass({
e.preventDefault();
this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
- },
- handleDiffEmail: function(e) {
+ }
+ handleDiffEmail(e) {
e.preventDefault();
this.setState({useDiff: true});
- },
- handleDiffSubmit: function(e) {
+ }
+ handleDiffSubmit(e) {
e.preventDefault();
var state = {useDiff: true, serverError: ''};
- var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
- if (!email || !utils.isEmail(email)) {
+ var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
+ if (!email || !Utils.isEmail(email)) {
state.emailError = 'Please enter a valid email address';
this.setState(state);
return;
@@ -41,7 +49,7 @@ module.exports = React.createClass({
}
state.emailError = '';
- client.signupTeam(email,
+ Client.signupTeam(email,
function success(data) {
if (data.follow_link) {
window.location.href = data.follow_link;
@@ -56,23 +64,17 @@ module.exports = React.createClass({
this.setState(this.state);
}.bind(this)
);
- },
- getInitialState: function() {
- return {useDiff: false};
- },
- handleKeyPress: function(event) {
+ }
+ handleKeyPress(event) {
if (event.keyCode === 13) {
this.submitNext(event);
}
- },
- componentWillMount: function() {
- document.addEventListener('keyup', this.handleKeyPress, false);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
document.removeEventListener('keyup', this.handleKeyPress, false);
- },
- render: function() {
- client.track('signup', 'signup_team_01_welcome');
+ }
+ render() {
+ Client.track('signup', 'signup_team_01_welcome');
var storageError = null;
if (this.state.storageError) {
@@ -105,7 +107,10 @@ module.exports = React.createClass({
return (
<div>
<p>
- <img className='signup-team-logo' src='/static/images/logo.png' />
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
<h3 className='sub-heading'>Welcome to:</h3>
<h1 className='margin--top-none'>{config.SiteName}</h1>
</p>
@@ -121,7 +126,14 @@ module.exports = React.createClass({
You can add other administrators later.
</p>
<div className='form-group'>
- <button className='btn-primary btn form-group' type='submit' onClick={this.submitNext}><i className='glyphicon glyphicon-ok'></i>Yes, this address is correct</button>
+ <button
+ className='btn-primary btn form-group'
+ type='submit'
+ onClick={this.submitNext}
+ >
+ <i className='glyphicon glyphicon-ok'></i>
+ Yes, this address is correct
+ </button>
{storageError}
</div>
<hr />
@@ -129,16 +141,42 @@ module.exports = React.createClass({
<div className={emailDivClass}>
<div className='row'>
<div className='col-sm-9'>
- <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' />
+ <input
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder='Email Address'
+ maxLength='128'
+ />
</div>
</div>
{emailError}
</div>
{serverError}
- <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit}>Use this instead</button>
+ <button
+ className='btn btn-md btn-primary'
+ type='button'
+ onClick={this.handleDiffSubmit}
+ >
+ Use this instead
+ </button>
</div>
- <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a>
+ <a
+ href='#'
+ onClick={this.handleDiffEmail}
+ className={differentEmailLinkClass}
+ >
+ Use a different email
+ </a>
</div>
);
}
-});
+}
+
+TeamSignupWelcomePage.defaultProps = {
+ state: {}
+};
+TeamSignupWelcomePage.propTypes = {
+ updateParent: React.PropTypes.func.isRequired,
+ state: React.PropTypes.object
+};
diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx
index c7204880f..d75736bd3 100644
--- a/web/react/components/team_signup_with_email.jsx
+++ b/web/react/components/team_signup_with_email.jsx
@@ -1,8 +1,9 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
-var client = require('../utils/client.jsx');
+const Utils = require('../utils/utils.jsx');
+const Client = require('../utils/client.jsx');
+import {strings} from '../utils/config.js';
export default class EmailSignUpPage extends React.Component {
constructor() {
@@ -14,11 +15,11 @@ export default class EmailSignUpPage extends React.Component {
}
handleSubmit(e) {
e.preventDefault();
- var team = {};
- var state = {serverError: ''};
+ let team = {};
+ let state = {serverError: ''};
- team.email = this.refs.email.getDOMNode().value.trim().toLowerCase();
- if (!team.email || !utils.isEmail(team.email)) {
+ team.email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
+ if (!team.email || !Utils.isEmail(team.email)) {
state.emailError = 'Please enter a valid email address';
state.inValid = true;
} else {
@@ -30,12 +31,12 @@ export default class EmailSignUpPage extends React.Component {
return;
}
- client.signupTeam(team.email,
+ Client.signupTeam(team.email,
function success(data) {
if (data.follow_link) {
window.location.href = data.follow_link;
} else {
- window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email);
+ window.location.href = `/signup_team_confirm/?email=${encodeURIComponent(team.email)}`;
}
},
function fail(err) {
@@ -69,7 +70,7 @@ export default class EmailSignUpPage extends React.Component {
</button>
</div>
<div className='form-group margin--extra-2x'>
- <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span>
+ <span><a href='/find_team'>{`Find my ${strings.Team}`}</a></span>
</div>
</form>
);
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index 6cb62efc7..521c21733 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -4,6 +4,7 @@
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
+import {strings} from '../utils/config.js';
export default class SSOSignUpPage extends React.Component {
constructor(props) {
@@ -52,7 +53,7 @@ export default class SSOSignUpPage extends React.Component {
);
}
nameChange() {
- this.setState({name: this.refs.teamname.getDOMNode().value.trim()});
+ this.setState({name: React.findDOMNode(this.refs.teamname).value.trim()});
}
render() {
var nameError = null;
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index efd2dd810..ea8126bec 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -1,66 +1,93 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var PostStore = require('../stores/post_store.jsx');
-var CommandList = require('./command_list.jsx');
-var ErrorStore = require('../stores/error_store.jsx');
-var AsyncClient = require('../utils/async_client.jsx');
-
-var utils = require('../utils/utils.jsx');
-var Constants = require('../utils/constants.jsx');
-var ActionTypes = Constants.ActionTypes;
+const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+const PostStore = require('../stores/post_store.jsx');
+const CommandList = require('./command_list.jsx');
+const ErrorStore = require('../stores/error_store.jsx');
+const AsyncClient = require('../utils/async_client.jsx');
+
+const Utils = require('../utils/utils.jsx');
+const Constants = require('../utils/constants.jsx');
+const ActionTypes = Constants.ActionTypes;
+
+export default class Textbox extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getStateFromStores = this.getStateFromStores.bind(this);
+ this.onListenerChange = this.onListenerChange.bind(this);
+ this.onRecievedError = this.onRecievedError.bind(this);
+ this.onTimerInterrupt = this.onTimerInterrupt.bind(this);
+ this.updateMentionTab = this.updateMentionTab.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleBackspace = this.handleBackspace.bind(this);
+ this.checkForNewMention = this.checkForNewMention.bind(this);
+ this.addMention = this.addMention.bind(this);
+ this.addCommand = this.addCommand.bind(this);
+ this.resize = this.resize.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+ this.handleBlur = this.handleBlur.bind(this);
+ this.handlePaste = this.handlePaste.bind(this);
+
+ this.state = {
+ mentionText: '-1',
+ mentions: [],
+ connection: '',
+ timerInterrupt: null
+ };
+
+ this.caret = -1;
+ this.addedMention = false;
+ this.doProcessMentions = false;
+ this.mentions = [];
+ }
+ getStateFromStores() {
+ const error = ErrorStore.getLastError();
-function getStateFromStores() {
- var error = ErrorStore.getLastError();
+ if (error) {
+ return {message: error.message};
+ }
- if (error) {
- return {message: error.message};
+ return {message: null};
}
- return {message: null};
-}
-
-module.exports = React.createClass({
- displayName: 'Textbox',
- caret: -1,
- addedMention: false,
- doProcessMentions: false,
- mentions: [],
- componentDidMount: function() {
+ componentDidMount() {
PostStore.addAddMentionListener(this.onListenerChange);
ErrorStore.addChangeListener(this.onRecievedError);
this.resize();
this.updateMentionTab(null);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
PostStore.removeAddMentionListener(this.onListenerChange);
ErrorStore.removeChangeListener(this.onRecievedError);
- },
- onListenerChange: function(id, username) {
+ }
+ onListenerChange(id, username) {
if (id === this.props.id) {
this.addMention(username);
}
- },
- onRecievedError: function() {
- var errorState = getStateFromStores();
+ }
+ onRecievedError() {
+ const errorState = this.getStateFromStores();
- if (this.state.timerInterrupt != null) {
+ if (this.state.timerInterrupt !== null) {
window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null});
}
if (errorState.message === 'There appears to be a problem with your internet connection') {
this.setState({connection: 'bad-connection'});
- var timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
+ const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000);
this.setState({timerInterrupt: timerInterrupt});
} else {
this.setState({connection: ''});
}
- },
- onTimerInterrupt: function() {
- //Since these should only happen when you have no connection and slightly briefly after any
- //performance hit should not matter
+ }
+ onTimerInterrupt() {
+ // Since these should only happen when you have no connection and slightly briefly after any
+ // performance hit should not matter
if (this.state.connection === 'bad-connection') {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
@@ -72,10 +99,10 @@ module.exports = React.createClass({
window.clearInterval(this.state.timerInterrupt);
this.setState({timerInterrupt: null});
- },
- componentDidUpdate: function() {
+ }
+ componentDidUpdate() {
if (this.caret >= 0) {
- utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret);
+ Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret);
this.caret = -1;
}
if (this.doProcessMentions) {
@@ -83,40 +110,33 @@ module.exports = React.createClass({
this.doProcessMentions = false;
}
this.resize();
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+ componentWillReceiveProps(nextProps) {
if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText);
}
- var text = this.refs.message.getDOMNode().value;
+ const text = React.findDOMNode(this.refs.message).value;
if (nextProps.channelId !== this.props.channelId || nextProps.messageText !== text) {
this.doProcessMentions = true;
}
this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText);
- this.resize();
- },
- getInitialState: function() {
- return {mentionText: '-1', mentions: [], connection: '', timerInterrupt: null};
- },
- updateMentionTab: function(mentionText) {
- var self = this;
-
+ }
+ updateMentionTab(mentionText) {
// using setTimeout so dispatch isn't called during an in progress dispatch
- setTimeout(function() {
+ setTimeout(function updateMentionTabAfterTimeout() {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_MENTION_DATA,
- id: self.props.id,
+ id: this.props.id,
mention_text: mentionText
});
- }, 1);
- },
- handleChange: function() {
- this.props.onUserInput(this.refs.message.getDOMNode().value);
- this.resize();
- },
- handleKeyPress: function(e) {
- var text = this.refs.message.getDOMNode().value;
+ }.bind(this), 1);
+ }
+ handleChange() {
+ this.props.onUserInput(React.findDOMNode(this.refs.message).value);
+ }
+ handleKeyPress(e) {
+ const text = React.findDOMNode(this.refs.message).value;
if (!this.refs.commands.isEmpty() && text.indexOf('/') === 0 && e.which === 13) {
this.refs.commands.addFirstCommand();
@@ -125,10 +145,10 @@ module.exports = React.createClass({
}
if (!this.doProcessMentions) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
- var preText = text.substring(0, caret);
- var lastSpace = preText.lastIndexOf(' ');
- var lastAt = preText.lastIndexOf('@');
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
+ const preText = text.substring(0, caret);
+ const lastSpace = preText.lastIndexOf(' ');
+ const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && lastSpace < lastAt) {
this.doProcessMentions = true;
@@ -136,18 +156,18 @@ module.exports = React.createClass({
}
this.props.onKeyPress(e);
- },
- handleKeyDown: function(e) {
- if (utils.getSelectedText(this.refs.message.getDOMNode()) !== '') {
+ }
+ handleKeyDown(e) {
+ if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') {
this.doProcessMentions = true;
}
if (e.keyCode === 8) {
this.handleBackspace(e);
}
- },
- handleBackspace: function() {
- var text = this.refs.message.getDOMNode().value;
+ }
+ handleBackspace() {
+ const text = React.findDOMNode(this.refs.message).value;
if (text.indexOf('/') === 0) {
this.refs.commands.getSuggestedCommands(text.substring(0, text.length - 1));
}
@@ -156,21 +176,21 @@ module.exports = React.createClass({
return;
}
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
- var preText = text.substring(0, caret);
- var lastSpace = preText.lastIndexOf(' ');
- var lastAt = preText.lastIndexOf('@');
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
+ const preText = text.substring(0, caret);
+ const lastSpace = preText.lastIndexOf(' ');
+ const lastAt = preText.lastIndexOf('@');
if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) {
this.doProcessMentions = true;
}
- },
- checkForNewMention: function(text) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
+ }
+ checkForNewMention(text) {
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
- var preText = text.substring(0, caret);
+ const preText = text.substring(0, caret);
- var atIndex = preText.lastIndexOf('@');
+ const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
@@ -178,8 +198,8 @@ module.exports = React.createClass({
return;
}
- var lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
- var lastSpace = preText.lastIndexOf(' ');
+ const lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
+ const lastSpace = preText.lastIndexOf(' ');
// If there is a space after the last @, nothing to do.
if (lastSpace > atIndex || lastCharSpace > atIndex) {
@@ -188,43 +208,45 @@ module.exports = React.createClass({
}
// Get the name typed so far.
- var name = preText.substring(atIndex + 1, preText.length).toLowerCase();
+ const name = preText.substring(atIndex + 1, preText.length).toLowerCase();
this.updateMentionTab(name);
- },
- addMention: function(name) {
- var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
+ }
+ addMention(name) {
+ const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message));
- var text = this.props.messageText;
+ const text = this.props.messageText;
- var preText = text.substring(0, caret);
+ const preText = text.substring(0, caret);
- var atIndex = preText.lastIndexOf('@');
+ const atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
return;
}
- var prefix = text.substring(0, atIndex);
- var suffix = text.substring(caret, text.length);
+ const prefix = text.substring(0, atIndex);
+ const suffix = text.substring(caret, text.length);
this.caret = prefix.length + name.length + 2;
this.addedMention = true;
this.doProcessMentions = true;
- this.props.onUserInput(prefix + '@' + name + ' ' + suffix);
- },
- addCommand: function(cmd) {
- var elm = this.refs.message.getDOMNode();
+ this.props.onUserInput(`${prefix}@${name} ${suffix}`);
+ }
+ addCommand(cmd) {
+ const elm = React.findDOMNode(this.refs.message);
elm.value = cmd;
this.handleChange();
- },
- resize: function() {
- var e = this.refs.message.getDOMNode();
- var w = this.refs.wrapper.getDOMNode();
+ }
+ resize() {
+ const e = React.findDOMNode(this.refs.message);
+ const w = React.findDOMNode(this.refs.wrapper);
+
+ const prevHeight = $(e).height();
- var lht = parseInt($(e).css('lineHeight'), 10);
- var lines = e.scrollHeight / lht;
- var mod = 15;
+ const lht = parseInt($(e).css('lineHeight'), 10);
+ const lines = e.scrollHeight / lht;
+ let mod = 15;
if (lines < 2.5 || this.props.messageText === '') {
mod = 30;
@@ -237,28 +259,67 @@ module.exports = React.createClass({
$(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
$(w).css({height: 'auto'}).height(167);
}
- },
- handleFocus: function() {
- var elm = this.refs.message.getDOMNode();
+
+ if (prevHeight !== $(e).height() && this.props.onHeightChange) {
+ this.props.onHeightChange();
+ }
+ }
+ handleFocus() {
+ const elm = React.findDOMNode(this.refs.message);
if (elm.title === elm.value) {
elm.value = '';
}
- },
- handleBlur: function() {
- var elm = this.refs.message.getDOMNode();
+ }
+ handleBlur() {
+ const elm = React.findDOMNode(this.refs.message);
if (elm.value === '') {
elm.value = elm.title;
}
- },
- handlePaste: function() {
+ }
+ handlePaste() {
this.doProcessMentions = true;
- },
- render: function() {
+ }
+ render() {
return (
- <div ref='wrapper' className='textarea-wrapper'>
- <CommandList ref='commands' addCommand={this.addCommand} channelId={this.props.channelId} />
- <textarea id={this.props.id} ref='message' className={'form-control custom-textarea ' + this.state.connection} spellCheck='true' autoComplete='off' autoCorrect='off' rows='1' maxLength={Constants.MAX_POST_LEN} placeholder={this.props.createMessage} value={this.props.messageText} onInput={this.handleChange} onChange={this.handleChange} onKeyPress={this.handleKeyPress} onKeyDown={this.handleKeyDown} onFocus={this.handleFocus} onBlur={this.handleBlur} onPaste={this.handlePaste} />
+ <div
+ ref='wrapper'
+ className='textarea-wrapper'
+ >
+ <CommandList
+ ref='commands'
+ addCommand={this.addCommand}
+ channelId={this.props.channelId}
+ />
+ <textarea
+ id={this.props.id}
+ ref='message'
+ className={`form-control custom-textarea ${this.state.connection}`}
+ spellCheck='true'
+ autoComplete='off'
+ autoCorrect='off'
+ rows='1'
+ maxLength={Constants.MAX_POST_LEN}
+ placeholder={this.props.createMessage}
+ value={this.props.messageText}
+ onInput={this.handleChange}
+ onChange={this.handleChange}
+ onKeyPress={this.handleKeyPress}
+ onKeyDown={this.handleKeyDown}
+ onFocus={this.handleFocus}
+ onBlur={this.handleBlur}
+ onPaste={this.handlePaste}
+ />
</div>
);
}
-});
+}
+
+Textbox.propTypes = {
+ id: React.PropTypes.string.isRequired,
+ channelId: React.PropTypes.string,
+ messageText: React.PropTypes.string.isRequired,
+ onUserInput: React.PropTypes.func.isRequired,
+ onKeyPress: React.PropTypes.func.isRequired,
+ onHeightChange: React.PropTypes.func,
+ createMessage: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 5c4d26a23..739084053 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -1,19 +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 };
- }
-}
+import {config} from '../utils/config.js';
var id = 0;
@@ -22,49 +12,77 @@ function nextId() {
return id;
}
+export default class UserProfile extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.uniqueId = nextId();
+ this.onChange = this.onChange.bind(this);
+
+ 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.jsx b/web/react/components/user_settings.jsx
index 9b0e906c5..282fb7681 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -8,56 +8,82 @@ var SecurityTab = require('./user_settings_security.jsx');
var GeneralTab = require('./user_settings_general.jsx');
var AppearanceTab = require('./user_settings_appearance.jsx');
-module.exports = React.createClass({
- displayName: 'UserSettings',
- propTypes: {
- activeTab: React.PropTypes.string,
- activeSection: React.PropTypes.string,
- updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func
- },
- componentDidMount: function() {
+export default class UserSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onListenerChange = this.onListenerChange.bind(this);
+
+ this.state = {user: UserStore.getCurrentUser()};
+ }
+
+ componentDidMount() {
UserStore.addChangeListener(this.onListenerChange);
- },
- componentWillUnmount: function() {
+ }
+
+ componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange);
- },
- onListenerChange: function () {
+ }
+
+ onListenerChange() {
var user = UserStore.getCurrentUser();
if (!utils.areStatesEqual(this.state.user, user)) {
this.setState({user: user});
}
- },
- getInitialState: function() {
- return {user: UserStore.getCurrentUser()};
- },
- render: function() {
+ }
+
+ render() {
if (this.props.activeTab === 'general') {
return (
<div>
- <GeneralTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} />
+ <GeneralTab
+ user={this.state.user}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
</div>
);
} else if (this.props.activeTab === 'security') {
return (
<div>
- <SecurityTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} />
+ <SecurityTab
+ user={this.state.user}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ updateTab={this.props.updateTab}
+ />
</div>
);
} else if (this.props.activeTab === 'notifications') {
return (
<div>
- <NotificationsTab user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} />
+ <NotificationsTab
+ user={this.state.user}
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ updateTab={this.props.updateTab}
+ />
</div>
);
} else if (this.props.activeTab === 'appearance') {
return (
<div>
- <AppearanceTab activeSection={this.props.activeSection} updateSection={this.props.updateSection} updateTab={this.props.updateTab} />
+ <AppearanceTab
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ updateTab={this.props.updateTab}
+ />
</div>
);
- } else {
- return <div/>;
}
+
+ return <div/>;
}
-});
+}
+
+UserSettings.propTypes = {
+ activeTab: React.PropTypes.string,
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func,
+ updateTab: React.PropTypes.func
+}; \ No newline at end of file
diff --git a/web/react/components/user_settings_appearance.jsx b/web/react/components/user_settings_appearance.jsx
index ba2d97ea8..3afdd7349 100644
--- a/web/react/components/user_settings_appearance.jsx
+++ b/web/react/components/user_settings_appearance.jsx
@@ -4,100 +4,138 @@
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');
+import {config} from '../utils/config.js';
-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
+ key={config.ThemeColors[i] + 'key' + i}
+ 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
+ key='themeColorSetting'
+ 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 +143,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_general.jsx b/web/react/components/user_settings_general.jsx
index fed11fbe9..ead7ac1d5 100644
--- a/web/react/components/user_settings_general.jsx
+++ b/web/react/components/user_settings_general.jsx
@@ -11,10 +11,32 @@ var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
var assign = require('object-assign');
-module.exports = React.createClass({
- displayName: 'GeneralTab',
- submitActive: false,
- submitUsername: function(e) {
+export default class UserSettingsGeneralTab extends React.Component {
+ constructor(props) {
+ super(props);
+ this.submitActive = false;
+
+ this.submitUsername = this.submitUsername.bind(this);
+ this.submitNickname = this.submitNickname.bind(this);
+ this.submitName = this.submitName.bind(this);
+ this.submitEmail = this.submitEmail.bind(this);
+ this.submitUser = this.submitUser.bind(this);
+ this.submitPicture = this.submitPicture.bind(this);
+
+ this.updateUsername = this.updateUsername.bind(this);
+ this.updateFirstName = this.updateFirstName.bind(this);
+ this.updateLastName = this.updateLastName.bind(this);
+ this.updateNickname = this.updateNickname.bind(this);
+ this.updateEmail = this.updateEmail.bind(this);
+ this.updatePicture = this.updatePicture.bind(this);
+ this.updateSection = this.updateSection.bind(this);
+
+ this.handleClose = this.handleClose.bind(this);
+ this.setupInitialState = this.setupInitialState.bind(this);
+
+ this.state = this.setupInitialState(props);
+ }
+ submitUsername(e) {
e.preventDefault();
var user = this.props.user;
@@ -37,8 +59,8 @@ module.exports = React.createClass({
user.username = username;
this.submitUser(user);
- },
- submitNickname: function(e) {
+ }
+ submitNickname(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
@@ -52,8 +74,8 @@ module.exports = React.createClass({
user.nickname = nickname;
this.submitUser(user);
- },
- submitName: function(e) {
+ }
+ submitName(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
@@ -69,8 +91,8 @@ module.exports = React.createClass({
user.last_name = lastName;
this.submitUser(user);
- },
- submitEmail: function(e) {
+ }
+ submitEmail(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
@@ -88,15 +110,15 @@ module.exports = React.createClass({
user.email = email;
this.submitUser(user);
- },
- submitUser: function(user) {
+ }
+ submitUser(user) {
client.updateUser(user,
- function() {
+ function updateSuccess() {
this.updateSection('');
AsyncClient.getMe();
}.bind(this),
- function(err) {
- var state = this.getInitialState();
+ function updateFailure(err) {
+ var state = this.setupInitialState(this.props);
if (err.message) {
state.serverError = err.message;
} else {
@@ -105,8 +127,8 @@ module.exports = React.createClass({
this.setState(state);
}.bind(this)
);
- },
- submitPicture: function(e) {
+ }
+ submitPicture(e) {
e.preventDefault();
if (!this.state.picture) {
@@ -129,34 +151,34 @@ module.exports = React.createClass({
this.setState({loadingPicture: true});
client.uploadProfileImage(formData,
- function() {
+ function imageUploadSuccess() {
this.submitActive = false;
AsyncClient.getMe();
window.location.reload();
}.bind(this),
- function(err) {
- var state = this.getInitialState();
+ function imageUploadFailure(err) {
+ var state = this.setupInitialState(this.props);
state.serverError = err;
this.setState(state);
}.bind(this)
);
- },
- updateUsername: function(e) {
+ }
+ updateUsername(e) {
this.setState({username: e.target.value});
- },
- updateFirstName: function(e) {
+ }
+ updateFirstName(e) {
this.setState({firstName: e.target.value});
- },
- updateLastName: function(e) {
+ }
+ updateLastName(e) {
this.setState({lastName: e.target.value});
- },
- updateNickname: function(e) {
+ }
+ updateNickname(e) {
this.setState({nickname: e.target.value});
- },
- updateEmail: function(e) {
+ }
+ updateEmail(e) {
this.setState({email: e.target.value});
- },
- updatePicture: function(e) {
+ }
+ updatePicture(e) {
if (e.target.files && e.target.files[0]) {
this.setState({picture: e.target.files[0]});
@@ -165,34 +187,33 @@ module.exports = React.createClass({
} else {
this.setState({picture: null});
}
- },
- updateSection: function(section) {
- this.setState(assign({}, this.getInitialState(), {clientError: ''}));
+ }
+ updateSection(section) {
+ this.setState(assign({}, this.setupInitialState(this.props), {clientError: '', serverError: '', emailError: ''}));
this.submitActive = false;
this.props.updateSection(section);
- },
- handleClose: function() {
- $(this.getDOMNode()).find('.form-control').each(function() {
+ }
+ handleClose() {
+ $(React.findDOMNode(this)).find('.form-control').each(function clearForms() {
this.value = '';
});
- this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null}));
+ this.setState(assign({}, this.setupInitialState(this.props), {clientError: null, serverError: null, emailError: null}));
this.props.updateSection('');
- },
- componentDidMount: function() {
+ }
+ componentDidMount() {
$('#user_settings').on('hidden.bs.modal', this.handleClose);
- },
- componentWillUnmount: function() {
+ }
+ componentWillUnmount() {
$('#user_settings').off('hidden.bs.modal', this.handleClose);
- },
- getInitialState: function() {
- var user = this.props.user;
+ }
+ setupInitialState(props) {
+ var user = props.user;
var emailEnabled = !ConfigStore.getSettingAsBoolean('ByPassEmail', false);
-
return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
- email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled};
- },
- render: function() {
+ email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled};
+ }
+ render() {
var user = this.props.user;
var clientError = null;
@@ -209,24 +230,39 @@ module.exports = React.createClass({
}
var nameSection;
- var self = this;
var inputs = [];
if (this.props.activeSection === 'name') {
inputs.push(
- <div className='form-group'>
+ <div
+ key='firstNameSetting'
+ className='form-group'
+ >
<label className='col-sm-5 control-label'>First Name</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateFirstName}
+ value={this.state.firstName}
+ />
</div>
</div>
);
inputs.push(
- <div className='form-group'>
+ <div
+ key='lastNameSetting'
+ className='form-group'
+ >
<label className='col-sm-5 control-label'>Last Name</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateLastName}
+ value={this.state.lastName}
+ />
</div>
</div>
);
@@ -238,10 +274,10 @@ module.exports = React.createClass({
submit={this.submitName}
server_error={serverError}
client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
+ updateSection={function clearSection(e) {
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -259,20 +295,33 @@ module.exports = React.createClass({
<SettingItemMin
title='Full Name'
describe={fullName}
- updateSection={function() {
- self.updateSection('name');
- }}
+ updateSection={function updateNameSection() {
+ this.updateSection('name');
+ }.bind(this)}
/>
);
}
var nicknameSection;
if (this.props.activeSection === 'nickname') {
+ let nicknameLabel = 'Nickname';
+ if (utils.isMobile()) {
+ nicknameLabel = '';
+ }
+
inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label>
+ <div
+ key='nicknameSetting'
+ className='form-group'
+ >
+ <label className='col-sm-5 control-label'>{nicknameLabel}</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateNickname} value={this.state.nickname}/>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateNickname}
+ value={this.state.nickname}
+ />
</div>
</div>
);
@@ -284,10 +333,10 @@ module.exports = React.createClass({
submit={this.submitNickname}
server_error={serverError}
client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
+ updateSection={function clearSection(e) {
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -295,20 +344,33 @@ module.exports = React.createClass({
<SettingItemMin
title='Nickname'
describe={UserStore.getCurrentUser().nickname}
- updateSection={function() {
- self.updateSection('nickname');
- }}
+ updateSection={function updateNicknameSection() {
+ this.updateSection('nickname');
+ }.bind(this)}
/>
);
}
var usernameSection;
if (this.props.activeSection === 'username') {
+ let usernameLabel = 'Username';
+ if (utils.isMobile()) {
+ usernameLabel = '';
+ }
+
inputs.push(
- <div className='form-group'>
- <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label>
+ <div
+ key='usernameSetting'
+ className='form-group'
+ >
+ <label className='col-sm-5 control-label'>{usernameLabel}</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateUsername}
+ value={this.state.username}
+ />
</div>
</div>
);
@@ -320,10 +382,10 @@ module.exports = React.createClass({
submit={this.submitUsername}
server_error={serverError}
client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
+ updateSection={function clearSection(e) {
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -331,9 +393,9 @@ module.exports = React.createClass({
<SettingItemMin
title='Username'
describe={UserStore.getCurrentUser().username}
- updateSection={function() {
- self.updateSection('username');
- }}
+ updateSection={function updateUsernameSection() {
+ this.updateSection('username');
+ }.bind(this)}
/>
);
}
@@ -346,11 +408,16 @@ module.exports = React.createClass({
}
inputs.push(
- <div>
+ <div key='emailSetting'>
<div className='form-group'>
<label className='col-sm-5 control-label'>Primary Email</label>
<div className='col-sm-7'>
- <input className='form-control' type='text' onChange={this.updateEmail} value={this.state.email}/>
+ <input
+ className='form-control'
+ type='text'
+ onChange={this.updateEmail}
+ value={this.state.email}
+ />
</div>
</div>
{helpText}
@@ -364,10 +431,10 @@ module.exports = React.createClass({
submit={this.submitEmail}
server_error={serverError}
client_error={emailError}
- updateSection={function(e) {
- self.updateSection('');
+ updateSection={function clearSection(e) {
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
/>
);
} else {
@@ -375,9 +442,9 @@ module.exports = React.createClass({
<SettingItemMin
title='Email'
describe={UserStore.getCurrentUser().email}
- updateSection={function() {
- self.updateSection('email');
- }}
+ updateSection={function updateEmailSection() {
+ this.updateSection('email');
+ }.bind(this)}
/>
);
}
@@ -391,10 +458,10 @@ module.exports = React.createClass({
src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update}
server_error={serverError}
client_error={clientError}
- updateSection={function(e) {
- self.updateSection('');
+ updateSection={function clearSection(e) {
+ this.updateSection('');
e.preventDefault();
- }}
+ }.bind(this)}
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
@@ -410,17 +477,30 @@ module.exports = React.createClass({
<SettingItemMin
title='Profile Picture'
describe={minMessage}
- updateSection={function() {
- self.updateSection('picture');
- }}
+ updateSection={function updatePictureSection() {
+ this.updateSection('picture');
+ }.bind(this)}
/>
);
}
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>General 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>
+ General Settings
+ </h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>General Settings</h3>
@@ -439,4 +519,10 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+UserSettingsGeneralTab.propTypes = {
+ user: React.PropTypes.object,
+ updateSection: React.PropTypes.func,
+ activeSection: React.PropTypes.string
+};
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index f5a555951..7ec75e000 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -4,51 +4,75 @@
var SettingsSidebar = require('./settings_sidebar.jsx');
var UserSettings = require('./user_settings.jsx');
-module.exports = React.createClass({
- componentDidMount: function() {
- $('body').on('click', '.modal-back', function(){
+export default class UserSettingsModal extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateTab = this.updateTab.bind(this);
+ this.updateSection = this.updateSection.bind(this);
+
+ this.state = {active_tab: 'general', active_section: ''};
+ }
+ componentDidMount() {
+ $('body').on('click', '.modal-back', function changeDisplay() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function(){
- setTimeout(function() {
+ $('body').on('click', '.modal-header .close', function closeModal() {
+ setTimeout(function finishClose() {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
- },
- updateTab: function(tab) {
- this.setState({ active_tab: tab });
- },
- updateSection: function(section) {
- this.setState({ active_section: section });
- },
- getInitialState: function() {
- return { active_tab: "general", active_section: "" };
- },
- render: function() {
+ }
+ updateTab(tab) {
+ this.setState({active_tab: tab});
+ }
+ updateSection(section) {
+ this.setState({active_section: section});
+ }
+ render() {
var tabs = [];
- tabs.push({name: "general", uiName: "General", icon: "glyphicon glyphicon-cog"});
- tabs.push({name: "security", uiName: "Security", icon: "glyphicon glyphicon-lock"});
- tabs.push({name: "notifications", uiName: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"});
- tabs.push({name: "appearance", uiName: "Appearance", icon: "glyphicon glyphicon-wrench"});
+ tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'});
+ tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
+ tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
+ tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
return (
- <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true">
- <div className="modal-dialog settings-modal">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" ref="title">Account Settings</h4>
+ <div
+ className='modal fade'
+ ref='modal'
+ id='user_settings'
+ role='dialog'
+ tabIndex='-1'
+ aria-hidden='true'
+ >
+ <div className='modal-dialog settings-modal'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>&times;</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ Account Settings
+ </h4>
</div>
- <div className="modal-body">
- <div className="settings-table">
- <div className="settings-links">
+ <div className='modal-body'>
+ <div className='settings-table'>
+ <div className='settings-links'>
<SettingsSidebar
tabs={tabs}
activeTab={this.state.active_tab}
updateTab={this.updateTab}
/>
</div>
- <div className="settings-content minimize-settings">
+ <div className='settings-content minimize-settings'>
<UserSettings
activeTab={this.state.active_tab}
activeSection={this.state.active_section}
@@ -63,4 +87,4 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx
index b89f72987..33db1a332 100644
--- a/web/react/components/user_settings_notifications.jsx
+++ b/web/react/components/user_settings_notifications.jsx
@@ -8,6 +8,7 @@ var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
var assign = require('object-assign');
+import {config} from '../utils/config.js';
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
@@ -67,7 +68,9 @@ function getNotificationsStateFromStores() {
}
}
- return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound, usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0, firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
+ return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound,
+ usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0,
+ firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
}
export default class NotificationsTab extends React.Component {
@@ -136,7 +139,7 @@ export default class NotificationsTab extends React.Component {
this.props.updateTab('general');
}
updateSection(section) {
- this.setState(this.getInitialState());
+ this.setState(getNotificationsStateFromStores());
this.props.updateSection(section);
}
componentDidMount() {
@@ -156,15 +159,15 @@ export default class NotificationsTab extends React.Component {
}
handleNotifyRadio(notifyLevel) {
this.setState({notifyLevel: notifyLevel});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
handleEmailRadio(enableEmail) {
this.setState({enableEmail: enableEmail});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
handleSoundRadio(enableSound) {
this.setState({enableSound: enableSound});
- this.refs.wrapper.getDOMNode().focus();
+ React.findDOMNode(this.refs.wrapper).focus();
}
updateUsernameKey(val) {
this.setState({usernameKey: val});
@@ -182,10 +185,10 @@ export default class NotificationsTab extends React.Component {
this.setState({channelKey: val});
}
updateCustomMentionKeys() {
- var checked = this.refs.customcheck.getDOMNode().checked;
+ var checked = React.findDOMNode(this.refs.customcheck).checked;
if (checked) {
- var text = this.refs.custommentions.getDOMNode().value;
+ var text = React.findDOMNode(this.refs.custommentions).value;
// remove all spaces and split string into individual keys
this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true});
@@ -194,7 +197,7 @@ export default class NotificationsTab extends React.Component {
}
}
onCustomChange() {
- this.refs.customcheck.getDOMNode().checked = true;
+ React.findDOMNode(this.refs.customcheck).checked = true;
this.updateCustomMentionKeys();
}
render() {
@@ -203,8 +206,6 @@ export default class NotificationsTab extends React.Component {
serverError = this.state.serverError;
}
- var self = this;
-
var user = this.props.user;
var desktopSection;
@@ -222,12 +223,12 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationLevelOption'>
<div className='radio'>
<label>
<input type='radio'
checked={notifyActive[0]}
- onChange={self.handleNotifyRadio.bind(this, 'all')}
+ onChange={this.handleNotifyRadio.bind(this, 'all')}
>
For all activity
</input>
@@ -239,7 +240,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={notifyActive[1]}
- onChange={self.handleNotifyRadio.bind(this, 'mention')}
+ onChange={this.handleNotifyRadio.bind(this, 'mention')}
>
Only for mentions and private messages
</input>
@@ -251,7 +252,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={notifyActive[2]}
- onChange={self.handleNotifyRadio.bind(this, 'none')}
+ onChange={this.handleNotifyRadio.bind(this, 'none')}
>
Never
</input>
@@ -261,9 +262,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateDesktopSection = function updateDesktopSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
desktopSection = (
<SettingItemMax
@@ -285,8 +286,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateDesktopSection = function updateDesktopSection() {
- self.props.updateSection('desktop');
- };
+ this.props.updateSection('desktop');
+ }.bind(this);
desktopSection = (
<SettingItemMin
@@ -310,13 +311,13 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationSoundOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={soundActive[0]}
- onChange={self.handleSoundRadio.bind(this, 'true')}
+ onChange={this.handleSoundRadio.bind(this, 'true')}
>
On
</input>
@@ -328,7 +329,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={soundActive[1]}
- onChange={self.handleSoundRadio.bind(this, 'false')}
+ onChange={this.handleSoundRadio.bind(this, 'false')}
>
Off
</input>
@@ -339,9 +340,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateSoundSection = function updateSoundSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
soundSection = (
<SettingItemMax
@@ -363,8 +364,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateSoundSection = function updateSoundSection() {
- self.props.updateSection('sound');
- };
+ this.props.updateSection('sound');
+ }.bind(this);
soundSection = (
<SettingItemMin
@@ -389,13 +390,13 @@ export default class NotificationsTab extends React.Component {
let inputs = [];
inputs.push(
- <div>
+ <div key='userNotificationEmailOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={emailActive[0]}
- onChange={self.handleEmailRadio.bind(this, 'true')}
+ onChange={this.handleEmailRadio.bind(this, 'true')}
>
On
</input>
@@ -407,7 +408,7 @@ export default class NotificationsTab extends React.Component {
<input
type='radio'
checked={emailActive[1]}
- onChange={self.handleEmailRadio.bind(this, 'false')}
+ onChange={this.handleEmailRadio.bind(this, 'false')}
>
Off
</input>
@@ -419,9 +420,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateEmailSection = function updateEmailSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
emailSection = (
<SettingItemMax
@@ -441,8 +442,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateEmailSection = function updateEmailSection() {
- self.props.updateSection('email');
- };
+ this.props.updateSection('email');
+ }.bind(this);
emailSection = (
<SettingItemMin
@@ -466,10 +467,10 @@ export default class NotificationsTab extends React.Component {
if (user.first_name) {
handleUpdateFirstNameKey = function handleFirstNameKeyChange(e) {
- self.updateFirstNameKey(e.target.checked);
- };
+ this.updateFirstNameKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationFirstNameOption'>
<div className='checkbox'>
<label>
<input
@@ -486,10 +487,10 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateUsernameKey = function handleUsernameKeyChange(e) {
- self.updateUsernameKey(e.target.checked);
- };
+ this.updateUsernameKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationUsernameOption'>
<div className='checkbox'>
<label>
<input
@@ -505,10 +506,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateMentionKey = function handleMentionKeyChange(e) {
- self.updateMentionKey(e.target.checked);
- };
+ this.updateMentionKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationMentionOption'>
<div className='checkbox'>
<label>
<input
@@ -524,10 +525,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateAllKey = function handleAllKeyChange(e) {
- self.updateAllKey(e.target.checked);
- };
+ this.updateAllKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationAllOption'>
<div className='checkbox'>
<label>
<input
@@ -543,10 +544,10 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateChannelKey = function handleChannelKeyChange(e) {
- self.updateChannelKey(e.target.checked);
- };
+ this.updateChannelKey(e.target.checked);
+ }.bind(this);
inputs.push(
- <div>
+ <div key='userNotificationChannelOption'>
<div className='checkbox'>
<label>
<input
@@ -562,7 +563,7 @@ export default class NotificationsTab extends React.Component {
);
inputs.push(
- <div>
+ <div key='userNotificationCustomOption'>
<div className='checkbox'>
<label>
<input
@@ -586,9 +587,9 @@ export default class NotificationsTab extends React.Component {
);
handleUpdateKeysSection = function updateKeysSection(e) {
- self.props.updateSection('');
+ this.props.updateSection('');
e.preventDefault();
- };
+ }.bind(this);
keysSection = (
<SettingItemMax
title='Words that trigger mentions'
@@ -631,8 +632,8 @@ export default class NotificationsTab extends React.Component {
}
handleUpdateKeysSection = function updateKeysSection() {
- self.props.updateSection('keys');
- };
+ this.props.updateSection('keys');
+ }.bind(this);
keysSection = (
<SettingItemMin
diff --git a/web/react/components/user_settings_security.jsx b/web/react/components/user_settings_security.jsx
index ae8a5f0bc..6ccd09cb1 100644
--- a/web/react/components/user_settings_security.jsx
+++ b/web/react/components/user_settings_security.jsx
@@ -3,13 +3,24 @@
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.setupInitialState = this.setupInitialState.bind(this);
+
+ this.state = this.setupInitialState();
+ }
+ submitPassword(e) {
e.preventDefault();
var user = this.props.user;
@@ -37,14 +48,14 @@ 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: ''});
+ this.setState(this.setupInitialState());
}.bind(this),
- function(err) {
- var state = this.getInitialState();
+ function fail(err) {
+ var state = this.setupInitialState();
if (err.message) {
state.serverError = err.message;
} else {
@@ -54,73 +65,102 @@ 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() {
+ }
+ setupInitialState() {
+ return {currentPassword: '', newPassword: '', confirmPassword: ''};
+ }
+ 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;
if (this.props.user.auth_service === '') {
inputs.push(
- <div className='form-group'>
+ <div
+ key='currentPasswordUpdateForm'
+ 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>
);
inputs.push(
- <div className='form-group'>
+ <div
+ key='newPasswordUpdateForm'
+ 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>
);
inputs.push(
- <div className='form-group'>
+ <div
+ key='retypeNewPasswordUpdateForm'
+ 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>
);
@@ -128,17 +168,20 @@ module.exports = React.createClass({
submit = this.submitPassword;
} else {
inputs.push(
- <div className='form-group'>
+ <div
+ key='oauthPasswordInfo'
+ className='form-group'
+ >
<label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
</div>
);
}
- 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 +197,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 +231,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 +252,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..b0eaba5d6 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -2,35 +2,47 @@
// See License.txt for license information.
var Client = require('../utils/client.jsx');
-var utils = require('../utils/utils.jsx');
+var Utils = require('../utils/utils.jsx');
+import {config} from '../utils/config.js';
-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 +50,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 +63,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 +91,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 +143,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 +241,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 +260,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 +286,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>
);
@@ -282,7 +307,8 @@ module.exports = React.createClass({
ref='previewArrowLeft'
className='modal-prev-bar'
href='#'
- onClick={this.handlePrev}>
+ onClick={this.handlePrev}
+ >
<i className='image-control image-prev'/>
</a>
);
@@ -292,25 +318,51 @@ module.exports = React.createClass({
ref='previewArrowRight'
className='modal-next-bar'
href='#'
- onClick={this.handleNext}>
+ onClick={this.handleNext}
+ >
<i className='image-control image-next'/>
</a>
);
}
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'>
- <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
+ <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 +374,19 @@ module.exports = React.createClass({
</div>
);
}
-});
+}
+
+ViewImageModal.defaultProps = {
+ filenames: [],
+ modalId: '',
+ channelId: '',
+ userId: '',
+ startId: 0
+};
+ViewImageModal.propTypes = {
+ filenames: React.PropTypes.array,
+ modalId: React.PropTypes.string,
+ channelId: React.PropTypes.string,
+ userId: React.PropTypes.string,
+ startId: React.PropTypes.number
+};
diff --git a/web/react/dispatcher/app_dispatcher.jsx b/web/react/dispatcher/app_dispatcher.jsx
index 4ae28e8eb..04e026f46 100644
--- a/web/react/dispatcher/app_dispatcher.jsx
+++ b/web/react/dispatcher/app_dispatcher.jsx
@@ -8,23 +8,21 @@ var Constants = require('../utils/constants.jsx');
var PayloadSources = Constants.PayloadSources;
var AppDispatcher = assign(new Dispatcher(), {
+ handleServerAction: function performServerAction(action) {
+ var payload = {
+ source: PayloadSources.SERVER_ACTION,
+ action: action
+ };
+ this.dispatch(payload);
+ },
- handleServerAction: function(action) {
- var payload = {
- source: PayloadSources.SERVER_ACTION,
- action: action
- };
- this.dispatch(payload);
- },
-
- handleViewAction: function(action) {
- var payload = {
- source: PayloadSources.VIEW_ACTION,
- action: action
- };
- this.dispatch(payload);
- }
-
+ handleViewAction: function performViewAction(action) {
+ var payload = {
+ source: PayloadSources.VIEW_ACTION,
+ action: action
+ };
+ this.dispatch(payload);
+ }
});
module.exports = AppDispatcher;
diff --git a/web/react/package.json b/web/react/package.json
index c930c4db6..da55dc2b8 100644
--- a/web/react/package.json
+++ b/web/react/package.json
@@ -8,7 +8,8 @@
"keymirror": "^0.1.1",
"object-assign": "^3.0.0",
"react": "^0.13.3",
- "react-zeroclipboard-mixin": "^0.1.0"
+ "react-zeroclipboard-mixin": "^0.1.0",
+ "twemoji": "^1.4.1"
},
"devDependencies": {
"browserify": "^11.0.1",
@@ -16,18 +17,17 @@
"babelify": "^6.1.3",
"uglify-js": "^2.4.24",
"watchify": "^3.3.1",
- "eslint": "^1.1.0",
- "eslint-plugin-react": "^3.2.3"
+ "eslint": "^1.3.1",
+ "eslint-plugin-react": "^3.3.1"
},
"scripts": {
"start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx",
- "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs > ../static/js/bundle.min.js",
- "build_old": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -cm > ../static/js/bundle.min.js",
+ "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js",
"test": "jest"
},
"browserify": {
"transform": [
- ["babelify", {"blacklist": ["strict"]}],
+ "babelify",
"envify"
]
},
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 98014ed12..71a03cde0 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -26,13 +26,13 @@ var ChannelMembersModal = require('../components/channel_members.jsx');
var ChannelInviteModal = require('../components/channel_invite_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx');
var DirectChannelModal = require('../components/more_direct_channels.jsx');
-var ErrorBar = require('../components/error_bar.jsx')
+var ErrorBar = require('../components/error_bar.jsx');
var ChannelLoader = require('../components/channel_loader.jsx');
var MentionList = require('../components/mention_list.jsx');
var ChannelInfoModal = require('../components/channel_info_modal.jsx');
var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx');
-var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx')
+var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -40,18 +40,18 @@ var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) {
+function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
AsyncClient.getConfig();
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
- name: channel_name,
- id: channel_id
+ name: channelName,
+ id: channelId
});
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_TEAM,
- id: team_id
+ id: teamId
});
// ChannelLoader must be rendered first
@@ -66,12 +66,15 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <Navbar teamDisplayName={team_name} />,
+ <Navbar teamDisplayName={teamName} />,
document.getElementById('navbar')
);
React.render(
- <Sidebar teamDisplayName={team_name} teamType={team_type} />,
+ <Sidebar
+ teamDisplayName={teamName}
+ teamType={teamType}
+ />,
document.getElementById('sidebar-left')
);
@@ -86,17 +89,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <TeamSettingsModal teamDisplayName={team_name} />,
+ <TeamSettingsModal teamDisplayName={teamName} />,
document.getElementById('team_settings_modal')
);
React.render(
- <TeamMembersModal teamDisplayName={team_name} />,
+ <TeamMembersModal teamDisplayName={teamName} />,
document.getElementById('team_members_modal')
);
React.render(
- <MemberInviteModal teamType={team_type} />,
+ <MemberInviteModal teamType={teamType} />,
document.getElementById('invite_member_modal')
);
@@ -186,7 +189,10 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <SidebarRightMenu teamDisplayName={team_name} teamType={team_type} />,
+ <SidebarRightMenu
+ teamDisplayName={teamName}
+ teamType={teamType}
+ />,
document.getElementById('sidebar-menu')
);
@@ -222,8 +228,10 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
React.render(
<FileUploadOverlay
- overlayType='center' />,
+ overlayType='center'
+ />,
document.getElementById('file_upload_overlay')
);
+}
-};
+global.window.setup_channel_page = setupChannelPage;
diff --git a/web/react/pages/find_team.jsx b/web/react/pages/find_team.jsx
index 5346c0cf0..dd11857ac 100644
--- a/web/react/pages/find_team.jsx
+++ b/web/react/pages/find_team.jsx
@@ -3,11 +3,11 @@
var FindTeam = require('../components/find_team.jsx');
-global.window.setup_find_team_page = function() {
-
+function setupFindTeamPage() {
React.render(
<FindTeam />,
document.getElementById('find-team')
);
+}
-};
+global.window.setup_find_team_page = setupFindTeamPage;
diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx
index b12fa4949..18553542c 100644
--- a/web/react/pages/home.jsx
+++ b/web/react/pages/home.jsx
@@ -2,14 +2,15 @@
// See License.txt for license information.
var ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
var Constants = require('../utils/constants.jsx');
-global.window.setup_home_page = function(teamURL) {
+function setupHomePage(teamURL) {
var last = ChannelStore.getLastVisitedName();
if (last == null || last.length === 0) {
- window.location = teamURL + "/channels/" + Constants.DEFAULT_CHANNEL;
+ window.location = teamURL + '/channels/' + Constants.DEFAULT_CHANNEL;
} else {
- window.location = teamURL + "/channels/" + last;
+ window.location = teamURL + '/channels/' + last;
}
}
+
+global.window.setup_home_page = setupHomePage;
diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx
index 6e7528373..424ae0e84 100644
--- a/web/react/pages/login.jsx
+++ b/web/react/pages/login.jsx
@@ -3,9 +3,15 @@
var Login = require('../components/login.jsx');
-global.window.setup_login_page = function(team_display_name, team_name, auth_services) {
+function setupLoginPage(teamDisplayName, teamName, authServices) {
React.render(
- <Login teamDisplayName={team_display_name} teamName={team_name} authServices={auth_services} />,
+ <Login
+ teamDisplayName={teamDisplayName}
+ teamName={teamName}
+ authServices={authServices}
+ />,
document.getElementById('login')
);
-};
+}
+
+global.window.setup_login_page = setupLoginPage;
diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx
index c7a208973..2ca468bea 100644
--- a/web/react/pages/password_reset.jsx
+++ b/web/react/pages/password_reset.jsx
@@ -3,17 +3,17 @@
var PasswordReset = require('../components/password_reset.jsx');
-global.window.setup_password_reset_page = function(is_reset, team_display_name, team_name, hash, data) {
-
+function setupPasswordResetPage(isReset, teamDisplayName, teamName, hash, data) {
React.render(
<PasswordReset
- isReset={is_reset}
- teamDisplayName={team_display_name}
- teamName={team_name}
+ isReset={isReset}
+ teamDisplayName={teamDisplayName}
+ teamName={teamName}
hash={hash}
data={data}
/>,
document.getElementById('reset')
);
+}
-};
+global.window.setup_password_reset_page = setupPasswordResetPage;
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index 4b58025ac..e9e803aa4 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -5,7 +5,7 @@ var SignupTeam = require('../components/signup_team.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-global.window.setup_signup_team_page = function(authServices) {
+function setupSignupTeamPage(authServices) {
AsyncClient.getConfig();
var services = JSON.parse(authServices);
@@ -14,4 +14,6 @@ global.window.setup_signup_team_page = function(authServices) {
<SignupTeam services={services} />,
document.getElementById('signup-team')
);
-};
+}
+
+global.window.setup_signup_team_page = setupSignupTeamPage;
diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx
index 71806c2ea..72f9992a8 100644
--- a/web/react/pages/signup_team_complete.jsx
+++ b/web/react/pages/signup_team_complete.jsx
@@ -1,11 +1,17 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SignupTeamComplete =require('../components/signup_team_complete.jsx');
+var SignupTeamComplete = require('../components/signup_team_complete.jsx');
-global.window.setup_signup_team_complete_page = function(email, data, hash) {
+function setupSignupTeamCompletePage(email, data, hash) {
React.render(
- <SignupTeamComplete email={email} hash={hash} data={data}/>,
+ <SignupTeamComplete
+ email={email}
+ hash={hash}
+ data={data}
+ />,
document.getElementById('signup-team-complete')
);
-};
+}
+
+global.window.setup_signup_team_complete_page = setupSignupTeamCompletePage;
diff --git a/web/react/pages/signup_user_complete.jsx b/web/react/pages/signup_user_complete.jsx
index 8f9be1f94..eaf93a61c 100644
--- a/web/react/pages/signup_user_complete.jsx
+++ b/web/react/pages/signup_user_complete.jsx
@@ -3,9 +3,19 @@
var SignupUserComplete = require('../components/signup_user_complete.jsx');
-global.window.setup_signup_user_complete_page = function(email, name, ui_name, id, data, hash, auth_services) {
+function setupSignupUserCompletePage(email, name, uiName, id, data, hash, authServices) {
React.render(
- <SignupUserComplete teamId={id} teamName={name} teamDisplayName={ui_name} email={email} hash={hash} data={data} authServices={auth_services} />,
+ <SignupUserComplete
+ teamId={id}
+ teamName={name}
+ teamDisplayName={uiName}
+ email={email}
+ hash={hash}
+ data={data}
+ authServices={authServices}
+ />,
document.getElementById('signup-user-complete')
);
-};
+}
+
+global.window.setup_signup_user_complete_page = setupSignupUserCompletePage;
diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx
index 96b556983..7077b40b8 100644
--- a/web/react/pages/verify.jsx
+++ b/web/react/pages/verify.jsx
@@ -5,7 +5,11 @@ var EmailVerify = require('../components/email_verify.jsx');
global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) {
React.render(
- <EmailVerify isVerified={isVerified} teamURL={teamURL} userEmail={userEmail} />,
+ <EmailVerify
+ isVerified={isVerified}
+ teamURL={teamURL}
+ userEmail={userEmail}
+ />,
document.getElementById('verify')
);
};
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index b1f51e5f4..e1ca52746 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -12,81 +12,70 @@ function getPrefix() {
// Also change model/utils.go ETAG_ROOT_VERSION
var BROWSER_STORE_VERSION = '.5';
-module.exports = {
- initialized: false,
+class BrowserStoreClass {
+ constructor() {
+ this.getItem = this.getItem.bind(this);
+ this.setItem = this.setItem.bind(this);
+ this.removeItem = this.removeItem.bind(this);
+ this.setGlobalItem = this.setGlobalItem.bind(this);
+ this.getGlobalItem = this.getGlobalItem.bind(this);
+ this.removeGlobalItem = this.removeGlobalItem.bind(this);
+ this.clear = this.clear.bind(this);
+ this.actionOnItemsWithPrefix = this.actionOnItemsWithPrefix.bind(this);
+ this.isLocalStorageSupported = this.isLocalStorageSupported.bind(this);
- initialize: function() {
var currentVersion = localStorage.getItem('local_storage_version');
if (currentVersion !== BROWSER_STORE_VERSION) {
this.clear();
localStorage.setItem('local_storage_version', BROWSER_STORE_VERSION);
}
- this.initialized = true;
- },
+ }
- getItem: function(name, defaultValue) {
+ getItem(name, defaultValue) {
return this.getGlobalItem(getPrefix() + name, defaultValue);
- },
+ }
- setItem: function(name, value) {
+ setItem(name, value) {
this.setGlobalItem(getPrefix() + name, value);
- },
-
- removeItem: function(name) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ removeItem(name) {
localStorage.removeItem(getPrefix() + name);
- },
-
- setGlobalItem: function(name, value) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ setGlobalItem(name, value) {
localStorage.setItem(name, JSON.stringify(value));
- },
-
- getGlobalItem: function(name, defaultValue) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ getGlobalItem(name, defaultValue) {
var result = null;
try {
result = JSON.parse(localStorage.getItem(name));
- } catch (err) {}
+ } catch (err) {
+ result = null;
+ }
if (result === null && typeof defaultValue !== 'undefined') {
result = defaultValue;
}
return result;
- },
-
- removeGlobalItem: function(name) {
- if (!this.initialized) {
- this.initialize();
- }
+ }
+ removeGlobalItem(name) {
localStorage.removeItem(name);
- },
+ }
- clear: function() {
+ clear() {
localStorage.clear();
sessionStorage.clear();
- },
+ }
/**
* Preforms the given action on each item that has the given prefix
* Signature for action is action(key, value)
*/
- actionOnItemsWithPrefix: function(prefix, action) {
- if (!this.initialized) {
- this.initialize();
- }
-
+ actionOnItemsWithPrefix(prefix, action) {
var globalPrefix = getPrefix();
var globalPrefixiLen = globalPrefix.length;
for (var key in localStorage) {
@@ -95,9 +84,9 @@ module.exports = {
action(userkey, this.getGlobalItem(key));
}
}
- },
+ }
- isLocalStorageSupported: function() {
+ isLocalStorageSupported() {
try {
sessionStorage.setItem('testSession', '1');
sessionStorage.removeItem('testSession');
@@ -113,4 +102,7 @@ module.exports = {
return false;
}
}
-};
+}
+
+var BrowserStore = new BrowserStoreClass();
+export default BrowserStore;
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index f7c23841c..bd655b767 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -14,36 +13,42 @@ var CHANGE_EVENT = 'change';
var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
-var ChannelStore = assign({}, EventEmitter.prototype, {
- currentId: null,
- emitChange: function() {
+class ChannelStoreClass extends EventEmitter {
+ constructor(props) {
+ super(props);
+
+ this.setMaxListeners(11);
+
+ this.currentId = null;
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- emitMoreChange: function() {
+ }
+ emitMoreChange() {
this.emit(MORE_CHANGE_EVENT);
- },
- addMoreChangeListener: function(callback) {
+ }
+ addMoreChangeListener(callback) {
this.on(MORE_CHANGE_EVENT, callback);
- },
- removeMoreChangeListener: function(callback) {
+ }
+ removeMoreChangeListener(callback) {
this.removeListener(MORE_CHANGE_EVENT, callback);
- },
- emitExtraInfoChange: function() {
+ }
+ emitExtraInfoChange() {
this.emit(EXTRA_INFO_EVENT);
- },
- addExtraInfoChangeListener: function(callback) {
+ }
+ addExtraInfoChangeListener(callback) {
this.on(EXTRA_INFO_EVENT, callback);
- },
- removeExtraInfoChangeListener: function(callback) {
+ }
+ removeExtraInfoChangeListener(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
- },
- findFirstBy: function(field, value) {
+ }
+ findFirstBy(field, value) {
var channels = this.pGetChannels();
for (var i = 0; i < channels.length; i++) {
if (channels[i][field] === value) {
@@ -52,39 +57,39 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return null;
- },
- get: function(id) {
+ }
+ get(id) {
return this.findFirstBy('id', id);
- },
- getMember: function(id) {
+ }
+ getMember(id) {
return this.getAllMembers()[id];
- },
- getByName: function(name) {
+ }
+ getByName(name) {
return this.findFirstBy('name', name);
- },
- getAll: function() {
+ }
+ getAll() {
return this.pGetChannels();
- },
- getAllMembers: function() {
+ }
+ getAllMembers() {
return this.pGetChannelMembers();
- },
- getMoreAll: function() {
+ }
+ getMoreAll() {
return this.pGetMoreChannels();
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
this.currentId = id;
- },
- setLastVisitedName: function(name) {
+ }
+ setLastVisitedName(name) {
if (name == null) {
BrowserStore.removeItem('last_visited_name');
} else {
BrowserStore.setItem('last_visited_name', name);
}
- },
- getLastVisitedName: function() {
+ }
+ getLastVisitedName() {
return BrowserStore.getItem('last_visited_name');
- },
- resetCounts: function(id) {
+ }
+ resetCounts(id) {
var cm = this.pGetChannelMembers();
for (var cmid in cm) {
if (cm[cmid].channel_id === id) {
@@ -97,36 +102,36 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
}
this.pStoreChannelMembers(cm);
- },
- getCurrentId: function() {
+ }
+ getCurrentId() {
return this.currentId;
- },
- getCurrent: function() {
+ }
+ getCurrent() {
var currentId = this.getCurrentId();
if (currentId) {
return this.get(currentId);
- } else {
- return null;
}
- },
- getCurrentMember: function() {
- var currentId = ChannelStore.getCurrentId();
+
+ return null;
+ }
+ getCurrentMember() {
+ var currentId = this.getCurrentId();
if (currentId) {
return this.getAllMembers()[currentId];
- } else {
- return null;
}
- },
- setChannelMember: function(member) {
+
+ return null;
+ }
+ setChannelMember(member) {
var members = this.pGetChannelMembers();
members[member.channel_id] = member;
this.pStoreChannelMembers(members);
this.emitChange();
- },
- getCurrentExtraInfo: function() {
- var currentId = ChannelStore.getCurrentId();
+ }
+ getCurrentExtraInfo() {
+ var currentId = this.getCurrentId();
var extra = null;
if (currentId) {
@@ -138,8 +143,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return extra;
- },
- getExtraInfo: function(channelId) {
+ }
+ getExtraInfo(channelId) {
var extra = null;
if (channelId) {
@@ -151,8 +156,8 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return extra;
- },
- pStoreChannel: function(channel) {
+ }
+ pStoreChannel(channel) {
var channels = this.pGetChannels();
var found;
@@ -179,28 +184,28 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
});
this.pStoreChannels(channels);
- },
- pStoreChannels: function(channels) {
+ }
+ pStoreChannels(channels) {
BrowserStore.setItem('channels', channels);
- },
- pGetChannels: function() {
+ }
+ pGetChannels() {
return BrowserStore.getItem('channels', []);
- },
- pStoreChannelMember: function(channelMember) {
+ }
+ pStoreChannelMember(channelMember) {
var members = this.pGetChannelMembers();
members[channelMember.channel_id] = channelMember;
this.pStoreChannelMembers(members);
- },
- pStoreChannelMembers: function(channelMembers) {
+ }
+ pStoreChannelMembers(channelMembers) {
BrowserStore.setItem('channel_members', channelMembers);
- },
- pGetChannelMembers: function() {
+ }
+ pGetChannelMembers() {
return BrowserStore.getItem('channel_members', {});
- },
- pStoreMoreChannels: function(channels) {
+ }
+ pStoreMoreChannels(channels) {
BrowserStore.setItem('more_channels', channels);
- },
- pGetMoreChannels: function() {
+ }
+ pGetMoreChannels() {
var channels = BrowserStore.getItem('more_channels');
if (channels == null) {
@@ -209,65 +214,67 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
}
return channels;
- },
- pStoreExtraInfos: function(extraInfos) {
+ }
+ pStoreExtraInfos(extraInfos) {
BrowserStore.setItem('extra_infos', extraInfos);
- },
- pGetExtraInfos: function() {
+ }
+ pGetExtraInfos() {
return BrowserStore.getItem('extra_infos', {});
- },
- isDefault: function(channel) {
+ }
+ isDefault(channel) {
return channel.name === Constants.DEFAULT_CHANNEL;
}
-});
+}
+
+var ChannelStore = new ChannelStoreClass();
-ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
+ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payload) {
var action = payload.action;
var currentId;
- switch(action.type) {
-
- case ActionTypes.CLICK_CHANNEL:
- ChannelStore.setCurrentId(action.id);
- ChannelStore.setLastVisitedName(action.name);
- ChannelStore.resetCounts(action.id);
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNELS:
- ChannelStore.pStoreChannels(action.channels);
- ChannelStore.pStoreChannelMembers(action.members);
- currentId = ChannelStore.getCurrentId();
- if (currentId) {
- ChannelStore.resetCounts(currentId);
- }
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNEL:
- ChannelStore.pStoreChannel(action.channel);
- ChannelStore.pStoreChannelMember(action.member);
- currentId = ChannelStore.getCurrentId();
- if (currentId) {
- ChannelStore.resetCounts(currentId);
- }
- ChannelStore.emitChange();
- break;
+ switch (action.type) {
+ case ActionTypes.CLICK_CHANNEL:
+ ChannelStore.setCurrentId(action.id);
+ ChannelStore.setLastVisitedName(action.name);
+ ChannelStore.resetCounts(action.id);
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNELS:
+ ChannelStore.pStoreChannels(action.channels);
+ ChannelStore.pStoreChannelMembers(action.members);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNEL:
+ ChannelStore.pStoreChannel(action.channel);
+ ChannelStore.pStoreChannelMember(action.member);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
- case ActionTypes.RECIEVED_MORE_CHANNELS:
- ChannelStore.pStoreMoreChannels(action.channels);
- ChannelStore.emitMoreChange();
- break;
+ case ActionTypes.RECIEVED_MORE_CHANNELS:
+ ChannelStore.pStoreMoreChannels(action.channels);
+ ChannelStore.emitMoreChange();
+ break;
- case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
- var extraInfos = ChannelStore.pGetExtraInfos();
- extraInfos[action.extra_info.id] = action.extra_info;
- ChannelStore.pStoreExtraInfos(extraInfos);
- ChannelStore.emitExtraInfoChange();
- break;
+ case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
+ var extraInfos = ChannelStore.pGetExtraInfos();
+ extraInfos[action.extra_info.id] = action.extra_info;
+ ChannelStore.pStoreExtraInfos(extraInfos);
+ ChannelStore.emitExtraInfoChange();
+ break;
- default:
+ default:
+ break;
}
});
-module.exports = ChannelStore;
+export default ChannelStore;
diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx
index 7ff177b35..b397937be 100644
--- a/web/react/stores/config_store.jsx
+++ b/web/react/stores/config_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var BrowserStore = require('../stores/browser_store.jsx');
@@ -12,45 +11,59 @@ var ActionTypes = Constants.ActionTypes;
var CHANGE_EVENT = 'change';
-var ConfigStore = assign({}, EventEmitter.prototype, {
- emitChange: function emitChange() {
+class ConfigStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.getSetting = this.getSetting.bind(this);
+ this.getSettingAsBoolean = this.getSettingAsBoolean.bind(this);
+ this.updateStoredSettings = this.updateStoredSettings.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function addChangeListener(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function removeChangeListener(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- getSetting: function getSetting(key, defaultValue) {
+ }
+ getSetting(key, defaultValue) {
return BrowserStore.getItem('config_' + key, defaultValue);
- },
- getSettingAsBoolean: function getSettingAsNumber(key, defaultValue) {
- var value = ConfigStore.getSetting(key, defaultValue);
+ }
+ getSettingAsBoolean(key, defaultValue) {
+ var value = this.getSetting(key, defaultValue);
if (typeof value !== 'string') {
- return !!value;
- } else {
- return value === 'true';
+ return Boolean(value);
}
- },
- updateStoredSettings: function updateStoredSettings(settings) {
- for (var key in settings) {
- BrowserStore.setItem('config_' + key, settings[key]);
+
+ return value === 'true';
+ }
+ updateStoredSettings(settings) {
+ for (let key in settings) {
+ if (settings.hasOwnProperty(key)) {
+ BrowserStore.setItem('config_' + key, settings[key]);
+ }
}
}
-});
+}
+
+var ConfigStore = new ConfigStoreClass();
ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_CONFIG:
- ConfigStore.updateStoredSettings(action.settings);
- ConfigStore.emitChange();
- break;
- default:
+ case ActionTypes.RECIEVED_CONFIG:
+ ConfigStore.updateStoredSettings(action.settings);
+ ConfigStore.emitChange();
+ break;
+ default:
}
});
-module.exports = ConfigStore;
+export default ConfigStore;
diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx
index 203b692ec..597c88cff 100644
--- a/web/react/stores/error_store.jsx
+++ b/web/react/stores/error_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -12,43 +11,53 @@ var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change';
-var ErrorStore = assign({}, EventEmitter.prototype, {
-
- emitChange: function() {
- this.emit(CHANGE_EVENT);
- },
-
- addChangeListener: function(callback) {
- this.on(CHANGE_EVENT, callback);
- },
-
- removeChangeListener: function(callback) {
- this.removeListener(CHANGE_EVENT, callback);
- },
- handledError: function() {
- BrowserStore.removeItem("last_error");
- },
- getLastError: function() {
- return BrowserStore.getItem('last_error');
- },
-
- _storeLastError: function(error) {
- BrowserStore.setItem("last_error", error);
- },
-});
-
-ErrorStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
- switch(action.type) {
+class ErrorStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.handledError = this.handledError.bind(this);
+ this.getLastError = this.getLastError.bind(this);
+ this.storeLastError = this.storeLastError.bind(this);
+ }
+
+ emitChange() {
+ this.emit(CHANGE_EVENT);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+ handledError() {
+ BrowserStore.removeItem('last_error');
+ }
+ getLastError() {
+ return BrowserStore.getItem('last_error');
+ }
+
+ storeLastError(error) {
+ BrowserStore.setItem('last_error', error);
+ }
+}
+
+var ErrorStore = new ErrorStoreClass();
+
+ErrorStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
+ switch (action.type) {
case ActionTypes.RECIEVED_ERROR:
- ErrorStore._storeLastError(action.err);
- ErrorStore.emitChange();
- break;
+ ErrorStore.storeLastError(action.err);
+ ErrorStore.emitChange();
+ break;
default:
- }
+ }
});
-module.exports = ErrorStore;
-
-
+export default ErrorStore;
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 4038814d2..5ffe65021 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var ChannelStore = require('../stores/channel_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
@@ -18,109 +17,169 @@ var SELECTED_POST_CHANGE_EVENT = 'selected_post_change';
var MENTION_DATA_CHANGE_EVENT = 'mention_data_change';
var ADD_MENTION_EVENT = 'add_mention';
-var PostStore = assign({}, EventEmitter.prototype, {
- emitChange: function emitChange() {
+class PostStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.emitSearchChange = this.emitSearchChange.bind(this);
+ this.addSearchChangeListener = this.addSearchChangeListener.bind(this);
+ this.removeSearchChangeListener = this.removeSearchChangeListener.bind(this);
+ this.emitSearchTermChange = this.emitSearchTermChange.bind(this);
+ this.addSearchTermChangeListener = this.addSearchTermChangeListener.bind(this);
+ this.removeSearchTermChangeListener = this.removeSearchTermChangeListener.bind(this);
+ this.emitSelectedPostChange = this.emitSelectedPostChange.bind(this);
+ this.addSelectedPostChangeListener = this.addSelectedPostChangeListener.bind(this);
+ this.removeSelectedPostChangeListener = this.removeSelectedPostChangeListener.bind(this);
+ this.emitMentionDataChange = this.emitMentionDataChange.bind(this);
+ this.addMentionDataChangeListener = this.addMentionDataChangeListener.bind(this);
+ this.removeMentionDataChangeListener = this.removeMentionDataChangeListener.bind(this);
+ this.emitAddMention = this.emitAddMention.bind(this);
+ this.addAddMentionListener = this.addAddMentionListener.bind(this);
+ this.removeAddMentionListener = this.removeAddMentionListener.bind(this);
+ this.getCurrentPosts = this.getCurrentPosts.bind(this);
+ this.storePosts = this.storePosts.bind(this);
+ this.pStorePosts = this.pStorePosts.bind(this);
+ this.getPosts = this.getPosts.bind(this);
+ this.storePost = this.storePost.bind(this);
+ this.pStorePost = this.pStorePost.bind(this);
+ this.removePost = this.removePost.bind(this);
+ this.storePendingPost = this.storePendingPost.bind(this);
+ this.pStorePendingPosts = this.pStorePendingPosts.bind(this);
+ this.getPendingPosts = this.getPendingPosts.bind(this);
+ this.storeUnseenDeletedPost = this.storeUnseenDeletedPost.bind(this);
+ this.storeUnseenDeletedPosts = this.storeUnseenDeletedPosts.bind(this);
+ this.getUnseenDeletedPosts = this.getUnseenDeletedPosts.bind(this);
+ this.clearUnseenDeletedPosts = this.clearUnseenDeletedPosts.bind(this);
+ this.removePendingPost = this.removePendingPost.bind(this);
+ this.pRemovePendingPost = this.pRemovePendingPost.bind(this);
+ this.clearPendingPosts = this.clearPendingPosts.bind(this);
+ this.updatePendingPost = this.updatePendingPost.bind(this);
+ this.storeSearchResults = this.storeSearchResults.bind(this);
+ this.getSearchResults = this.getSearchResults.bind(this);
+ this.getIsMentionSearch = this.getIsMentionSearch.bind(this);
+ this.storeSelectedPost = this.storeSelectedPost.bind(this);
+ this.getSelectedPost = this.getSelectedPost.bind(this);
+ this.storeSearchTerm = this.storeSearchTerm.bind(this);
+ this.getSearchTerm = this.getSearchTerm.bind(this);
+ this.getEmptyDraft = this.getEmptyDraft.bind(this);
+ this.storeCurrentDraft = this.storeCurrentDraft.bind(this);
+ this.getCurrentDraft = this.getCurrentDraft.bind(this);
+ this.storeDraft = this.storeDraft.bind(this);
+ this.getDraft = this.getDraft.bind(this);
+ this.storeCommentDraft = this.storeCommentDraft.bind(this);
+ this.getCommentDraft = this.getCommentDraft.bind(this);
+ this.clearDraftUploads = this.clearDraftUploads.bind(this);
+ this.clearCommentDraftUploads = this.clearCommentDraftUploads.bind(this);
+ this.storeLatestUpdate = this.storeLatestUpdate.bind(this);
+ this.getLatestUpdate = this.getLatestUpdate.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
+ }
- addChangeListener: function addChangeListener(callback) {
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
+ }
- removeChangeListener: function removeChangeListener(callback) {
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
+ }
- emitSearchChange: function emitSearchChange() {
+ emitSearchChange() {
this.emit(SEARCH_CHANGE_EVENT);
- },
+ }
- addSearchChangeListener: function addSearchChangeListener(callback) {
+ addSearchChangeListener(callback) {
this.on(SEARCH_CHANGE_EVENT, callback);
- },
+ }
- removeSearchChangeListener: function removeSearchChangeListener(callback) {
+ removeSearchChangeListener(callback) {
this.removeListener(SEARCH_CHANGE_EVENT, callback);
- },
+ }
- emitSearchTermChange: function emitSearchTermChange(doSearch, isMentionSearch) {
+ emitSearchTermChange(doSearch, isMentionSearch) {
this.emit(SEARCH_TERM_CHANGE_EVENT, doSearch, isMentionSearch);
- },
+ }
- addSearchTermChangeListener: function addSearchTermChangeListener(callback) {
+ addSearchTermChangeListener(callback) {
this.on(SEARCH_TERM_CHANGE_EVENT, callback);
- },
+ }
- removeSearchTermChangeListener: function removeSearchTermChangeListener(callback) {
+ removeSearchTermChangeListener(callback) {
this.removeListener(SEARCH_TERM_CHANGE_EVENT, callback);
- },
+ }
- emitSelectedPostChange: function emitSelectedPostChange(fromSearch) {
+ emitSelectedPostChange(fromSearch) {
this.emit(SELECTED_POST_CHANGE_EVENT, fromSearch);
- },
+ }
- addSelectedPostChangeListener: function addSelectedPostChangeListener(callback) {
+ addSelectedPostChangeListener(callback) {
this.on(SELECTED_POST_CHANGE_EVENT, callback);
- },
+ }
- removeSelectedPostChangeListener: function removeSelectedPostChangeListener(callback) {
+ removeSelectedPostChangeListener(callback) {
this.removeListener(SELECTED_POST_CHANGE_EVENT, callback);
- },
+ }
- emitMentionDataChange: function emitMentionDataChange(id, mentionText) {
+ emitMentionDataChange(id, mentionText) {
this.emit(MENTION_DATA_CHANGE_EVENT, id, mentionText);
- },
+ }
- addMentionDataChangeListener: function addMentionDataChangeListener(callback) {
+ addMentionDataChangeListener(callback) {
this.on(MENTION_DATA_CHANGE_EVENT, callback);
- },
+ }
- removeMentionDataChangeListener: function removeMentionDataChangeListener(callback) {
+ removeMentionDataChangeListener(callback) {
this.removeListener(MENTION_DATA_CHANGE_EVENT, callback);
- },
+ }
- emitAddMention: function emitAddMention(id, username) {
+ emitAddMention(id, username) {
this.emit(ADD_MENTION_EVENT, id, username);
- },
+ }
- addAddMentionListener: function addAddMentionListener(callback) {
+ addAddMentionListener(callback) {
this.on(ADD_MENTION_EVENT, callback);
- },
+ }
- removeAddMentionListener: function removeAddMentionListener(callback) {
+ removeAddMentionListener(callback) {
this.removeListener(ADD_MENTION_EVENT, callback);
- },
+ }
- getCurrentPosts: function getCurrentPosts() {
+ getCurrentPosts() {
var currentId = ChannelStore.getCurrentId();
if (currentId != null) {
return this.getPosts(currentId);
}
return null;
- },
- storePosts: function storePosts(channelId, newPostList) {
+ }
+ storePosts(channelId, newPostList) {
if (isPostListNull(newPostList)) {
return;
}
- var postList = makePostListNonNull(PostStore.getPosts(channelId));
-
- for (var pid in newPostList.posts) {
- var np = newPostList.posts[pid];
- if (np.delete_at === 0) {
- postList.posts[pid] = np;
- if (postList.order.indexOf(pid) === -1) {
- postList.order.push(pid);
- }
- } else {
- if (pid in postList.posts) {
- delete postList.posts[pid];
- }
-
- var index = postList.order.indexOf(pid);
- if (index !== -1) {
- postList.order.splice(index, 1);
+ var postList = makePostListNonNull(this.getPosts(channelId));
+
+ for (let pid in newPostList.posts) {
+ if (newPostList.posts.hasOwnProperty(pid)) {
+ var np = newPostList.posts[pid];
+ if (np.delete_at === 0) {
+ postList.posts[pid] = np;
+ if (postList.order.indexOf(pid) === -1) {
+ postList.order.push(pid);
+ }
+ } else {
+ if (pid in postList.posts) {
+ delete postList.posts[pid];
+ }
+
+ var index = postList.order.indexOf(pid);
+ if (index !== -1) {
+ postList.order.splice(index, 1);
+ }
}
}
}
@@ -146,19 +205,19 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.storeLatestUpdate(channelId, latestUpdate);
this.pStorePosts(channelId, postList);
this.emitChange();
- },
- pStorePosts: function pStorePosts(channelId, posts) {
+ }
+ pStorePosts(channelId, posts) {
BrowserStore.setItem('posts_' + channelId, posts);
- },
- getPosts: function getPosts(channelId) {
+ }
+ getPosts(channelId) {
return BrowserStore.getItem('posts_' + channelId);
- },
- storePost: function(post) {
+ }
+ storePost(post) {
this.pStorePost(post);
this.emitChange();
- },
- pStorePost: function(post) {
- var postList = PostStore.getPosts(post.channel_id);
+ }
+ pStorePost(post) {
+ var postList = this.getPosts(post.channel_id);
postList = makePostListNonNull(postList);
if (post.pending_post_id !== '') {
@@ -173,9 +232,9 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
this.pStorePosts(post.channel_id, postList);
- },
- removePost: function(postId, channelId) {
- var postList = PostStore.getPosts(channelId);
+ }
+ removePost(postId, channelId) {
+ var postList = this.getPosts(channelId);
if (isPostListNull(postList)) {
return;
}
@@ -190,8 +249,8 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
this.pStorePosts(channelId, postList);
- },
- storePendingPost: function(post) {
+ }
+ storePendingPost(post) {
post.state = Constants.POST_LOADING;
var postList = this.getPendingPosts(post.channel_id);
@@ -199,10 +258,10 @@ var PostStore = assign({}, EventEmitter.prototype, {
postList.posts[post.pending_post_id] = post;
postList.order.unshift(post.pending_post_id);
- this._storePendingPosts(post.channel_id, postList);
+ this.pStorePendingPosts(post.channel_id, postList);
this.emitChange();
- },
- _storePendingPosts: function(channelId, postList) {
+ }
+ pStorePendingPosts(channelId, postList) {
var posts = postList.posts;
// sort failed posts to the bottom
@@ -225,11 +284,11 @@ var PostStore = assign({}, EventEmitter.prototype, {
});
BrowserStore.setItem('pending_posts_' + channelId, postList);
- },
- getPendingPosts: function(channelId) {
+ }
+ getPendingPosts(channelId) {
return BrowserStore.getItem('pending_posts_' + channelId);
- },
- storeUnseenDeletedPost: function(post) {
+ }
+ storeUnseenDeletedPost(post) {
var posts = this.getUnseenDeletedPosts(post.channel_id);
if (!posts) {
@@ -241,21 +300,21 @@ var PostStore = assign({}, EventEmitter.prototype, {
posts[post.id] = post;
this.storeUnseenDeletedPosts(post.channel_id, posts);
- },
- storeUnseenDeletedPosts: function(channelId, posts) {
+ }
+ storeUnseenDeletedPosts(channelId, posts) {
BrowserStore.setItem('deleted_posts_' + channelId, posts);
- },
- getUnseenDeletedPosts: function(channelId) {
+ }
+ getUnseenDeletedPosts(channelId) {
return BrowserStore.getItem('deleted_posts_' + channelId);
- },
- clearUnseenDeletedPosts: function(channelId) {
+ }
+ clearUnseenDeletedPosts(channelId) {
BrowserStore.setItem('deleted_posts_' + channelId, {});
- },
- removePendingPost: function(channelId, pendingPostId) {
- this._removePendingPost(channelId, pendingPostId);
+ }
+ removePendingPost(channelId, pendingPostId) {
+ this.pRemovePendingPost(channelId, pendingPostId);
this.emitChange();
- },
- _removePendingPost: function(channelId, pendingPostId) {
+ }
+ pRemovePendingPost(channelId, pendingPostId) {
var postList = this.getPendingPosts(channelId);
postList = makePostListNonNull(postList);
@@ -267,14 +326,14 @@ var PostStore = assign({}, EventEmitter.prototype, {
postList.order.splice(index, 1);
}
- this._storePendingPosts(channelId, postList);
- },
- clearPendingPosts: function() {
+ this.pStorePendingPosts(channelId, postList);
+ }
+ clearPendingPosts() {
BrowserStore.actionOnItemsWithPrefix('pending_posts_', function clearPending(key) {
BrowserStore.removeItem(key);
});
- },
- updatePendingPost: function(post) {
+ }
+ updatePendingPost(post) {
var postList = this.getPendingPosts(post.channel_id);
postList = makePostListNonNull(postList);
@@ -283,112 +342,114 @@ var PostStore = assign({}, EventEmitter.prototype, {
}
postList.posts[post.pending_post_id] = post;
- this._storePendingPosts(post.channel_id, postList);
+ this.pStorePendingPosts(post.channel_id, postList);
this.emitChange();
- },
- storeSearchResults: function storeSearchResults(results, isMentionSearch) {
+ }
+ storeSearchResults(results, isMentionSearch) {
BrowserStore.setItem('search_results', results);
BrowserStore.setItem('is_mention_search', Boolean(isMentionSearch));
- },
- getSearchResults: function getSearchResults() {
+ }
+ getSearchResults() {
return BrowserStore.getItem('search_results');
- },
- getIsMentionSearch: function getIsMentionSearch() {
+ }
+ getIsMentionSearch() {
return BrowserStore.getItem('is_mention_search');
- },
- storeSelectedPost: function storeSelectedPost(postList) {
+ }
+ storeSelectedPost(postList) {
BrowserStore.setItem('select_post', postList);
- },
- getSelectedPost: function getSelectedPost() {
+ }
+ getSelectedPost() {
return BrowserStore.getItem('select_post');
- },
- storeSearchTerm: function storeSearchTerm(term) {
+ }
+ storeSearchTerm(term) {
BrowserStore.setItem('search_term', term);
- },
- getSearchTerm: function getSearchTerm() {
+ }
+ getSearchTerm() {
return BrowserStore.getItem('search_term');
- },
- getEmptyDraft: function getEmptyDraft(draft) {
+ }
+ getEmptyDraft() {
return {message: '', uploadsInProgress: [], previews: []};
- },
- storeCurrentDraft: function storeCurrentDraft(draft) {
+ }
+ storeCurrentDraft(draft) {
var channelId = ChannelStore.getCurrentId();
BrowserStore.setItem('draft_' + channelId, draft);
- },
- getCurrentDraft: function getCurrentDraft() {
+ }
+ getCurrentDraft() {
var channelId = ChannelStore.getCurrentId();
- return PostStore.getDraft(channelId);
- },
- storeDraft: function storeDraft(channelId, draft) {
+ return this.getDraft(channelId);
+ }
+ storeDraft(channelId, draft) {
BrowserStore.setItem('draft_' + channelId, draft);
- },
- getDraft: function getDraft(channelId) {
- return BrowserStore.getItem('draft_' + channelId, PostStore.getEmptyDraft());
- },
- storeCommentDraft: function storeCommentDraft(parentPostId, draft) {
+ }
+ getDraft(channelId) {
+ return BrowserStore.getItem('draft_' + channelId, this.getEmptyDraft());
+ }
+ storeCommentDraft(parentPostId, draft) {
BrowserStore.setItem('comment_draft_' + parentPostId, draft);
- },
- getCommentDraft: function getCommentDraft(parentPostId) {
- return BrowserStore.getItem('comment_draft_' + parentPostId, PostStore.getEmptyDraft());
- },
- clearDraftUploads: function clearDraftUploads() {
+ }
+ getCommentDraft(parentPostId) {
+ return BrowserStore.getItem('comment_draft_' + parentPostId, this.getEmptyDraft());
+ }
+ clearDraftUploads() {
BrowserStore.actionOnItemsWithPrefix('draft_', function clearUploads(key, value) {
if (value) {
value.uploadsInProgress = [];
BrowserStore.setItem(key, value);
}
});
- },
- clearCommentDraftUploads: function clearCommentDraftUploads() {
+ }
+ clearCommentDraftUploads() {
BrowserStore.actionOnItemsWithPrefix('comment_draft_', function clearUploads(key, value) {
if (value) {
value.uploadsInProgress = [];
BrowserStore.setItem(key, value);
}
});
- },
- storeLatestUpdate: function(channelId, time) {
+ }
+ storeLatestUpdate(channelId, time) {
BrowserStore.setItem('latest_post_' + channelId, time);
- },
- getLatestUpdate: function(channelId) {
+ }
+ getLatestUpdate(channelId) {
return BrowserStore.getItem('latest_post_' + channelId, 0);
}
-});
+}
+
+var PostStore = new PostStoreClass();
PostStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_POSTS:
- PostStore.storePosts(action.id, makePostListNonNull(action.post_list));
- break;
- case ActionTypes.RECIEVED_POST:
- PostStore.pStorePost(action.post);
- PostStore.emitChange();
- break;
- case ActionTypes.RECIEVED_SEARCH:
- PostStore.storeSearchResults(action.results, action.is_mention_search);
- PostStore.emitSearchChange();
- break;
- case ActionTypes.RECIEVED_SEARCH_TERM:
- PostStore.storeSearchTerm(action.term);
- PostStore.emitSearchTermChange(action.do_search, action.is_mention_search);
- break;
- case ActionTypes.RECIEVED_POST_SELECTED:
- PostStore.storeSelectedPost(action.post_list);
- PostStore.emitSelectedPostChange(action.from_search);
- break;
- case ActionTypes.RECIEVED_MENTION_DATA:
- PostStore.emitMentionDataChange(action.id, action.mention_text);
- break;
- case ActionTypes.RECIEVED_ADD_MENTION:
- PostStore.emitAddMention(action.id, action.username);
- break;
- default:
+ case ActionTypes.RECIEVED_POSTS:
+ PostStore.storePosts(action.id, makePostListNonNull(action.post_list));
+ break;
+ case ActionTypes.RECIEVED_POST:
+ PostStore.pStorePost(action.post);
+ PostStore.emitChange();
+ break;
+ case ActionTypes.RECIEVED_SEARCH:
+ PostStore.storeSearchResults(action.results, action.is_mention_search);
+ PostStore.emitSearchChange();
+ break;
+ case ActionTypes.RECIEVED_SEARCH_TERM:
+ PostStore.storeSearchTerm(action.term);
+ PostStore.emitSearchTermChange(action.do_search, action.is_mention_search);
+ break;
+ case ActionTypes.RECIEVED_POST_SELECTED:
+ PostStore.storeSelectedPost(action.post_list);
+ PostStore.emitSelectedPostChange(action.from_search);
+ break;
+ case ActionTypes.RECIEVED_MENTION_DATA:
+ PostStore.emitMentionDataChange(action.id, action.mention_text);
+ break;
+ case ActionTypes.RECIEVED_ADD_MENTION:
+ PostStore.emitAddMention(action.id, action.username);
+ break;
+ default:
}
});
-module.exports = PostStore;
+export default PostStore;
function makePostListNonNull(pl) {
var postList = pl;
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index e43a8f2be..ae74059d1 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -2,10 +2,8 @@
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
-var UserStore = require('./user_store.jsx')
+var UserStore = require('./user_store.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
-var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -14,14 +12,24 @@ var CHANGE_EVENT = 'change';
var conn;
-var SocketStore = assign({}, EventEmitter.prototype, {
- initialize: function() {
+class SocketStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.initialize = this.initialize.bind(this);
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.sendMessage = this.sendMessage.bind(this);
+
+ this.initialize();
+ }
+ initialize() {
if (!UserStore.getCurrentId()) {
return;
}
- var self = this;
- self.setMaxListeners(0);
+ this.setMaxListeners(0);
if (window.WebSocket && !conn) {
var protocol = 'ws://';
@@ -29,24 +37,24 @@ var SocketStore = assign({}, EventEmitter.prototype, {
protocol = 'wss://';
}
var connUrl = protocol + location.host + '/api/v1/websocket';
- console.log('connecting to ' + connUrl);
+ console.log('connecting to ' + connUrl); //eslint-disable-line no-console
conn = new WebSocket(connUrl);
conn.onclose = function closeConn(evt) {
- console.log('websocket closed');
- console.log(evt);
+ console.log('websocket closed'); //eslint-disable-line no-console
+ console.log(evt); //eslint-disable-line no-console
conn = null;
setTimeout(
function reconnect() {
- self.initialize();
- },
+ this.initialize();
+ }.bind(this),
3000
);
- };
+ }.bind(this);
conn.onerror = function connError(evt) {
- console.log('websocket error');
- console.log(evt);
+ console.log('websocket error'); //eslint-disable-line no-console
+ console.log(evt); //eslint-disable-line no-console
};
conn.onmessage = function connMessage(evt) {
@@ -56,17 +64,17 @@ var SocketStore = assign({}, EventEmitter.prototype, {
});
};
}
- },
- emitChange: function(msg) {
+ }
+ emitChange(msg) {
this.emit(CHANGE_EVENT, msg);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- sendMessage: function(msg) {
+ }
+ sendMessage(msg) {
if (conn && conn.readyState === WebSocket.OPEN) {
conn.send(JSON.stringify(msg));
} else if (!conn || conn.readyState === WebSocket.Closed) {
@@ -74,19 +82,20 @@ var SocketStore = assign({}, EventEmitter.prototype, {
this.initialize();
}
}
-});
+}
+
+var SocketStore = new SocketStoreClass();
-SocketStore.dispatchToken = AppDispatcher.register(function(payload) {
+SocketStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_MSG:
+ case ActionTypes.RECIEVED_MSG:
SocketStore.emitChange(action.msg);
break;
- default:
+ default:
}
});
-SocketStore.initialize();
-module.exports = SocketStore;
+export default SocketStore;
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index 3f2248c44..1f33fe03b 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -19,21 +18,38 @@ function getWindowLocationOrigin() {
return utils.getWindowLocationOrigin();
}
-var TeamStore = assign({}, EventEmitter.prototype, {
- emitChange: function() {
+class TeamStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.get = this.get.bind(this);
+ this.getByName = this.getByName.bind(this);
+ this.getAll = this.getAll.bind(this);
+ this.setCurrentId = this.setCurrentId.bind(this);
+ this.getCurrentId = this.getCurrentId.bind(this);
+ this.getCurrent = this.getCurrent.bind(this);
+ this.getCurrentTeamUrl = this.getCurrentTeamUrl.bind(this);
+ this.storeTeam = this.storeTeam.bind(this);
+ this.pStoreTeams = this.pStoreTeams.bind(this);
+ this.pGetTeams = this.pGetTeams.bind(this);
+ }
+ emitChange() {
this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- get: function(id) {
+ }
+ get(id) {
var c = this.pGetTeams();
return c[id];
- },
- getByName: function(name) {
+ }
+ getByName(name) {
var t = this.pGetTeams();
for (var id in t) {
@@ -43,64 +59,65 @@ var TeamStore = assign({}, EventEmitter.prototype, {
}
return null;
- },
- getAll: function() {
+ }
+ getAll() {
return this.pGetTeams();
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
if (id === null) {
BrowserStore.removeItem('current_team_id');
} else {
BrowserStore.setItem('current_team_id', id);
}
- },
- getCurrentId: function() {
+ }
+ getCurrentId() {
return BrowserStore.getItem('current_team_id');
- },
- getCurrent: function() {
- var currentId = TeamStore.getCurrentId();
+ }
+ getCurrent() {
+ var currentId = this.getCurrentId();
if (currentId !== null) {
return this.get(currentId);
}
return null;
- },
- getCurrentTeamUrl: function() {
+ }
+ getCurrentTeamUrl() {
if (this.getCurrent()) {
return getWindowLocationOrigin() + '/' + this.getCurrent().name;
}
return null;
- },
- storeTeam: function(team) {
+ }
+ storeTeam(team) {
var teams = this.pGetTeams();
teams[team.id] = team;
this.pStoreTeams(teams);
- },
- pStoreTeams: function(teams) {
+ }
+ pStoreTeams(teams) {
BrowserStore.setItem('user_teams', teams);
- },
- pGetTeams: function() {
+ }
+ pGetTeams() {
return BrowserStore.getItem('user_teams', {});
}
-});
+}
+
+var TeamStore = new TeamStoreClass();
TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
+ case ActionTypes.CLICK_TEAM:
+ TeamStore.setCurrentId(action.id);
+ TeamStore.emitChange();
+ break;
- case ActionTypes.CLICK_TEAM:
- TeamStore.setCurrentId(action.id);
- TeamStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_TEAM:
- TeamStore.storeTeam(action.team);
- TeamStore.emitChange();
- break;
+ case ActionTypes.RECIEVED_TEAM:
+ TeamStore.storeTeam(action.team);
+ TeamStore.emitChange();
+ break;
- default:
+ default:
}
});
-module.exports = TeamStore;
+export default TeamStore;
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 248495dac..f75c1d4c3 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -3,7 +3,6 @@
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var EventEmitter = require('events').EventEmitter;
-var assign = require('object-assign');
var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx');
@@ -16,64 +15,114 @@ var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses';
-var UserStore = assign({}, EventEmitter.prototype, {
+class UserStoreClass extends EventEmitter {
+ constructor() {
+ super();
- gCurrentId: null,
+ this.emitChange = this.emitChange.bind(this);
+ this.addChangeListener = this.addChangeListener.bind(this);
+ this.removeChangeListener = this.removeChangeListener.bind(this);
+ this.emitSessionsChange = this.emitSessionsChange.bind(this);
+ this.addSessionsChangeListener = this.addSessionsChangeListener.bind(this);
+ this.removeSessionsChangeListener = this.removeSessionsChangeListener.bind(this);
+ this.emitAuditsChange = this.emitAuditsChange.bind(this);
+ this.addAuditsChangeListener = this.addAuditsChangeListener.bind(this);
+ this.removeAuditsChangeListener = this.removeAuditsChangeListener.bind(this);
+ this.emitTeamsChange = this.emitTeamsChange.bind(this);
+ this.addTeamsChangeListener = this.addTeamsChangeListener.bind(this);
+ this.removeTeamsChangeListener = this.removeTeamsChangeListener.bind(this);
+ this.emitStatusesChange = this.emitStatusesChange.bind(this);
+ this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
+ this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
+ this.setCurrentId = this.setCurrentId.bind(this);
+ this.getCurrentId = this.getCurrentId.bind(this);
+ this.getCurrentUser = this.getCurrentUser.bind(this);
+ this.setCurrentUser = this.setCurrentUser.bind(this);
+ this.getLastEmail = this.getLastEmail.bind(this);
+ this.setLastEmail = this.setLastEmail.bind(this);
+ this.removeCurrentUser = this.removeCurrentUser.bind(this);
+ this.hasProfile = this.hasProfile.bind(this);
+ this.getProfile = this.getProfile.bind(this);
+ this.getProfileByUsername = this.getProfileByUsername.bind(this);
+ this.getProfilesUsernameMap = this.getProfilesUsernameMap.bind(this);
+ this.getProfiles = this.getProfiles.bind(this);
+ this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this);
+ this.saveProfile = this.saveProfile.bind(this);
+ this.pStoreProfiles = this.pStoreProfiles.bind(this);
+ this.pGetProfiles = this.pGetProfiles.bind(this);
+ this.pGetProfilesUsernameMap = this.pGetProfilesUsernameMap.bind(this);
+ this.setSessions = this.setSessions.bind(this);
+ this.getSessions = this.getSessions.bind(this);
+ this.setAudits = this.setAudits.bind(this);
+ this.getAudits = this.getAudits.bind(this);
+ this.setTeams = this.setTeams.bind(this);
+ this.getTeams = this.getTeams.bind(this);
+ this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this);
+ this.getLastVersion = this.getLastVersion.bind(this);
+ this.setLastVersion = this.setLastVersion.bind(this);
+ this.setStatuses = this.setStatuses.bind(this);
+ this.pSetStatuses = this.pSetStatuses.bind(this);
+ this.setStatus = this.setStatus.bind(this);
+ this.getStatuses = this.getStatuses.bind(this);
+ this.getStatus = this.getStatus.bind(this);
- emitChange: function(userId) {
+ this.gCurrentId = null;
+ }
+
+ emitChange(userId) {
this.emit(CHANGE_EVENT, userId);
- },
- addChangeListener: function(callback) {
+ }
+ addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
+ }
+ removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
- },
- emitSessionsChange: function() {
+ }
+ emitSessionsChange() {
this.emit(CHANGE_EVENT_SESSIONS);
- },
- addSessionsChangeListener: function(callback) {
+ }
+ addSessionsChangeListener(callback) {
this.on(CHANGE_EVENT_SESSIONS, callback);
- },
- removeSessionsChangeListener: function(callback) {
+ }
+ removeSessionsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_SESSIONS, callback);
- },
- emitAuditsChange: function() {
+ }
+ emitAuditsChange() {
this.emit(CHANGE_EVENT_AUDITS);
- },
- addAuditsChangeListener: function(callback) {
+ }
+ addAuditsChangeListener(callback) {
this.on(CHANGE_EVENT_AUDITS, callback);
- },
- removeAuditsChangeListener: function(callback) {
+ }
+ removeAuditsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_AUDITS, callback);
- },
- emitTeamsChange: function() {
+ }
+ emitTeamsChange() {
this.emit(CHANGE_EVENT_TEAMS);
- },
- addTeamsChangeListener: function(callback) {
+ }
+ addTeamsChangeListener(callback) {
this.on(CHANGE_EVENT_TEAMS, callback);
- },
- removeTeamsChangeListener: function(callback) {
+ }
+ removeTeamsChangeListener(callback) {
this.removeListener(CHANGE_EVENT_TEAMS, callback);
- },
- emitStatusesChange: function() {
+ }
+ emitStatusesChange() {
this.emit(CHANGE_EVENT_STATUSES);
- },
- addStatusesChangeListener: function(callback) {
+ }
+ addStatusesChangeListener(callback) {
this.on(CHANGE_EVENT_STATUSES, callback);
- },
- removeStatusesChangeListener: function(callback) {
+ }
+ removeStatusesChangeListener(callback) {
this.removeListener(CHANGE_EVENT_STATUSES, callback);
- },
- setCurrentId: function(id) {
+ }
+ setCurrentId(id) {
this.gCurrentId = id;
if (id == null) {
BrowserStore.removeGlobalItem('current_user_id');
} else {
BrowserStore.setGlobalItem('current_user_id', id);
}
- },
- getCurrentId: function(skipFetch) {
+ }
+ getCurrentId(skipFetch) {
var currentId = this.gCurrentId;
if (currentId == null) {
@@ -93,46 +142,45 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return currentId;
- },
- getCurrentUser: function() {
+ }
+ getCurrentUser() {
if (this.getCurrentId() == null) {
return null;
}
- return this._getProfiles()[this.getCurrentId()];
- },
- setCurrentUser: function(user) {
+ return this.pGetProfiles()[this.getCurrentId()];
+ }
+ setCurrentUser(user) {
this.setCurrentId(user.id);
this.saveProfile(user);
- },
- getLastEmail: function() {
+ }
+ getLastEmail() {
return BrowserStore.getItem('last_email', '');
- },
- setLastEmail: function(email) {
+ }
+ setLastEmail(email) {
BrowserStore.setItem('last_email', email);
- },
- removeCurrentUser: function() {
+ }
+ removeCurrentUser() {
this.setCurrentId(null);
- },
- hasProfile: function(userId) {
- return this._getProfiles()[userId] != null;
- },
- getProfile: function(userId) {
- return this._getProfiles()[userId];
- },
- getProfileByUsername: function(username) {
- return this._getProfilesUsernameMap()[username];
- },
- getProfilesUsernameMap: function() {
- return this._getProfilesUsernameMap();
- },
- getProfiles: function() {
-
- return this._getProfiles();
- },
- getActiveOnlyProfiles: function() {
+ }
+ hasProfile(userId) {
+ return this.pGetProfiles()[userId] != null;
+ }
+ getProfile(userId) {
+ return this.pGetProfiles()[userId];
+ }
+ getProfileByUsername(username) {
+ return this.pGetProfilesUsernameMap()[username];
+ }
+ getProfilesUsernameMap() {
+ return this.pGetProfilesUsernameMap();
+ }
+ getProfiles() {
+ return this.pGetProfiles();
+ }
+ getActiveOnlyProfiles() {
var active = {};
- var current = this._getProfiles();
+ var current = this.pGetProfiles();
for (var key in current) {
if (current[key].delete_at === 0) {
@@ -141,45 +189,47 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return active;
- },
- saveProfile: function(profile) {
- var ps = this._getProfiles();
+ }
+ saveProfile(profile) {
+ var ps = this.pGetProfiles();
ps[profile.id] = profile;
- this._storeProfiles(ps);
- },
- _storeProfiles: function(profiles) {
+ this.pStoreProfiles(ps);
+ }
+ pStoreProfiles(profiles) {
BrowserStore.setItem('profiles', profiles);
var profileUsernameMap = {};
for (var id in profiles) {
- profileUsernameMap[profiles[id].username] = profiles[id];
+ if (profiles.hasOwnProperty(id)) {
+ profileUsernameMap[profiles[id].username] = profiles[id];
+ }
}
BrowserStore.setItem('profileUsernameMap', profileUsernameMap);
- },
- _getProfiles: function() {
+ }
+ pGetProfiles() {
return BrowserStore.getItem('profiles', {});
- },
- _getProfilesUsernameMap: function() {
+ }
+ pGetProfilesUsernameMap() {
return BrowserStore.getItem('profileUsernameMap', {});
- },
- setSessions: function(sessions) {
+ }
+ setSessions(sessions) {
BrowserStore.setItem('sessions', sessions);
- },
- getSessions: function() {
+ }
+ getSessions() {
return BrowserStore.getItem('sessions', {loading: true});
- },
- setAudits: function(audits) {
+ }
+ setAudits(audits) {
BrowserStore.setItem('audits', audits);
- },
- getAudits: function() {
+ }
+ getAudits() {
return BrowserStore.getItem('audits', {loading: true});
- },
- setTeams: function(teams) {
+ }
+ setTeams(teams) {
BrowserStore.setItem('teams', teams);
- },
- getTeams: function() {
+ }
+ getTeams() {
return BrowserStore.getItem('teams', []);
- },
- getCurrentMentionKeys: function() {
+ }
+ getCurrentMentionKeys() {
var user = this.getCurrentUser();
var keys = [];
@@ -205,74 +255,76 @@ var UserStore = assign({}, EventEmitter.prototype, {
}
return keys;
- },
- getLastVersion: function() {
+ }
+ getLastVersion() {
return BrowserStore.getItem('last_version', '');
- },
- setLastVersion: function(version) {
+ }
+ setLastVersion(version) {
BrowserStore.setItem('last_version', version);
- },
- setStatuses: function(statuses) {
- this._setStatuses(statuses);
+ }
+ setStatuses(statuses) {
+ this.pSetStatuses(statuses);
this.emitStatusesChange();
- },
- _setStatuses: function(statuses) {
+ }
+ pSetStatuses(statuses) {
BrowserStore.setItem('statuses', statuses);
- },
- setStatus: function(userId, status) {
+ }
+ setStatus(userId, status) {
var statuses = this.getStatuses();
statuses[userId] = status;
- this._setStatuses(statuses);
+ this.pSetStatuses(statuses);
this.emitStatusesChange();
- },
- getStatuses: function() {
+ }
+ getStatuses() {
return BrowserStore.getItem('statuses', {});
- },
- getStatus: function(id) {
+ }
+ getStatus(id) {
return this.getStatuses()[id];
}
-});
+}
+
+var UserStore = new UserStoreClass();
+UserStore.setMaxListeners(0);
-UserStore.dispatchToken = AppDispatcher.register(function(payload) {
+UserStore.dispatchToken = AppDispatcher.register(function registry(payload) {
var action = payload.action;
switch (action.type) {
- case ActionTypes.RECIEVED_PROFILES:
- for (var id in action.profiles) {
- // profiles can have incomplete data, so don't overwrite current user
- if (id === UserStore.getCurrentId()) {
- continue;
- }
- var profile = action.profiles[id];
- UserStore.saveProfile(profile);
- UserStore.emitChange(profile.id);
+ case ActionTypes.RECIEVED_PROFILES:
+ for (var id in action.profiles) {
+ // profiles can have incomplete data, so don't overwrite current user
+ if (id === UserStore.getCurrentId()) {
+ continue;
}
- break;
- case ActionTypes.RECIEVED_ME:
- UserStore.setCurrentUser(action.me);
- UserStore.emitChange(action.me.id);
- break;
- case ActionTypes.RECIEVED_SESSIONS:
- UserStore.setSessions(action.sessions);
- UserStore.emitSessionsChange();
- break;
- case ActionTypes.RECIEVED_AUDITS:
- UserStore.setAudits(action.audits);
- UserStore.emitAuditsChange();
- break;
- case ActionTypes.RECIEVED_TEAMS:
- UserStore.setTeams(action.teams);
- UserStore.emitTeamsChange();
- break;
- case ActionTypes.RECIEVED_STATUSES:
- UserStore._setStatuses(action.statuses);
- UserStore.emitStatusesChange();
- break;
+ var profile = action.profiles[id];
+ UserStore.saveProfile(profile);
+ UserStore.emitChange(profile.id);
+ }
+ break;
+ case ActionTypes.RECIEVED_ME:
+ UserStore.setCurrentUser(action.me);
+ UserStore.emitChange(action.me.id);
+ break;
+ case ActionTypes.RECIEVED_SESSIONS:
+ UserStore.setSessions(action.sessions);
+ UserStore.emitSessionsChange();
+ break;
+ case ActionTypes.RECIEVED_AUDITS:
+ UserStore.setAudits(action.audits);
+ UserStore.emitAuditsChange();
+ break;
+ case ActionTypes.RECIEVED_TEAMS:
+ UserStore.setTeams(action.teams);
+ UserStore.emitTeamsChange();
+ break;
+ case ActionTypes.RECIEVED_STATUSES:
+ UserStore.pSetStatuses(action.statuses);
+ UserStore.emitStatusesChange();
+ break;
- default:
+ default:
}
});
-UserStore.setMaxListeners(0);
global.window.UserStore = UserStore;
-module.exports = UserStore;
+export default UserStore;
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index c03a0230b..6ccef0506 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -4,7 +4,6 @@
var client = require('./client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
-var ConfigStore = require('../stores/config_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('./utils.jsx');
@@ -15,14 +14,13 @@ var ActionTypes = Constants.ActionTypes;
// Used to track in progress async calls
var callTracker = {};
-function dispatchError(err, method) {
+export function dispatchError(err, method) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
err: err,
method: method
});
}
-module.exports.dispatchError = dispatchError;
function isCallInProgress(callName) {
if (!(callName in callTracker)) {
@@ -34,14 +32,14 @@ function isCallInProgress(callName) {
}
if (utils.getTimestamp() - callTracker[callName] > 5000) {
- console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
+ //console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
return false;
}
return true;
}
-function getChannels(force, updateLastViewed, checkVersion) {
+export function getChannels(force, updateLastViewed, checkVersion) {
var channels = ChannelStore.getAll();
if (channels.length === 0 || force) {
@@ -52,7 +50,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
callTracker.getChannels = utils.getTimestamp();
client.getChannels(
- function(data, textStatus, xhr) {
+ function getChannelsSuccess(data, textStatus, xhr) {
callTracker.getChannels = 0;
if (checkVersion) {
@@ -65,7 +63,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
if (serverVersion !== UserStore.getLastVersion()) {
UserStore.setLastVersion(serverVersion);
window.location.href = window.location.href;
- console.log('Detected version update refreshing the page');
+ console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
}
}
@@ -79,7 +77,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
members: data.members
});
},
- function(err) {
+ function getChannelsFailure(err) {
callTracker.getChannels = 0;
dispatchError(err, 'getChannels');
}
@@ -92,7 +90,7 @@ function getChannels(force, updateLastViewed, checkVersion) {
callTracker.getChannelCounts = utils.getTimestamp();
client.getChannelCounts(
- function(data, textStatus, xhr) {
+ function getChannelCountsSuccess(data, textStatus, xhr) {
callTracker.getChannelCounts = 0;
if (xhr.status === 304 || !data) {
@@ -103,15 +101,17 @@ function getChannels(force, updateLastViewed, checkVersion) {
var updateAtMap = data.update_times;
for (var id in countMap) {
- var c = ChannelStore.get(id);
- var count = countMap[id];
- var updateAt = updateAtMap[id];
- if (!c || c.total_msg_count !== count || updateAt > c.update_at) {
- getChannel(id);
+ if ({}.hasOwnProperty.call(countMap, id)) {
+ var c = ChannelStore.get(id);
+ var count = countMap[id];
+ var updateAt = updateAtMap[id];
+ if (!c || c.total_msg_count !== count || updateAt > c.update_at) {
+ getChannel(id);
+ }
}
}
},
- function(err) {
+ function getChannelCountsFailure(err) {
callTracker.getChannelCounts = 0;
dispatchError(err, 'getChannelCounts');
}
@@ -119,12 +119,11 @@ function getChannels(force, updateLastViewed, checkVersion) {
}
if (updateLastViewed && ChannelStore.getCurrentId() != null) {
- module.exports.updateLastViewedAt();
+ updateLastViewedAt();
}
}
-module.exports.getChannels = getChannels;
-function getChannel(id) {
+export function getChannel(id) {
if (isCallInProgress('getChannel' + id)) {
return;
}
@@ -132,7 +131,7 @@ function getChannel(id) {
callTracker['getChannel' + id] = utils.getTimestamp();
client.getChannel(id,
- function(data, textStatus, xhr) {
+ function getChannelSuccess(data, textStatus, xhr) {
callTracker['getChannel' + id] = 0;
if (xhr.status === 304 || !data) {
@@ -145,43 +144,49 @@ function getChannel(id) {
member: data.member
});
},
- function(err) {
+ function getChannelFailure(err) {
callTracker['getChannel' + id] = 0;
dispatchError(err, 'getChannel');
}
);
}
-module.exports.getChannel = getChannel;
-module.exports.updateLastViewedAt = function() {
- if (isCallInProgress('updateLastViewed')) return;
+export function updateLastViewedAt() {
+ if (isCallInProgress('updateLastViewed')) {
+ return;
+ }
- if (ChannelStore.getCurrentId() == null) return;
+ if (ChannelStore.getCurrentId() == null) {
+ return;
+ }
- callTracker['updateLastViewed'] = utils.getTimestamp();
+ callTracker.updateLastViewed = utils.getTimestamp();
client.updateLastViewedAt(
ChannelStore.getCurrentId(),
- function(data) {
- callTracker['updateLastViewed'] = 0;
+ function updateLastViewedAtSuccess() {
+ callTracker.updateLastViewed = 0;
},
- function(err) {
- callTracker['updateLastViewed'] = 0;
+ function updateLastViewdAtFailure(err) {
+ callTracker.updateLastViewed = 0;
dispatchError(err, 'updateLastViewedAt');
}
);
}
-module.exports.getMoreChannels = function(force) {
- if (isCallInProgress('getMoreChannels')) return;
+export function getMoreChannels(force) {
+ if (isCallInProgress('getMoreChannels')) {
+ return;
+ }
if (ChannelStore.getMoreAll().loading || force) {
-
- callTracker['getMoreChannels'] = utils.getTimestamp();
+ callTracker.getMoreChannels = utils.getTimestamp();
client.getMoreChannels(
- function(data, textStatus, xhr) {
- callTracker['getMoreChannels'] = 0;
+ function getMoreChannelsSuccess(data, textStatus, xhr) {
+ callTracker.getMoreChannels = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_MORE_CHANNELS,
@@ -189,37 +194,44 @@ module.exports.getMoreChannels = function(force) {
members: data.members
});
},
- function(err) {
- callTracker['getMoreChannels'] = 0;
+ function getMoreChannelsFailure(err) {
+ callTracker.getMoreChannels = 0;
dispatchError(err, 'getMoreChannels');
}
);
}
}
-module.exports.getChannelExtraInfo = function(force) {
+export function getChannelExtraInfo(force) {
var channelId = ChannelStore.getCurrentId();
if (channelId != null) {
- if (isCallInProgress('getChannelExtraInfo_'+channelId)) return;
- var minMembers = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D' ? 1 : 0;
+ if (isCallInProgress('getChannelExtraInfo_' + channelId)) {
+ return;
+ }
+ var minMembers = 0;
+ if (ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D') {
+ minMembers = 1;
+ }
if (ChannelStore.getCurrentExtraInfo().members.length <= minMembers || force) {
- callTracker['getChannelExtraInfo_'+channelId] = utils.getTimestamp();
+ callTracker['getChannelExtraInfo_' + channelId] = utils.getTimestamp();
client.getChannelExtraInfo(
channelId,
- function(data, textStatus, xhr) {
- callTracker['getChannelExtraInfo_'+channelId] = 0;
+ function getChannelExtraInfoSuccess(data, textStatus, xhr) {
+ callTracker['getChannelExtraInfo_' + channelId] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO,
extra_info: data
});
},
- function(err) {
- callTracker['getChannelExtraInfo_'+channelId] = 0;
+ function getChannelExtraInfoFailure(err) {
+ callTracker['getChannelExtraInfo_' + channelId] = 0;
dispatchError(err, 'getChannelExtraInfo');
}
);
@@ -227,124 +239,144 @@ module.exports.getChannelExtraInfo = function(force) {
}
}
-module.exports.getProfiles = function() {
- if (isCallInProgress('getProfiles')) return;
+export function getProfiles() {
+ if (isCallInProgress('getProfiles')) {
+ return;
+ }
- callTracker['getProfiles'] = utils.getTimestamp();
+ callTracker.getProfiles = utils.getTimestamp();
client.getProfiles(
- function(data, textStatus, xhr) {
- callTracker['getProfiles'] = 0;
+ function getProfilesSuccess(data, textStatus, xhr) {
+ callTracker.getProfiles = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_PROFILES,
profiles: data
});
},
- function(err) {
- callTracker['getProfiles'] = 0;
+ function getProfilesFailure(err) {
+ callTracker.getProfiles = 0;
dispatchError(err, 'getProfiles');
}
);
}
-module.exports.getSessions = function() {
- if (isCallInProgress('getSessions')) return;
+export function getSessions() {
+ if (isCallInProgress('getSessions')) {
+ return;
+ }
- callTracker['getSessions'] = utils.getTimestamp();
+ callTracker.getSessions = utils.getTimestamp();
client.getSessions(
UserStore.getCurrentId(),
- function(data, textStatus, xhr) {
- callTracker['getSessions'] = 0;
+ function getSessionsSuccess(data, textStatus, xhr) {
+ callTracker.getSessions = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SESSIONS,
sessions: data
});
},
- function(err) {
- callTracker['getSessions'] = 0;
+ function getSessionsFailure(err) {
+ callTracker.getSessions = 0;
dispatchError(err, 'getSessions');
}
);
}
-module.exports.getAudits = function() {
- if (isCallInProgress('getAudits')) return;
+export function getAudits() {
+ if (isCallInProgress('getAudits')) {
+ return;
+ }
- callTracker['getAudits'] = utils.getTimestamp();
+ callTracker.getAudits = utils.getTimestamp();
client.getAudits(
UserStore.getCurrentId(),
- function(data, textStatus, xhr) {
- callTracker['getAudits'] = 0;
+ function getAuditsSuccess(data, textStatus, xhr) {
+ callTracker.getAudits = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_AUDITS,
audits: data
});
},
- function(err) {
- callTracker['getAudits'] = 0;
+ function getAuditsFailure(err) {
+ callTracker.getAudits = 0;
dispatchError(err, 'getAudits');
}
);
}
-module.exports.findTeams = function(email) {
- if (isCallInProgress('findTeams_'+email)) return;
+export function findTeams(email) {
+ if (isCallInProgress('findTeams_' + email)) {
+ return;
+ }
var user = UserStore.getCurrentUser();
if (user) {
- callTracker['findTeams_'+email] = utils.getTimestamp();
+ callTracker['findTeams_' + email] = utils.getTimestamp();
client.findTeams(
user.email,
- function(data, textStatus, xhr) {
- callTracker['findTeams_'+email] = 0;
+ function findTeamsSuccess(data, textStatus, xhr) {
+ callTracker['findTeams_' + email] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_TEAMS,
teams: data
});
},
- function(err) {
- callTracker['findTeams_'+email] = 0;
+ function findTeamsFailure(err) {
+ callTracker['findTeams_' + email] = 0;
dispatchError(err, 'findTeams');
}
);
}
}
-module.exports.search = function(terms) {
- if (isCallInProgress('search_'+String(terms))) return;
+export function search(terms) {
+ if (isCallInProgress('search_' + String(terms))) {
+ return;
+ }
- callTracker['search_'+String(terms)] = utils.getTimestamp();
+ callTracker['search_' + String(terms)] = utils.getTimestamp();
client.search(
terms,
- function(data, textStatus, xhr) {
- callTracker['search_'+String(terms)] = 0;
+ function searchSuccess(data, textStatus, xhr) {
+ callTracker['search_' + String(terms)] = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH,
results: data
});
},
- function(err) {
- callTracker['search_'+String(terms)] = 0;
+ function searchFailure(err) {
+ callTracker['search_' + String(terms)] = 0;
dispatchError(err, 'search');
}
);
}
-module.exports.getPostsPage = function(force, id, maxPosts) {
+export function getPostsPage(force, id, maxPosts) {
if (PostStore.getCurrentPosts() == null || force) {
var channelId = id;
if (channelId == null) {
@@ -377,8 +409,10 @@ module.exports.getPostsPage = function(force, id, maxPosts) {
channelId,
0,
numPosts,
- function(data, textStatus, xhr) {
- if (xhr.status === 304 || !data) return;
+ function getPostsPageSuccess(data, textStatus, xhr) {
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_POSTS,
@@ -386,20 +420,20 @@ module.exports.getPostsPage = function(force, id, maxPosts) {
post_list: data
});
- module.exports.getProfiles();
+ getProfiles();
},
- function(err) {
+ function getPostsPageFailure(err) {
dispatchError(err, 'getPostsPage');
},
- function() {
+ function getPostsPageComplete() {
callTracker['getPostsPage_' + channelId] = 0;
}
);
}
}
-};
+}
-function getPosts(id) {
+export function getPosts(id) {
var channelId = id;
if (channelId == null) {
if (ChannelStore.getCurrentId() == null) {
@@ -413,7 +447,7 @@ function getPosts(id) {
}
if (PostStore.getCurrentPosts() == null) {
- module.exports.getPostsPage(true, id, Constants.POST_CHUNK_SIZE);
+ getPostsPage(true, id, Constants.POST_CHUNK_SIZE);
return;
}
@@ -435,7 +469,7 @@ function getPosts(id) {
post_list: data
});
- module.exports.getProfiles();
+ getProfiles();
},
function fail(err) {
dispatchError(err, 'getPosts');
@@ -445,86 +479,94 @@ function getPosts(id) {
}
);
}
-module.exports.getPosts = getPosts;
-function getMe() {
+export function getMe() {
if (isCallInProgress('getMe')) {
return;
}
callTracker.getMe = utils.getTimestamp();
client.getMeSynchronous(
- function(data, textStatus, xhr) {
+ function getMeSyncSuccess(data, textStatus, xhr) {
callTracker.getMe = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ME,
me: data
});
},
- function(err) {
+ function getMeSyncFailure(err) {
callTracker.getMe = 0;
dispatchError(err, 'getMe');
}
);
}
-module.exports.getMe = getMe;
-module.exports.getStatuses = function() {
- if (isCallInProgress('getStatuses')) return;
+export function getStatuses() {
+ if (isCallInProgress('getStatuses')) {
+ return;
+ }
- callTracker['getStatuses'] = utils.getTimestamp();
+ callTracker.getStatuses = utils.getTimestamp();
client.getStatuses(
- function(data, textStatus, xhr) {
- callTracker['getStatuses'] = 0;
+ function getStatusesSuccess(data, textStatus, xhr) {
+ callTracker.getStatuses = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_STATUSES,
statuses: data
});
},
- function(err) {
- callTracker['getStatuses'] = 0;
+ function getStatusesFailure(err) {
+ callTracker.getStatuses = 0;
dispatchError(err, 'getStatuses');
}
);
}
-module.exports.getMyTeam = function() {
- if (isCallInProgress('getMyTeam')) return;
+export function getMyTeam() {
+ if (isCallInProgress('getMyTeam')) {
+ return;
+ }
- callTracker['getMyTeam'] = utils.getTimestamp();
+ callTracker.getMyTeam = utils.getTimestamp();
client.getMyTeam(
- function(data, textStatus, xhr) {
- callTracker['getMyTeam'] = 0;
+ function getMyTeamSuccess(data, textStatus, xhr) {
+ callTracker.getMyTeam = 0;
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_TEAM,
team: data
});
},
- function(err) {
- callTracker['getMyTeam'] = 0;
+ function getMyTeamFailure(err) {
+ callTracker.getMyTeam = 0;
dispatchError(err, 'getMyTeam');
}
);
}
-function getConfig() {
+export function getConfig() {
if (isCallInProgress('getConfig')) {
return;
}
- callTracker['getConfig'] = utils.getTimestamp();
+ callTracker.getConfig = utils.getTimestamp();
client.getConfig(
- function(data, textStatus, xhr) {
- callTracker['getConfig'] = 0;
+ function getConfigSuccess(data, textStatus, xhr) {
+ callTracker.getConfig = 0;
if (data && xhr.status !== 304) {
AppDispatcher.handleServerAction({
@@ -533,10 +575,9 @@ function getConfig() {
});
}
},
- function(err) {
- callTracker['getConfig'] = 0;
+ function getConfigFailure(err) {
+ callTracker.getConfig = 0;
dispatchError(err, 'getConfig');
}
);
}
-module.exports.getConfig = getConfig;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 082f82a08..10f9c0b37 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -3,15 +3,15 @@
var BrowserStore = require('../stores/browser_store.jsx');
var TeamStore = require('../stores/team_store.jsx');
-module.exports.track = function(category, action, label, prop, val) {
+export function track(category, action, label, prop, val) {
global.window.snowplow('trackStructEvent', category, action, label, prop, val);
global.window.analytics.track(action, {category: category, label: label, property: prop, value: val});
-};
+}
-module.exports.trackPage = function() {
+export function trackPage() {
global.window.snowplow('trackPageView');
global.window.analytics.page();
-};
+}
function handleError(methodName, xhr, status, err) {
var LTracker = global.window.LTracker || [];
@@ -41,7 +41,7 @@ function handleError(methodName, xhr, status, err) {
console.error(e); //eslint-disable-line no-console
LTracker.push(msg);
- module.exports.track('api', 'api_weberror', methodName, 'message', msg);
+ track('api', 'api_weberror', methodName, 'message', msg);
if (xhr.status === 401) {
if (window.location.href.indexOf('/channels') === 0) {
@@ -55,7 +55,7 @@ function handleError(methodName, xhr, status, err) {
return e;
}
-module.exports.createTeamFromSignup = function(teamSignup, success, error) {
+export function createTeamFromSignup(teamSignup, success, error) {
$.ajax({
url: '/api/v1/teams/create_from_signup',
dataType: 'json',
@@ -68,9 +68,9 @@ module.exports.createTeamFromSignup = function(teamSignup, success, error) {
error(e);
}
});
-};
+}
-module.exports.createTeamWithSSO = function(team, service, success, error) {
+export function createTeamWithSSO(team, service, success, error) {
$.ajax({
url: '/api/v1/teams/create_with_sso/' + service,
dataType: 'json',
@@ -83,9 +83,9 @@ module.exports.createTeamWithSSO = function(team, service, success, error) {
error(e);
}
});
-};
+}
-module.exports.createUser = function(user, data, emailHash, success, error) {
+export function createUser(user, data, emailHash, success, error) {
$.ajax({
url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash),
dataType: 'json',
@@ -99,10 +99,10 @@ module.exports.createUser = function(user, data, emailHash, success, error) {
}
});
- module.exports.track('api', 'api_users_create', user.team_id, 'email', user.email);
-};
+ track('api', 'api_users_create', user.team_id, 'email', user.email);
+}
-module.exports.updateUser = function(user, success, error) {
+export function updateUser(user, success, error) {
$.ajax({
url: '/api/v1/users/update',
dataType: 'json',
@@ -116,10 +116,10 @@ module.exports.updateUser = function(user, success, error) {
}
});
- module.exports.track('api', 'api_users_update');
-};
+ track('api', 'api_users_update');
+}
-module.exports.updatePassword = function(data, success, error) {
+export function updatePassword(data, success, error) {
$.ajax({
url: '/api/v1/users/newpassword',
dataType: 'json',
@@ -133,10 +133,10 @@ module.exports.updatePassword = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_newpassword');
-};
+ track('api', 'api_users_newpassword');
+}
-module.exports.updateUserNotifyProps = function(data, success, error) {
+export function updateUserNotifyProps(data, success, error) {
$.ajax({
url: '/api/v1/users/update_notify',
dataType: 'json',
@@ -149,9 +149,9 @@ module.exports.updateUserNotifyProps = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.updateRoles = function(data, success, error) {
+export function updateRoles(data, success, error) {
$.ajax({
url: '/api/v1/users/update_roles',
dataType: 'json',
@@ -165,10 +165,10 @@ module.exports.updateRoles = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_update_roles');
-};
+ track('api', 'api_users_update_roles');
+}
-module.exports.updateActive = function(userId, active, success, error) {
+export function updateActive(userId, active, success, error) {
var data = {};
data.user_id = userId;
data.active = '' + active;
@@ -186,10 +186,10 @@ module.exports.updateActive = function(userId, active, success, error) {
}
});
- module.exports.track('api', 'api_users_update_roles');
-};
+ track('api', 'api_users_update_roles');
+}
-module.exports.sendPasswordReset = function(data, success, error) {
+export function sendPasswordReset(data, success, error) {
$.ajax({
url: '/api/v1/users/send_password_reset',
dataType: 'json',
@@ -203,10 +203,10 @@ module.exports.sendPasswordReset = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_send_password_reset');
-};
+ track('api', 'api_users_send_password_reset');
+}
-module.exports.resetPassword = function(data, success, error) {
+export function resetPassword(data, success, error) {
$.ajax({
url: '/api/v1/users/reset_password',
dataType: 'json',
@@ -220,17 +220,17 @@ module.exports.resetPassword = function(data, success, error) {
}
});
- module.exports.track('api', 'api_users_reset_password');
-};
+ track('api', 'api_users_reset_password');
+}
-module.exports.logout = function() {
- module.exports.track('api', 'api_users_logout');
+export function logout() {
+ track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
BrowserStore.clear();
window.location.href = currentTeamUrl + '/logout';
-};
+}
-module.exports.loginByEmail = function(name, email, password, success, error) {
+export function loginByEmail(name, email, password, success, error) {
$.ajax({
url: '/api/v1/users/login',
dataType: 'json',
@@ -238,19 +238,19 @@ module.exports.loginByEmail = function(name, email, password, success, error) {
type: 'POST',
data: JSON.stringify({name: name, email: email, password: password}),
success: function onSuccess(data, textStatus, xhr) {
- module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email);
+ track('api', 'api_users_login_success', data.team_id, 'email', data.email);
success(data, textStatus, xhr);
},
error: function onError(xhr, status, err) {
- module.exports.track('api', 'api_users_login_fail', window.getSubDomain(), 'email', email);
+ track('api', 'api_users_login_fail', name, 'email', email);
var e = handleError('loginByEmail', xhr, status, err);
error(e);
}
});
-};
+}
-module.exports.revokeSession = function(altId, success, error) {
+export function revokeSession(altId, success, error) {
$.ajax({
url: '/api/v1/users/revoke_session',
dataType: 'json',
@@ -263,9 +263,9 @@ module.exports.revokeSession = function(altId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getSessions = function(userId, success, error) {
+export function getSessions(userId, success, error) {
$.ajax({
cache: false,
url: '/api/v1/users/' + userId + '/sessions',
@@ -278,9 +278,9 @@ module.exports.getSessions = function(userId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getAudits = function(userId, success, error) {
+export function getAudits(userId, success, error) {
$.ajax({
url: '/api/v1/users/' + userId + '/audits',
dataType: 'json',
@@ -292,9 +292,9 @@ module.exports.getAudits = function(userId, success, error) {
error(e);
}
});
-};
+}
-module.exports.getMeSynchronous = function(success, error) {
+export function getMeSynchronous(success, error) {
var currentUser = null;
$.ajax({
async: false,
@@ -318,9 +318,9 @@ module.exports.getMeSynchronous = function(success, error) {
});
return currentUser;
-};
+}
-module.exports.inviteMembers = function(data, success, error) {
+export function inviteMembers(data, success, error) {
$.ajax({
url: '/api/v1/teams/invite_members',
dataType: 'json',
@@ -334,10 +334,10 @@ module.exports.inviteMembers = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_invite_members');
-};
+ track('api', 'api_teams_invite_members');
+}
-module.exports.updateTeamDisplayName = function(data, success, error) {
+export function updateTeamDisplayName(data, success, error) {
$.ajax({
url: '/api/v1/teams/update_name',
dataType: 'json',
@@ -351,10 +351,10 @@ module.exports.updateTeamDisplayName = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_update_name');
-};
+ track('api', 'api_teams_update_name');
+}
-module.exports.signupTeam = function(email, success, error) {
+export function signupTeam(email, success, error) {
$.ajax({
url: '/api/v1/teams/signup',
dataType: 'json',
@@ -368,10 +368,10 @@ module.exports.signupTeam = function(email, success, error) {
}
});
- module.exports.track('api', 'api_teams_signup');
-};
+ track('api', 'api_teams_signup');
+}
-module.exports.createTeam = function(team, success, error) {
+export function createTeam(team, success, error) {
$.ajax({
url: '/api/v1/teams/create',
dataType: 'json',
@@ -384,9 +384,9 @@ module.exports.createTeam = function(team, success, error) {
error(e);
}
});
-};
+}
-module.exports.findTeamByName = function(teamName, success, error) {
+export function findTeamByName(teamName, success, error) {
$.ajax({
url: '/api/v1/teams/find_team_by_name',
dataType: 'json',
@@ -399,9 +399,9 @@ module.exports.findTeamByName = function(teamName, success, error) {
error(e);
}
});
-};
+}
-module.exports.findTeamsSendEmail = function(email, success, error) {
+export function findTeamsSendEmail(email, success, error) {
$.ajax({
url: '/api/v1/teams/email_teams',
dataType: 'json',
@@ -415,10 +415,10 @@ module.exports.findTeamsSendEmail = function(email, success, error) {
}
});
- module.exports.track('api', 'api_teams_email_teams');
-};
+ track('api', 'api_teams_email_teams');
+}
-module.exports.findTeams = function(email, success, error) {
+export function findTeams(email, success, error) {
$.ajax({
url: '/api/v1/teams/find_teams',
dataType: 'json',
@@ -431,9 +431,9 @@ module.exports.findTeams = function(email, success, error) {
error(e);
}
});
-};
+}
-module.exports.createChannel = function(channel, success, error) {
+export function createChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/create',
dataType: 'json',
@@ -447,10 +447,10 @@ module.exports.createChannel = function(channel, success, error) {
}
});
- module.exports.track('api', 'api_channels_create', channel.type, 'name', channel.name);
-};
+ track('api', 'api_channels_create', channel.type, 'name', channel.name);
+}
-module.exports.createDirectChannel = function(channel, userId, success, error) {
+export function createDirectChannel(channel, userId, success, error) {
$.ajax({
url: '/api/v1/channels/create_direct',
dataType: 'json',
@@ -458,16 +458,16 @@ module.exports.createDirectChannel = function(channel, userId, success, error) {
type: 'POST',
data: JSON.stringify({user_id: userId}),
success: success,
- error: function(xhr, status, err) {
+ error: function onError(xhr, status, err) {
var e = handleError('createDirectChannel', xhr, status, err);
error(e);
}
});
- module.exports.track('api', 'api_channels_create_direct', channel.type, 'name', channel.name);
-};
+ track('api', 'api_channels_create_direct', channel.type, 'name', channel.name);
+}
-module.exports.updateChannel = function(channel, success, error) {
+export function updateChannel(channel, success, error) {
$.ajax({
url: '/api/v1/channels/update',
dataType: 'json',
@@ -481,10 +481,10 @@ module.exports.updateChannel = function(channel, success, error) {
}
});
- module.exports.track('api', 'api_channels_update');
-};
+ track('api', 'api_channels_update');
+}
-module.exports.updateChannelDesc = function(data, success, error) {
+export function updateChannelDesc(data, success, error) {
$.ajax({
url: '/api/v1/channels/update_desc',
dataType: 'json',
@@ -498,10 +498,10 @@ module.exports.updateChannelDesc = function(data, success, error) {
}
});
- module.exports.track('api', 'api_channels_desc');
-};
+ track('api', 'api_channels_desc');
+}
-module.exports.updateNotifyLevel = function(data, success, error) {
+export function updateNotifyLevel(data, success, error) {
$.ajax({
url: '/api/v1/channels/update_notify_level',
dataType: 'json',
@@ -514,9 +514,9 @@ module.exports.updateNotifyLevel = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.joinChannel = function(id, success, error) {
+export function joinChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/join',
dataType: 'json',
@@ -529,10 +529,10 @@ module.exports.joinChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_join');
-};
+ track('api', 'api_channels_join');
+}
-module.exports.leaveChannel = function(id, success, error) {
+export function leaveChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/leave',
dataType: 'json',
@@ -545,10 +545,10 @@ module.exports.leaveChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_leave');
-};
+ track('api', 'api_channels_leave');
+}
-module.exports.deleteChannel = function(id, success, error) {
+export function deleteChannel(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/delete',
dataType: 'json',
@@ -561,10 +561,10 @@ module.exports.deleteChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channels_delete');
-};
+ track('api', 'api_channels_delete');
+}
-module.exports.updateLastViewedAt = function(channelId, success, error) {
+export function updateLastViewedAt(channelId, success, error) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/update_last_viewed_at',
dataType: 'json',
@@ -576,9 +576,9 @@ module.exports.updateLastViewedAt = function(channelId, success, error) {
error(e);
}
});
-};
+}
-function getChannels(success, error) {
+export function getChannels(success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/',
@@ -592,9 +592,8 @@ function getChannels(success, error) {
}
});
}
-module.exports.getChannels = getChannels;
-module.exports.getChannel = function(id, success, error) {
+export function getChannel(id, success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + id + '/',
@@ -607,10 +606,10 @@ module.exports.getChannel = function(id, success, error) {
}
});
- module.exports.track('api', 'api_channel_get');
-};
+ track('api', 'api_channel_get');
+}
-module.exports.getMoreChannels = function(success, error) {
+export function getMoreChannels(success, error) {
$.ajax({
url: '/api/v1/channels/more',
dataType: 'json',
@@ -622,9 +621,9 @@ module.exports.getMoreChannels = function(success, error) {
error(e);
}
});
-};
+}
-function getChannelCounts(success, error) {
+export function getChannelCounts(success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/counts',
@@ -638,9 +637,8 @@ function getChannelCounts(success, error) {
}
});
}
-module.exports.getChannelCounts = getChannelCounts;
-module.exports.getChannelExtraInfo = function(id, success, error) {
+export function getChannelExtraInfo(id, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/extra_info',
dataType: 'json',
@@ -651,9 +649,9 @@ module.exports.getChannelExtraInfo = function(id, success, error) {
error(e);
}
});
-};
+}
-module.exports.executeCommand = function(channelId, command, suggest, success, error) {
+export function executeCommand(channelId, command, suggest, success, error) {
$.ajax({
url: '/api/v1/command',
dataType: 'json',
@@ -666,9 +664,9 @@ module.exports.executeCommand = function(channelId, command, suggest, success, e
error(e);
}
});
-};
+}
-module.exports.getPostsPage = function(channelId, offset, limit, success, error, complete) {
+export function getPostsPage(channelId, offset, limit, success, error, complete) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + channelId + '/posts/' + offset + '/' + limit,
@@ -682,9 +680,9 @@ module.exports.getPostsPage = function(channelId, offset, limit, success, error,
},
complete: complete
});
-};
+}
-module.exports.getPosts = function(channelId, since, success, error, complete) {
+export function getPosts(channelId, since, success, error, complete) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/posts/' + since,
dataType: 'json',
@@ -697,9 +695,9 @@ module.exports.getPosts = function(channelId, since, success, error, complete) {
},
complete: complete
});
-};
+}
-module.exports.getPost = function(channelId, postId, success, error) {
+export function getPost(channelId, postId, success, error) {
$.ajax({
cache: false,
url: '/api/v1/channels/' + channelId + '/post/' + postId,
@@ -712,9 +710,9 @@ module.exports.getPost = function(channelId, postId, success, error) {
error(e);
}
});
-};
+}
-module.exports.search = function(terms, success, error) {
+export function search(terms, success, error) {
$.ajax({
url: '/api/v1/posts/search',
dataType: 'json',
@@ -727,10 +725,10 @@ module.exports.search = function(terms, success, error) {
}
});
- module.exports.track('api', 'api_posts_search');
-};
+ track('api', 'api_posts_search');
+}
-module.exports.deletePost = function(channelId, id, success, error) {
+export function deletePost(channelId, id, success, error) {
$.ajax({
url: '/api/v1/channels/' + channelId + '/post/' + id + '/delete',
dataType: 'json',
@@ -743,10 +741,10 @@ module.exports.deletePost = function(channelId, id, success, error) {
}
});
- module.exports.track('api', 'api_posts_delete');
-};
+ track('api', 'api_posts_delete');
+}
-module.exports.createPost = function(post, channel, success, error) {
+export function createPost(post, channel, success, error) {
$.ajax({
url: '/api/v1/channels/' + post.channel_id + '/create',
dataType: 'json',
@@ -760,7 +758,7 @@ module.exports.createPost = function(post, channel, success, error) {
}
});
- module.exports.track('api', 'api_posts_create', channel.name, 'length', post.message.length);
+ track('api', 'api_posts_create', channel.name, 'length', post.message.length);
// global.window.analytics.track('api_posts_create', {
// category: 'api',
@@ -770,9 +768,9 @@ module.exports.createPost = function(post, channel, success, error) {
// files: (post.filenames || []).length,
// mentions: (post.message.match('/<mention>/g') || []).length
// });
-};
+}
-module.exports.updatePost = function(post, success, error) {
+export function updatePost(post, success, error) {
$.ajax({
url: '/api/v1/channels/' + post.channel_id + '/update',
dataType: 'json',
@@ -786,10 +784,10 @@ module.exports.updatePost = function(post, success, error) {
}
});
- module.exports.track('api', 'api_posts_update');
-};
+ track('api', 'api_posts_update');
+}
-module.exports.addChannelMember = function(id, data, success, error) {
+export function addChannelMember(id, data, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/add',
dataType: 'json',
@@ -803,10 +801,10 @@ module.exports.addChannelMember = function(id, data, success, error) {
}
});
- module.exports.track('api', 'api_channels_add_member');
-};
+ track('api', 'api_channels_add_member');
+}
-module.exports.removeChannelMember = function(id, data, success, error) {
+export function removeChannelMember(id, data, success, error) {
$.ajax({
url: '/api/v1/channels/' + id + '/remove',
dataType: 'json',
@@ -820,10 +818,10 @@ module.exports.removeChannelMember = function(id, data, success, error) {
}
});
- module.exports.track('api', 'api_channels_remove_member');
-};
+ track('api', 'api_channels_remove_member');
+}
-module.exports.getProfiles = function(success, error) {
+export function getProfiles(success, error) {
$.ajax({
cache: false,
url: '/api/v1/users/profiles',
@@ -837,9 +835,9 @@ module.exports.getProfiles = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.uploadFile = function(formData, success, error) {
+export function uploadFile(formData, success, error) {
var request = $.ajax({
url: '/api/v1/files/upload',
type: 'POST',
@@ -856,12 +854,12 @@ module.exports.uploadFile = function(formData, success, error) {
}
});
- module.exports.track('api', 'api_files_upload');
+ track('api', 'api_files_upload');
return request;
-};
+}
-module.exports.getFileInfo = function(filename, success, error) {
+export function getFileInfo(filename, success, error) {
$.ajax({
url: '/api/v1/files/get_info' + filename,
dataType: 'json',
@@ -873,9 +871,9 @@ module.exports.getFileInfo = function(filename, success, error) {
error(e);
}
});
-};
+}
-module.exports.getPublicLink = function(data, success, error) {
+export function getPublicLink(data, success, error) {
$.ajax({
url: '/api/v1/files/get_public_link',
dataType: 'json',
@@ -887,9 +885,9 @@ module.exports.getPublicLink = function(data, success, error) {
error(e);
}
});
-};
+}
-module.exports.uploadProfileImage = function(imageData, success, error) {
+export function uploadProfileImage(imageData, success, error) {
$.ajax({
url: '/api/v1/users/newimage',
type: 'POST',
@@ -903,9 +901,9 @@ module.exports.uploadProfileImage = function(imageData, success, error) {
error(e);
}
});
-};
+}
-module.exports.importSlack = function(fileData, success, error) {
+export function importSlack(fileData, success, error) {
$.ajax({
url: '/api/v1/teams/import_team',
type: 'POST',
@@ -919,9 +917,9 @@ module.exports.importSlack = function(fileData, success, error) {
error(e);
}
});
-};
+}
-module.exports.getStatuses = function(success, error) {
+export function getStatuses(success, error) {
$.ajax({
url: '/api/v1/users/status',
dataType: 'json',
@@ -933,9 +931,9 @@ module.exports.getStatuses = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.getMyTeam = function(success, error) {
+export function getMyTeam(success, error) {
$.ajax({
url: '/api/v1/teams/me',
dataType: 'json',
@@ -947,9 +945,9 @@ module.exports.getMyTeam = function(success, error) {
error(e);
}
});
-};
+}
-module.exports.updateValetFeature = function(data, success, error) {
+export function updateValetFeature(data, success, error) {
$.ajax({
url: '/api/v1/teams/update_valet_feature',
dataType: 'json',
@@ -963,10 +961,10 @@ module.exports.updateValetFeature = function(data, success, error) {
}
});
- module.exports.track('api', 'api_teams_update_valet_feature');
-};
+ track('api', 'api_teams_update_valet_feature');
+}
-function getConfig(success, error) {
+export function getConfig(success, error) {
$.ajax({
url: '/api/v1/config/get_all',
dataType: 'json',
@@ -979,4 +977,3 @@ function getConfig(success, error) {
}
});
}
-module.exports.getConfig = getConfig;
diff --git a/web/react/utils/config.js b/web/react/utils/config.js
new file mode 100644
index 000000000..c7d1aa2bc
--- /dev/null
+++ b/web/react/utils/config.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export var config = {
+
+ // Loggly configs
+ LogglyWriteKey: '',
+ LogglyConsoleErrors: true,
+
+ // Segment configs
+ SegmentWriteKey: '',
+
+ // Feature switches
+ AllowPublicLink: true,
+ AllowInviteNames: true,
+ RequireInviteNames: false,
+ AllowSignupDomainsWizard: false,
+
+ // Google Developer Key (for Youtube API links)
+ // Leave blank to disable
+ GoogleDeveloperKey: '',
+
+ // Privacy switches
+ ShowEmail: true,
+
+ // Links
+ TermsLink: '/static/help/configure_links.html',
+ PrivacyLink: '/static/help/configure_links.html',
+ AboutLink: '/static/help/configure_links.html',
+ HelpLink: '/static/help/configure_links.html',
+ ReportProblemLink: '/static/help/configure_links.html',
+ HomeLink: '',
+
+ // Toggle whether or not users are shown a message about agreeing to the Terms of Service during the signup process
+ ShowTermsDuringSignup: false,
+
+ ThemeColors: ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000']
+};
+
+// Flavor strings
+export var strings = {
+ Team: 'team',
+ TeamPlural: 'teams',
+ Company: 'company',
+ CompanyPlural: 'companies'
+};
+
+global.window.config = config;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 8721ced7c..18b7ff59c 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -4,97 +4,108 @@
var keyMirror = require('keymirror');
module.exports = {
- ActionTypes: keyMirror({
- RECIEVED_ERROR: null,
+ ActionTypes: keyMirror({
+ RECIEVED_ERROR: null,
- CLICK_CHANNEL: null,
- CREATE_CHANNEL: null,
- RECIEVED_CHANNELS: null,
- RECIEVED_CHANNEL: null,
- RECIEVED_MORE_CHANNELS: null,
- RECIEVED_CHANNEL_EXTRA_INFO: null,
+ CLICK_CHANNEL: null,
+ CREATE_CHANNEL: null,
+ RECIEVED_CHANNELS: null,
+ RECIEVED_CHANNEL: null,
+ RECIEVED_MORE_CHANNELS: null,
+ RECIEVED_CHANNEL_EXTRA_INFO: null,
- RECIEVED_POSTS: null,
- RECIEVED_POST: null,
- RECIEVED_SEARCH: null,
- RECIEVED_POST_SELECTED: null,
- RECIEVED_MENTION_DATA: null,
- RECIEVED_ADD_MENTION: null,
+ RECIEVED_POSTS: null,
+ RECIEVED_POST: null,
+ RECIEVED_SEARCH: null,
+ RECIEVED_POST_SELECTED: null,
+ RECIEVED_MENTION_DATA: null,
+ RECIEVED_ADD_MENTION: null,
- RECIEVED_PROFILES: null,
- RECIEVED_ME: null,
- RECIEVED_SESSIONS: null,
- RECIEVED_AUDITS: null,
- RECIEVED_TEAMS: null,
- RECIEVED_STATUSES: null,
+ RECIEVED_PROFILES: null,
+ RECIEVED_ME: null,
+ RECIEVED_SESSIONS: null,
+ RECIEVED_AUDITS: null,
+ RECIEVED_TEAMS: null,
+ RECIEVED_STATUSES: null,
- RECIEVED_MSG: null,
+ RECIEVED_MSG: null,
- CLICK_TEAM: null,
- RECIEVED_TEAM: null,
+ CLICK_TEAM: null,
+ RECIEVED_TEAM: null,
- RECIEVED_CONFIG: null
- }),
+ RECIEVED_CONFIG: null
+ }),
- PayloadSources: keyMirror({
- SERVER_ACTION: null,
- VIEW_ACTION: null
- }),
- SPECIAL_MENTIONS: ['all', 'channel'],
- CHARACTER_LIMIT: 4000,
- IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
- AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
- VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],
- SPREADSHEET_TYPES: ['ppt', 'pptx', 'csv'],
- EXCEL_TYPES: ['xlsx'],
- WORD_TYPES: ['doc', 'docx'],
- CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'],
- PDF_TYPES: ['pdf'],
- PATCH_TYPES: ['patch'],
- ICON_FROM_TYPE: {'audio': 'audio', 'video': 'video', 'spreadsheet': 'ppt', 'pdf': 'pdf', 'code': 'code' , 'word': 'word' , 'excel': 'excel' , 'patch': 'patch', 'other': 'generic'},
- MAX_DISPLAY_FILES: 5,
- MAX_UPLOAD_FILES: 5,
- MAX_FILE_SIZE: 50000000, // 50 MB
- THUMBNAIL_WIDTH: 128,
- THUMBNAIL_HEIGHT: 100,
- DEFAULT_CHANNEL: 'town-square',
- OFFTOPIC_CHANNEL: 'off-topic',
- GITLAB_SERVICE: 'gitlab',
- EMAIL_SERVICE: 'email',
- POST_CHUNK_SIZE: 60,
- MAX_POST_CHUNKS: 3,
- POST_LOADING: 'loading',
- POST_FAILED: 'failed',
- POST_DELETED: 'deleted',
- RESERVED_TEAM_NAMES: [
- "www",
- "web",
- "admin",
- "support",
- "notify",
- "test",
- "demo",
- "mail",
- "team",
- "channel",
- "internal",
- "localhost",
- "dockerhost",
- "stag",
- "post",
- "cluster",
- "api",
- ],
- RESERVED_USERNAMES: [
- "valet",
- "all",
- "channel",
- ],
- MONTHS: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
- MAX_DMS: 20,
- MAX_POST_LEN: 4000,
- ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
- OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
- MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
- COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>"
+ PayloadSources: keyMirror({
+ SERVER_ACTION: null,
+ VIEW_ACTION: null
+ }),
+ SPECIAL_MENTIONS: ['all', 'channel'],
+ CHARACTER_LIMIT: 4000,
+ IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
+ AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
+ VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],
+ PRESENTATION_TYPES: ['ppt', 'pptx'],
+ SPREADSHEET_TYPES: ['xlsx', 'csv'],
+ WORD_TYPES: ['doc', 'docx'],
+ CODE_TYPES: ['css', 'html', 'js', 'php', 'rb'],
+ PDF_TYPES: ['pdf'],
+ PATCH_TYPES: ['patch'],
+ ICON_FROM_TYPE: {
+ audio: 'audio',
+ video: 'video',
+ spreadsheet: 'excel',
+ presentation: 'ppt',
+ pdf: 'pdf',
+ code: 'code',
+ word: 'word',
+ patch: 'patch',
+ other: 'generic'
+ },
+ MAX_DISPLAY_FILES: 5,
+ MAX_UPLOAD_FILES: 5,
+ MAX_FILE_SIZE: 50000000, // 50 MB
+ THUMBNAIL_WIDTH: 128,
+ THUMBNAIL_HEIGHT: 100,
+ DEFAULT_CHANNEL: 'town-square',
+ OFFTOPIC_CHANNEL: 'off-topic',
+ GITLAB_SERVICE: 'gitlab',
+ EMAIL_SERVICE: 'email',
+ POST_CHUNK_SIZE: 60,
+ MAX_POST_CHUNKS: 3,
+ POST_LOADING: 'loading',
+ POST_FAILED: 'failed',
+ POST_DELETED: 'deleted',
+ RESERVED_TEAM_NAMES: [
+ 'www',
+ 'web',
+ 'admin',
+ 'support',
+ 'notify',
+ 'test',
+ 'demo',
+ 'mail',
+ 'team',
+ 'channel',
+ 'internal',
+ 'localhost',
+ 'dockerhost',
+ 'stag',
+ 'post',
+ 'cluster',
+ 'api'
+ ],
+ RESERVED_USERNAMES: [
+ 'valet',
+ 'all',
+ 'channel'
+ ],
+ MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ MAX_DMS: 20,
+ MAX_POST_LEN: 4000,
+ EMOJI_SIZE: 16,
+ ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
+ OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
+ MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
+ COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>"
};
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 5266b1381..ea42256aa 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -9,20 +9,21 @@ var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
+import {config} from '../utils/config.js';
-module.exports.isEmail = function(email) {
+export function isEmail(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
return regex.test(email);
-};
+}
-module.exports.cleanUpUrlable = function(input) {
+export function cleanUpUrlable(input) {
var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-');
cleaned = cleaned.replace(/^\-+/, '');
cleaned = cleaned.replace(/\-+$/, '');
return cleaned;
-};
+}
-module.exports.isTestDomain = function() {
+export function isTestDomain() {
if ((/^localhost/).test(window.location.hostname)) {
return true;
}
@@ -52,38 +53,9 @@ module.exports.isTestDomain = function() {
}
return false;
-};
-
-function getSubDomain() {
- if (module.exports.isTestDomain()) {
- return '';
- }
-
- if ((/^www/).test(window.location.hostname)) {
- return '';
- }
-
- if ((/^beta/).test(window.location.hostname)) {
- return '';
- }
-
- if ((/^ci/).test(window.location.hostname)) {
- return '';
- }
-
- var parts = window.location.hostname.split('.');
-
- if (parts.length !== 3) {
- return '';
- }
-
- return parts[0];
}
-global.window.getSubDomain = getSubDomain;
-module.exports.getSubDomain = getSubDomain;
-
-module.exports.getDomainWithOutSub = function() {
+export function getDomainWithOutSub() {
var parts = window.location.host.split('.');
if (parts.length === 1) {
@@ -95,17 +67,17 @@ module.exports.getDomainWithOutSub = function() {
}
return parts[1] + '.' + parts[2];
-};
+}
-module.exports.getCookie = function(name) {
+export function getCookie(name) {
var value = '; ' + document.cookie;
var parts = value.split('; ' + name + '=');
if (parts.length === 2) {
return parts.pop().split(';').shift();
}
-};
+}
-module.exports.notifyMe = function(title, body, channel) {
+export function notifyMe(title, body, channel) {
if ('Notification' in window && Notification.permission !== 'denied') {
Notification.requestPermission(function onRequestPermission(permission) {
if (Notification.permission !== permission) {
@@ -117,7 +89,7 @@ module.exports.notifyMe = function(title, body, channel) {
notification.onclick = function onClick() {
window.focus();
if (channel) {
- module.exports.switchChannel(channel);
+ switchChannel(channel);
} else {
window.location.href = '/';
}
@@ -128,16 +100,16 @@ module.exports.notifyMe = function(title, body, channel) {
}
});
}
-};
+}
-module.exports.ding = function() {
- if (!module.exports.isBrowserFirefox()) {
+export function ding() {
+ if (!isBrowserFirefox()) {
var audio = new Audio('/static/images/ding.mp3');
audio.play();
}
-};
+}
-module.exports.getUrlParameter = function(sParam) {
+export function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
@@ -147,20 +119,20 @@ module.exports.getUrlParameter = function(sParam) {
}
}
return null;
-};
+}
-module.exports.getDateForUnixTicks = function(ticks) {
+export function getDateForUnixTicks(ticks) {
return new Date(ticks);
-};
+}
-module.exports.displayDate = function(ticks) {
+export function displayDate(ticks) {
var d = new Date(ticks);
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
return monthNames[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
-};
+}
-module.exports.displayTime = function(ticks) {
+export function displayTime(ticks) {
var d = new Date(ticks);
var hours = d.getHours();
var minutes = d.getMinutes();
@@ -178,9 +150,9 @@ module.exports.displayTime = function(ticks) {
minutes = '0' + minutes;
}
return hours + ':' + minutes + ' ' + ampm;
-};
+}
-module.exports.displayDateTime = function(ticks) {
+export function displayDateTime(ticks) {
var seconds = Math.floor((Date.now() - ticks) / 1000);
var interval = Math.floor(seconds / 3600);
@@ -203,16 +175,16 @@ module.exports.displayDateTime = function(ticks) {
}
return '1 minute ago';
-};
+}
-module.exports.displayCommentDateTime = function(ticks) {
- return module.exports.displayDate(ticks) + ' ' + module.exports.displayTime(ticks);
+export function displayCommentDateTime(ticks) {
+ return displayDate(ticks) + ' ' + displayTime(ticks);
}
// returns Unix timestamp in milliseconds
-module.exports.getTimestamp = function() {
+export function getTimestamp() {
return Date.now();
-};
+}
function testUrlMatch(text) {
var urlMatcher = new Autolinker.matchParser.MatchParser({
@@ -240,7 +212,7 @@ function testUrlMatch(text) {
return result;
}
-module.exports.extractLinks = function(text) {
+export function extractLinks(text) {
var repRegex = new RegExp('<br>', 'g');
var matches = testUrlMatch(text.replace(repRegex, '\n'));
@@ -254,16 +226,46 @@ module.exports.extractLinks = function(text) {
}
return {links: links, text: text};
-};
+}
-module.exports.escapeRegExp = function(string) {
+export function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
-};
+}
+
+function handleYoutubeTime(link) {
+ var timeRegex = /[\\?&]t=([0-9hms]+)/;
+
+ var time = link.trim().match(timeRegex);
+ if (!time || !time[1]) {
+ return '';
+ }
+
+ var hours = time[1].match(/([0-9]+)h/);
+ var minutes = time[1].match(/([0-9]+)m/);
+ var seconds = time[1].match(/([0-9]+)s/);
+
+ var ticks = 0;
+
+ if (hours && hours[1]) {
+ ticks += parseInt(hours[1], 10) * 3600;
+ }
+
+ if (minutes && minutes[1]) {
+ ticks += parseInt(minutes[1], 10) * 60;
+ }
+
+ if (seconds && seconds[1]) {
+ ticks += parseInt(seconds[1], 10);
+ }
+
+ return '&start=' + ticks.toString();
+}
function getYoutubeEmbed(link) {
var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
var youtubeId = link.trim().match(regex)[1];
+ var time = handleYoutubeTime(link);
function onClick(e) {
var div = $(e.target).closest('.video-thumbnail__container')[0];
@@ -271,7 +273,8 @@ function getYoutubeEmbed(link) {
iframe.setAttribute('src',
'https://www.youtube.com/embed/' +
div.id +
- '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1');
+ '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' +
+ time);
iframe.setAttribute('width', '480px');
iframe.setAttribute('height', '360px');
iframe.setAttribute('type', 'text/html');
@@ -286,6 +289,7 @@ function getYoutubeEmbed(link) {
return;
}
var metadata = data.items[0].snippet;
+ $('.video-type.' + youtubeId).html('Youtube - ');
$('.video-uploader.' + youtubeId).html(metadata.channelTitle);
$('.video-title.' + youtubeId).find('a').html(metadata.title);
$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight);
@@ -296,22 +300,34 @@ function getYoutubeEmbed(link) {
async: true,
url: 'https://www.googleapis.com/youtube/v3/videos',
type: 'GET',
- data: {part: 'snippet', id: youtubeId, key:config.GoogleDeveloperKey},
+ data: {part: 'snippet', id: youtubeId, key: config.GoogleDeveloperKey},
success: success
});
}
return (
<div className='post-comment'>
- <h4 className='video-type'>YouTube</h4>
+ <h4>
+ <span className={'video-type ' + youtubeId}>YouTube</span>
+ <span className={'video-title ' + youtubeId}><a href={link}></a></span>
+ </h4>
<h4 className={'video-uploader ' + youtubeId}></h4>
- <h4 className={'video-title ' + youtubeId}><a href={link}></a></h4>
- <div className='video-div embed-responsive-item' id={youtubeId} onClick={onClick}>
+ <div
+ className='video-div embed-responsive-item'
+ id={youtubeId}
+ onClick={onClick}
+ >
<div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
- <div id={youtubeId} className='video-thumbnail__container'>
- <img className='video-thumbnail' src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}/>
+ <div
+ id={youtubeId}
+ className='video-thumbnail__container'
+ >
+ <img
+ className='video-thumbnail'
+ src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}
+ />
<div className='block'>
- <span className='play-button'><span></span></span>
+ <span className='play-button'><span/></span>
</div>
</div>
</div>
@@ -320,7 +336,7 @@ function getYoutubeEmbed(link) {
);
}
-module.exports.getEmbed = function(link) {
+export function getEmbed(link) {
var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
var match = link.trim().match(ytRegex);
@@ -372,13 +388,13 @@ module.exports.getEmbed = function(link) {
</div>
);
*/
-};
+}
-module.exports.areStatesEqual = function(state1, state2) {
+export function areStatesEqual(state1, state2) {
return JSON.stringify(state1) === JSON.stringify(state2);
-};
+}
-module.exports.replaceHtmlEntities = function(text) {
+export function replaceHtmlEntities(text) {
var tagsToReplace = {
'&amp;': '&',
'&lt;': '<',
@@ -392,9 +408,9 @@ module.exports.replaceHtmlEntities = function(text) {
}
}
return newtext;
-};
+}
-module.exports.insertHtmlEntities = function(text) {
+export function insertHtmlEntities(text) {
var tagsToReplace = {
'&': '&amp;',
'<': '&lt;',
@@ -408,123 +424,216 @@ module.exports.insertHtmlEntities = function(text) {
}
}
return newtext;
-};
+}
-module.exports.searchForTerm = function(term) {
+export function searchForTerm(term) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH_TERM,
term: term,
do_search: true
});
-};
+}
var puncStartRegex = /^((?![@#])\W)+/g;
var puncEndRegex = /(\W)+$/g;
-module.exports.textToJsx = function(text, options) {
-
- if (options && options['singleline']) {
- var repRegex = new RegExp('\n', 'g');
+export function textToJsx(textin, options) {
+ var text = textin;
+ if (options && options.singleline) {
+ var repRegex = new RegExp('\n', 'g'); //eslint-disable-line no-control-regex
text = text.replace(repRegex, ' ');
}
- var searchTerm = ''
- if (options && options['searchTerm']) {
- searchTerm = options['searchTerm'].toLowerCase()
+ var searchTerm = '';
+ if (options && options.searchTerm) {
+ searchTerm = options.searchTerm.toLowerCase();
}
var mentionClass = 'mention-highlight';
- if (options && options['noMentionHighlight']) {
+ if (options && options.noMentionHighlight) {
mentionClass = '';
}
var inner = [];
// Function specific regex
- var hashRegex = /^href="#[^']+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
+ var hashRegex = /^href="#[^']+"|(^#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
var implicitKeywords = UserStore.getCurrentMentionKeys();
var lines = text.split('\n');
- for (var i = 0; i < lines.length; i++) {
+ for (let i = 0; i < lines.length; i++) {
var line = lines[i];
var words = line.split(' ');
var highlightSearchClass = '';
- for (var z = 0; z < words.length; z++) {
+ for (let z = 0; z < words.length; z++) {
var word = words[z];
var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
var explicitMention = mentionRegex.exec(trimWord);
- if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != '') {
-
- highlightSearchClass = ' search-highlight';
+ if (searchTerm !== '') {
+ let searchWords = searchTerm.split(' ');
+ for (let idx in searchWords) {
+ if ({}.hasOwnProperty.call(searchWords, idx)) {
+ let searchWord = searchWords[idx];
+ if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) {
+ highlightSearchClass = ' search-highlight';
+ break;
+ } else if (searchWord.charAt(searchWord.length - 1) === '*') {
+ let searchWordPrefix = searchWord.slice(0, -1);
+ if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) {
+ highlightSearchClass = ' search-highlight';
+ break;
+ }
+ }
+ }
+ }
}
if (explicitMention &&
(UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
- {
- var name = explicitMention[1];
- // do both a non-case sensitive and case senstive check
- var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : '';
-
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) {
+ let name = explicitMention[1];
- if (searchTerm === name) {
- highlightSearchClass = ' search-highlight';
- }
+ // do both a non-case sensitive and case senstive check
+ let mClass = '';
+ if (('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
+ mClass = mentionClass;
+ }
- inner.push(<span key={name+i+z+'_span'}>{prefix}<a className={mClass + highlightSearchClass + ' mention-link'} key={name+i+z+'_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
- } else if (testUrlMatch(word).length) {
- var match = testUrlMatch(word)[0];
- var link = match.link;
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
- var prefix = word.substring(0,word.indexOf(match.text));
- var suffix = word.substring(word.indexOf(match.text)+match.text.length);
+ if (searchTerm === name) {
+ highlightSearchClass = ' search-highlight';
+ }
- inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>);
+ inner.push(
+ <span key={name + i + z + '_span'}>
+ {prefix}
+ <a
+ className={mClass + highlightSearchClass + ' mention-link'}
+ key={name + i + z + '_link'}
+ href='#'
+ onClick={() => searchForTerm(name)} //eslint-disable-line no-loop-func
+ >
+ @{name}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (testUrlMatch(word).length) {
+ let match = testUrlMatch(word)[0];
+ let link = match.link;
+
+ let prefix = word.substring(0, word.indexOf(match.text));
+ let suffix = word.substring(word.indexOf(match.text) + match.text.length);
+
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ key={word + i + z + '_link'}
+ className={'theme' + highlightSearchClass}
+ target='_blank'
+ href={link}
+ >
+ {match.text}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (trimWord.match(hashRegex)) {
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
+ let mClass = '';
+ if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ mClass = mentionClass;
+ }
- } else if (trimWord.match(hashRegex)) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
- var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : '';
+ if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
+ highlightSearchClass = ' search-highlight';
+ }
- if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ key={word + i + z + '_hash'}
+ className={'theme ' + mClass + highlightSearchClass}
+ href='#'
+ onClick={searchForTerm.bind(this, trimWord)} //eslint-disable-line no-loop-func
+ >
+ {trimWord}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
+ } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ let suffix = word.match(puncEndRegex);
+ let prefix = word.match(puncStartRegex);
+
+ if (trimWord.charAt(0) === '@') {
+ if (searchTerm === trimWord.substring(1).toLowerCase()) {
highlightSearchClass = ' search-highlight';
}
-
- inner.push(<span key={word+i+z+'_span'}>{prefix}<a key={word+i+z+'_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
-
- } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- var suffix = word.match(puncEndRegex);
- var prefix = word.match(puncStartRegex);
-
- if (trimWord.charAt(0) === '@') {
- if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
- inner.push(<span key={word+i+z+'_span'} key={name+i+z+'_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+'_link'} href='#'>{trimWord}</a>{suffix} </span>);
- } else {
- inner.push(<span key={word+i+z+'_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
- }
-
- } else if (word === '') {
- // if word is empty dont include a span
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <a
+ className={mentionClass + highlightSearchClass}
+ key={name + i + z + '_link'}
+ href='#'
+ >
+ {trimWord}
+ </a>
+ {suffix}
+ {' '}
+ </span>
+ );
} else {
- inner.push(<span key={word+i+z+'_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ {prefix}
+ <span className={mentionClass + highlightSearchClass}>
+ {replaceHtmlEntities(trimWord)}
+ </span>
+ {suffix}
+ {' '}
+ </span>
+ );
}
- highlightSearchClass = '';
+ } else if (word === '') {
+
+ // if word is empty dont include a span
+
+ } else {
+ inner.push(
+ <span key={word + i + z + '_span'}>
+ <span className={highlightSearchClass}>
+ {replaceHtmlEntities(word)}
+ </span>
+ {' '}
+ </span>
+ );
+ }
+ highlightSearchClass = '';
+ }
+ if (i !== lines.length - 1) {
+ inner.push(
+ <br key={'br_' + i}/>
+ );
}
- if (i != lines.length-1)
- inner.push(<br key={'br_'+i+z}/>);
}
return inner;
}
-module.exports.getFileType = function(extin) {
+export function getFileType(extin) {
var ext = extin.toLowerCase();
if (Constants.IMAGE_TYPES.indexOf(ext) > -1) {
return 'image';
@@ -550,8 +659,8 @@ module.exports.getFileType = function(extin) {
return 'word';
}
- if (Constants.EXCEL_TYPES.indexOf(ext) > -1) {
- return 'excel';
+ if (Constants.PRESENTATION_TYPES.indexOf(ext) > -1) {
+ return 'presentation';
}
if (Constants.PDF_TYPES.indexOf(ext) > -1) {
@@ -563,9 +672,9 @@ module.exports.getFileType = function(extin) {
}
return 'other';
-};
+}
-module.exports.getPreviewImagePathForFileType = function(fileTypeIn) {
+export function getPreviewImagePathForFileType(fileTypeIn) {
var fileType = fileTypeIn.toLowerCase();
var icon;
@@ -576,9 +685,9 @@ module.exports.getPreviewImagePathForFileType = function(fileTypeIn) {
}
return '/static/images/icons/' + icon + '.png';
-};
+}
-module.exports.getIconClassName = function(fileTypeIn) {
+export function getIconClassName(fileTypeIn) {
var fileType = fileTypeIn.toLowerCase();
if (fileType in Constants.ICON_FROM_TYPE) {
@@ -586,9 +695,9 @@ module.exports.getIconClassName = function(fileTypeIn) {
}
return 'glyphicon-file';
-};
+}
-module.exports.splitFileLocation = function(fileLocation) {
+export function splitFileLocation(fileLocation) {
var fileSplit = fileLocation.split('.');
var ext = '';
@@ -601,16 +710,16 @@ module.exports.splitFileLocation = function(fileLocation) {
var filename = filePath.split('/')[filePath.split('/').length - 1];
return {ext: ext, name: filename, path: filePath};
-};
+}
-module.exports.toTitleCase = function(str) {
+export function toTitleCase(str) {
function doTitleCase(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
return str.replace(/\w\S*/g, doTitleCase);
-};
+}
-module.exports.changeCss = function(className, classValue) {
+export function changeCss(className, classValue) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
if (cssMainContainer.length === 0) {
@@ -628,9 +737,9 @@ module.exports.changeCss = function(className, classValue) {
// append additional style
classContainer.html('<style>' + className + ' {' + classValue + '}</style>');
-};
+}
-module.exports.rgb2hex = function(rgbIn) {
+export function rgb2hex(rgbIn) {
if (/^#[0-9A-F]{6}$/i.test(rgbIn)) {
return rgbIn;
}
@@ -640,9 +749,9 @@ module.exports.rgb2hex = function(rgbIn) {
return ('0' + parseInt(x, 10).toString(16)).slice(-2);
}
return '#' + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
-};
+}
-module.exports.placeCaretAtEnd = function(el) {
+export function placeCaretAtEnd(el) {
el.focus();
if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') {
var range = document.createRange();
@@ -657,9 +766,9 @@ module.exports.placeCaretAtEnd = function(el) {
textRange.collapse(false);
textRange.select();
}
-};
+}
-module.exports.getCaretPosition = function(el) {
+export function getCaretPosition(el) {
if (el.selectionStart) {
return el.selectionStart;
} else if (document.selection) {
@@ -678,9 +787,9 @@ module.exports.getCaretPosition = function(el) {
return rc.text.length;
}
return 0;
-};
+}
-module.exports.setSelectionRange = function(input, selectionStart, selectionEnd) {
+export function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
@@ -691,13 +800,13 @@ module.exports.setSelectionRange = function(input, selectionStart, selectionEnd)
range.moveStart('character', selectionStart);
range.select();
}
-};
+}
-module.exports.setCaretPosition = function(input, pos) {
- module.exports.setSelectionRange(input, pos, pos);
-};
+export function setCaretPosition(input, pos) {
+ setSelectionRange(input, pos, pos);
+}
-module.exports.getSelectedText = function(input) {
+export function getSelectedText(input) {
var selectedText;
if (typeof document.selection !== 'undefined') {
input.focus();
@@ -710,9 +819,9 @@ module.exports.getSelectedText = function(input) {
}
return selectedText;
-};
+}
-module.exports.isValidUsername = function(name) {
+export function isValidUsername(name) {
var error = '';
if (!name) {
error = 'This field is required';
@@ -734,20 +843,18 @@ module.exports.isValidUsername = function(name) {
}
return error;
-};
+}
-function updateTabTitle(name) {
+export function updateTabTitle(name) {
document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
}
-module.exports.updateTabTitle = updateTabTitle;
-function updateAddressBar(channelName) {
+export function updateAddressBar(channelName) {
var teamURL = window.location.href.split('/channels')[0];
history.replaceState('data', '', teamURL + '/channels/' + channelName);
}
-module.exports.updateAddressBar = updateAddressBar;
-function switchChannel(channel, teammateName) {
+export function switchChannel(channel, teammateName) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
@@ -773,20 +880,19 @@ function switchChannel(channel, teammateName) {
return false;
}
-module.exports.switchChannel = switchChannel;
-module.exports.isMobile = function() {
+export function isMobile() {
return screen.width <= 768;
-};
+}
-module.exports.isComment = function(post) {
+export function isComment(post) {
if ('root_id' in post) {
return post.root_id !== '';
}
return false;
-};
+}
-module.exports.getDirectTeammate = function(channelId) {
+export function getDirectTeammate(channelId) {
var userIds = ChannelStore.get(channelId).name.split('__');
var curUserId = UserStore.getCurrentId();
var teammate = {};
@@ -803,9 +909,9 @@ module.exports.getDirectTeammate = function(channelId) {
}
return teammate;
-};
+}
-Image.prototype.load = function(url, progressCallback) {
+Image.prototype.load = function imageLoad(url, progressCallback) {
var self = this;
var xmlHTTP = new XMLHttpRequest();
xmlHTTP.open('GET', url, true);
@@ -832,7 +938,7 @@ Image.prototype.load = function(url, progressCallback) {
Image.prototype.completedPercentage = 0;
-module.exports.changeColor = function(colourIn, amt) {
+export function changeColor(colourIn, amt) {
var usePound = false;
var col = colourIn;
@@ -873,10 +979,9 @@ module.exports.changeColor = function(colourIn, amt) {
}
return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
-};
-
-module.exports.changeOpacity = function(oldColor, opacity) {
+}
+export function changeOpacity(oldColor, opacity) {
var col = oldColor;
if (col[0] === '#') {
col = col.slice(1);
@@ -887,9 +992,9 @@ module.exports.changeOpacity = function(oldColor, opacity) {
var b = parseInt(col.substring(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
-};
+}
-module.exports.getFullName = function(user) {
+export function getFullName(user) {
if (user.first_name && user.last_name) {
return user.first_name + ' ' + user.last_name;
} else if (user.first_name) {
@@ -899,23 +1004,23 @@ module.exports.getFullName = function(user) {
}
return '';
-};
+}
-module.exports.getDisplayName = function(user) {
+export function getDisplayName(user) {
if (user.nickname && user.nickname.trim().length > 0) {
return user.nickname;
}
- var fullName = module.exports.getFullName(user);
+ var fullName = getFullName(user);
if (fullName) {
return fullName;
}
return user.username;
-};
+}
//IE10 does not set window.location.origin automatically so this must be called instead when using it
-module.exports.getWindowLocationOrigin = function() {
+export function getWindowLocationOrigin() {
var windowLocationOrigin = window.location.origin;
if (!windowLocationOrigin) {
windowLocationOrigin = window.location.protocol + '//' + window.location.hostname;
@@ -924,10 +1029,10 @@ module.exports.getWindowLocationOrigin = function() {
}
}
return windowLocationOrigin;
-};
+}
// Converts a file size in bytes into a human-readable string of the form '123MB'.
-module.exports.fileSizeToString = function(bytes) {
+export function fileSizeToString(bytes) {
// it's unlikely that we'll have files bigger than this
if (bytes > 1024 * 1024 * 1024 * 1024) {
return Math.floor(bytes / (1024 * 1024 * 1024 * 1024)) + 'TB';
@@ -940,29 +1045,29 @@ module.exports.fileSizeToString = function(bytes) {
}
return bytes + 'B';
-};
+}
// Converts a filename (like those attached to Post objects) to a url that can be used to retrieve attachments from the server.
-module.exports.getFileUrl = function(filename) {
+export function getFileUrl(filename) {
var url = filename;
// This is a temporary patch to fix issue with old files using absolute paths
if (url.indexOf('/api/v1/files/get') !== -1) {
url = filename.split('/api/v1/files/get')[1];
}
- url = module.exports.getWindowLocationOrigin() + '/api/v1/files/get' + url;
+ url = getWindowLocationOrigin() + '/api/v1/files/get' + url;
return url;
-};
+}
// Gets the name of a file (including extension) from a given url or file path.
-module.exports.getFileName = function(path) {
+export function getFileName(path) {
var split = path.split('/');
return split[split.length - 1];
-};
+}
// Generates a RFC-4122 version 4 compliant globally unique identifier.
-module.exports.generateId = function() {
+export function generateId() {
// implementation taken from http://stackoverflow.com/a/2117523
var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
@@ -980,14 +1085,14 @@ module.exports.generateId = function() {
});
return id;
-};
+}
-module.exports.isBrowserFirefox = function() {
+export function isBrowserFirefox() {
return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
-};
+}
// Checks if browser is IE10 or IE11
-module.exports.isBrowserIE = function() {
+export function isBrowserIE() {
if (window.navigator && window.navigator.userAgent) {
var ua = window.navigator.userAgent;
@@ -995,14 +1100,14 @@ module.exports.isBrowserIE = function() {
}
return false;
-};
+}
-module.exports.isBrowserEdge = function() {
+export function isBrowserEdge() {
return window.naviagtor && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1;
-};
+}
// Used to get the id of the other user from a DM channel
-module.exports.getUserIdFromChannelName = function(channel) {
+export function getUserIdFromChannelName(channel) {
var ids = channel.name.split('__');
var otherUserId = '';
if (ids[0] === UserStore.getCurrentId()) {
@@ -1012,13 +1117,13 @@ module.exports.getUserIdFromChannelName = function(channel) {
}
return otherUserId;
-};
+}
-module.exports.importSlack = function(file, success, error) {
+export function importSlack(file, success, error) {
var formData = new FormData();
formData.append('file', file, file.name);
formData.append('filesize', file.size);
formData.append('importFrom', 'slack');
client.importSlack(formData, success, error);
-};
+}