summaryrefslogtreecommitdiffstats
path: root/web/react/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components')
-rw-r--r--web/react/components/access_history_modal.jsx2
-rw-r--r--web/react/components/activity_log_modal.jsx2
-rw-r--r--web/react/components/admin_console/email_settings.jsx16
-rw-r--r--web/react/components/channel_header.jsx8
-rw-r--r--web/react/components/channel_invite_modal.jsx2
-rw-r--r--web/react/components/channel_members_modal.jsx2
-rw-r--r--web/react/components/channel_notifications.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx2
-rw-r--r--web/react/components/file_attachment.jsx2
-rw-r--r--web/react/components/invite_member_modal.jsx44
-rw-r--r--web/react/components/login.jsx4
-rw-r--r--web/react/components/more_channels.jsx2
-rw-r--r--web/react/components/navbar_dropdown.jsx2
-rw-r--r--web/react/components/new_channel_modal.jsx2
-rw-r--r--web/react/components/notify_counts.jsx2
-rw-r--r--web/react/components/popover_list_members.jsx22
-rw-r--r--web/react/components/post.jsx2
-rw-r--r--web/react/components/post_attachment_oembed.jsx83
-rw-r--r--web/react/components/post_body.jsx100
-rw-r--r--web/react/components/post_body_additional_content.jsx20
-rw-r--r--web/react/components/posts_view.jsx10
-rw-r--r--web/react/components/posts_view_container.jsx2
-rw-r--r--web/react/components/providers.json324
-rw-r--r--web/react/components/rhs_comment.jsx2
-rw-r--r--web/react/components/rhs_root_post.jsx2
-rw-r--r--web/react/components/rhs_thread.jsx4
-rw-r--r--web/react/components/search_autocomplete.jsx12
-rw-r--r--web/react/components/search_bar.jsx2
-rw-r--r--web/react/components/search_results.jsx2
-rw-r--r--web/react/components/settings_sidebar.jsx4
-rw-r--r--web/react/components/sidebar.jsx9
-rw-r--r--web/react/components/sidebar_right.jsx4
-rw-r--r--web/react/components/team_general_tab.jsx3
-rw-r--r--web/react/components/team_members.jsx2
-rw-r--r--web/react/components/team_settings.jsx2
-rw-r--r--web/react/components/textbox.jsx39
-rw-r--r--web/react/components/tutorial/tutorial_intro_screens.jsx12
-rw-r--r--web/react/components/tutorial/tutorial_tip.jsx27
-rw-r--r--web/react/components/user_profile.jsx4
-rw-r--r--web/react/components/user_settings/user_settings.jsx2
-rw-r--r--web/react/components/user_settings/user_settings_appearance.jsx12
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx164
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx20
43 files changed, 761 insertions, 224 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index 27959ec7e..ab5686720 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -54,7 +54,7 @@ export default class AccessHistoryModal extends React.Component {
}
onAuditChange() {
var newState = this.getStateFromStoresForAudits();
- if (!Utils.areStatesEqual(newState.audits, this.state.audits)) {
+ if (!Utils.areObjectsEqual(newState.audits, this.state.audits)) {
this.setState(newState);
}
}
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index ef3077470..af423a601 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -73,7 +73,7 @@ export default class ActivityLogModal extends React.Component {
}
onListenerChange() {
const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(newState.sessions, this.state.sessions)) {
+ if (!Utils.areObjectsEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
}
}
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 40e00ff04..0cabf7f70 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -296,7 +296,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackName'
ref='feedbackName'
- placeholder='Ex: "Mattermost Notification", "System", "No-Reply"'
+ placeholder='E.g.: "Mattermost Notification", "System", "No-Reply"'
defaultValue={this.props.config.EmailSettings.FeedbackName}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -318,7 +318,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='feedbackEmail'
ref='feedbackEmail'
- placeholder='Ex: "mattermost@yourcompany.com", "admin@yourcompany.com"'
+ placeholder='E.g.: "mattermost@yourcompany.com", "admin@yourcompany.com"'
defaultValue={this.props.config.EmailSettings.FeedbackEmail}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -340,7 +340,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPUsername'
ref='SMTPUsername'
- placeholder='Ex: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
+ placeholder='E.g.: "admin@yourcompany.com", "AKIADTOVBGERKLCBV"'
defaultValue={this.props.config.EmailSettings.SMTPUsername}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -362,7 +362,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPassword'
ref='SMTPPassword'
- placeholder='Ex: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ placeholder='E.g.: "yourpassword", "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
defaultValue={this.props.config.EmailSettings.SMTPPassword}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -384,7 +384,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPServer'
ref='SMTPServer'
- placeholder='Ex: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
+ placeholder='E.g.: "smtp.yourcompany.com", "email-smtp.us-east-1.amazonaws.com"'
defaultValue={this.props.config.EmailSettings.SMTPServer}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -406,7 +406,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='SMTPPort'
ref='SMTPPort'
- placeholder='Ex: "25", "465"'
+ placeholder='E.g.: "25", "465"'
defaultValue={this.props.config.EmailSettings.SMTPPort}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -476,7 +476,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='InviteSalt'
ref='InviteSalt'
- placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
defaultValue={this.props.config.EmailSettings.InviteSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
@@ -507,7 +507,7 @@ export default class EmailSettings extends React.Component {
className='form-control'
id='PasswordResetSalt'
ref='PasswordResetSalt'
- placeholder='Ex "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
+ placeholder='E.g.: "bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo"'
defaultValue={this.props.config.EmailSettings.PasswordResetSalt}
onChange={this.handleChange}
disabled={!this.state.sendEmailNotifications}
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 895dc5fe4..a8d4ec100 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -39,11 +39,14 @@ export default class ChannelHeader extends React.Component {
this.state = state;
}
getStateFromStores() {
+ const extraInfo = ChannelStore.getCurrentExtraInfo();
+
return {
channel: ChannelStore.getCurrent(),
memberChannel: ChannelStore.getCurrentMember(),
memberTeam: UserStore.getCurrentUser(),
- users: ChannelStore.getCurrentExtraInfo().members,
+ users: extraInfo.members,
+ userCount: extraInfo.member_count,
searchVisible: SearchStore.getSearchResults() !== null
};
}
@@ -63,7 +66,7 @@ export default class ChannelHeader extends React.Component {
}
onListenerChange() {
const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
@@ -373,6 +376,7 @@ export default class ChannelHeader extends React.Component {
<th>
<PopoverListMembers
members={this.state.users}
+ memberCount={this.state.userCount}
channelId={channel.id}
/>
</th>
diff --git a/web/react/components/channel_invite_modal.jsx b/web/react/components/channel_invite_modal.jsx
index 7c1032321..47bc50971 100644
--- a/web/react/components/channel_invite_modal.jsx
+++ b/web/react/components/channel_invite_modal.jsx
@@ -78,7 +78,7 @@ export default class ChannelInviteModal extends React.Component {
}
onListenerChange() {
var newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(this.state, newState)) {
+ if (!Utils.areObjectsEqual(this.state, newState)) {
this.setState(newState);
}
}
diff --git a/web/react/components/channel_members_modal.jsx b/web/react/components/channel_members_modal.jsx
index 2fa7ae8ff..5cf3511f4 100644
--- a/web/react/components/channel_members_modal.jsx
+++ b/web/react/components/channel_members_modal.jsx
@@ -91,7 +91,7 @@ export default class ChannelMembersModal extends React.Component {
}
onChange() {
const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(this.state, newState)) {
+ if (!Utils.areObjectsEqual(this.state, newState)) {
this.setState(newState);
}
}
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications.jsx
index 43700bf36..f57fc12c5 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications.jsx
@@ -69,7 +69,7 @@ export default class ChannelNotifications extends React.Component {
newState.notifyLevel = notifyLevel;
newState.markUnreadLevel = markUnreadLevel;
- if (!Utils.areStatesEqual(this.state, newState)) {
+ if (!Utils.areObjectsEqual(this.state, newState)) {
this.setState(newState);
}
}
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 3a3dabce5..f3bead1c2 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -81,7 +81,7 @@ export default class DeletePostModal extends React.Component {
}
onListenerChange() {
var newList = PostStore.getSelectedPost();
- if (!Utils.areStatesEqual(this.state.selectedList, newList)) {
+ if (!Utils.areObjectsEqual(this.state.selectedList, newList)) {
this.setState({selectedList: newList});
}
}
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index e707e32f5..d6a30abf9 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -67,7 +67,7 @@ export default class FileAttachment extends React.Component {
this.canSetState = false;
}
shouldComponentUpdate(nextProps, nextState) {
- if (!utils.areStatesEqual(nextProps, this.props)) {
+ if (!utils.areObjectsEqual(nextProps, this.props)) {
return true;
}
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index c09477a69..3f6ad3358 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -31,7 +31,8 @@ export default class InviteMemberModal extends React.Component {
firstNameErrors: {},
lastNameErrors: {},
emailEnabled: global.window.mm_config.SendEmailNotifications === 'true',
- showConfirmModal: false
+ showConfirmModal: false,
+ isSendingEmails: false
};
}
@@ -89,10 +90,13 @@ export default class InviteMemberModal extends React.Component {
var data = {};
data.invites = invites;
+ this.setState({isSendingEmails: true});
+
Client.inviteMembers(
data,
() => {
this.handleHide(false);
+ this.setState({isSendingEmails: false});
},
(err) => {
if (err.message === 'This person is already on your team') {
@@ -101,6 +105,8 @@ export default class InviteMemberModal extends React.Component {
} else {
this.setState({serverError: err.message});
}
+
+ this.setState({isSendingEmails: false});
}
);
}
@@ -289,11 +295,6 @@ export default class InviteMemberModal extends React.Component {
var content = null;
var sendButton = null;
- var sendButtonLabel = 'Send Invitation';
- if (this.state.inviteIds.length > 1) {
- sendButtonLabel = 'Send Invitations';
- }
-
if (this.state.emailEnabled) {
content = (
<div>
@@ -309,14 +310,25 @@ export default class InviteMemberModal extends React.Component {
</div>
);
- sendButton =
- (
- <button
- onClick={this.handleSubmit}
- type='button'
- className='btn btn-primary'
- >{sendButtonLabel}</button>
+ var sendButtonLabel = 'Send Invitation';
+ if (this.state.isSendingEmails) {
+ sendButtonLabel = (
+ <span><i className='fa fa-spinner fa-spin' />{' Sending'}</span>
);
+ } else if (this.state.inviteIds.length > 1) {
+ sendButtonLabel = 'Send Invitations';
+ }
+
+ sendButton = (
+ <button
+ onClick={this.handleSubmit}
+ type='button'
+ className='btn btn-primary'
+ disabled={this.state.isSendingEmails}
+ >
+ {sendButtonLabel}
+ </button>
+ );
} else {
var teamInviteLink = null;
if (currentUser && TeamStore.getCurrent().type === 'O') {
@@ -351,12 +363,13 @@ export default class InviteMemberModal extends React.Component {
return (
<div>
<Modal
- className='modal-invite-member'
+ dialogClassName='modal-invite-member'
show={this.state.show}
onHide={this.handleHide.bind(this, true)}
enforceFocus={!this.state.showConfirmModal}
+ backdrop={this.state.isSendingEmails ? 'static' : true}
>
- <Modal.Header closeButton={true}>
+ <Modal.Header closeButton={!this.state.isSendingEmails}>
<Modal.Title>{'Invite New Member'}</Modal.Title>
</Modal.Header>
<Modal.Body ref='modalBody'>
@@ -370,6 +383,7 @@ export default class InviteMemberModal extends React.Component {
type='button'
className='btn btn-default'
onClick={this.handleHide.bind(this, true)}
+ disabled={this.state.isSendingEmails}
>
{'Cancel'}
</button>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 2b9ce67ca..423ba9067 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -125,7 +125,7 @@ export default class Login extends React.Component {
let emailSignup;
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
emailSignup = (
- <div>
+ <div className='signup__email-container'>
<div className={'form-group' + errorClass}>
<input
autoFocus={focusEmail}
@@ -206,7 +206,7 @@ export default class Login extends React.Component {
href='/'
className='signup-team-login'
>
- {'Sign up now'}
+ {'Create one now'}
</a>
</span>
</div>
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index c4f831c2e..8a6dd84a4 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -46,7 +46,7 @@ export default class MoreChannels extends React.Component {
}
onListenerChange() {
var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState.channels, this.state.channels)) {
+ if (!utils.areObjectsEqual(newState.channels, this.state.channels)) {
this.setState(newState);
}
}
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 0b755f377..cf9db055d 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -70,7 +70,7 @@ export default class NavbarDropdown extends React.Component {
}
onListenerChange() {
var newState = getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx
index c0cea496f..2c044cd5d 100644
--- a/web/react/components/new_channel_modal.jsx
+++ b/web/react/components/new_channel_modal.jsx
@@ -115,7 +115,7 @@ export default class NewChannelModal extends React.Component {
type='text'
ref='display_name'
className='form-control'
- placeholder='Ex: "Bugs", "Marketing", "办公室恋情"'
+ placeholder='E.g.: "Bugs", "Marketing", "办公室恋情"'
maxLength='22'
value={this.props.channelData.displayName}
autoFocus={true}
diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx
index 54b9e4289..0a4f60989 100644
--- a/web/react/components/notify_counts.jsx
+++ b/web/react/components/notify_counts.jsx
@@ -39,7 +39,7 @@ export default class NotifyCounts extends React.Component {
}
onListenerChange() {
var newState = getCountsStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ if (!utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index f3c0fa0b4..102bddcf5 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -69,8 +69,6 @@ export default class PopoverListMembers extends React.Component {
render() {
let popoverHtml = [];
- let count = 0;
- let countText = '-';
const members = this.props.members;
const teamMembers = UserStore.getProfilesUsernameMap();
const currentUserId = UserStore.getCurrentId();
@@ -147,15 +145,22 @@ export default class PopoverListMembers extends React.Component {
</div>
</div>
);
- count++;
}
});
+ }
- if (count > 20) {
- countText = '20+';
- } else if (count > 0) {
- countText = count.toString();
- }
+ let count = this.props.memberCount;
+ let countText = '-';
+
+ // fall back to checking the length of the member list if the count isn't set
+ if (!count && members) {
+ count = members.length;
+ }
+
+ if (count > 20) {
+ countText = '20+';
+ } else if (count > 0) {
+ countText = count.toString();
}
return (
@@ -195,5 +200,6 @@ export default class PopoverListMembers extends React.Component {
PopoverListMembers.propTypes = {
members: React.PropTypes.array.isRequired,
+ memberCount: React.PropTypes.number,
channelId: React.PropTypes.string.isRequired
};
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index c3c5b3e0b..2b9586345 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -77,7 +77,7 @@ export default class Post extends React.Component {
this.forceUpdate();
}
shouldComponentUpdate(nextProps) {
- if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ if (!utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
diff --git a/web/react/components/post_attachment_oembed.jsx b/web/react/components/post_attachment_oembed.jsx
new file mode 100644
index 000000000..f544dbc88
--- /dev/null
+++ b/web/react/components/post_attachment_oembed.jsx
@@ -0,0 +1,83 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class PostAttachmentOEmbed extends React.Component {
+ constructor(props) {
+ super(props);
+ this.fetchData = this.fetchData.bind(this);
+
+ this.isLoading = false;
+ }
+
+ componentWillMount() {
+ this.setState({data: {}});
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.fetchData(nextProps.link);
+ }
+
+ fetchData(link) {
+ if (!this.isLoading) {
+ this.isLoading = true;
+ return $.ajax({
+ url: 'https://noembed.com/embed?nowrap=on&url=' + encodeURIComponent(link),
+ dataType: 'jsonp',
+ success: (result) => {
+ this.isLoading = false;
+ if (result.error) {
+ this.setState({data: {}});
+ } else {
+ this.setState({data: result});
+ }
+ },
+ error: () => {
+ this.setState({data: {}});
+ }
+ });
+ }
+ }
+
+ render() {
+ if ($.isEmptyObject(this.state.data)) {
+ return <div></div>;
+ }
+
+ return (
+ <div
+ className='attachment attachment--oembed'
+ ref='attachment'
+ >
+ <div className='attachment__content'>
+ <div
+ className={'clearfix attachment__container'}
+ >
+ <h1
+ className='attachment__title'
+ >
+ <a
+ className='attachment__title-link'
+ href={this.state.data.url}
+ target='_blank'
+ >
+ {this.state.data.title}
+ </a>
+ </h1>
+ <div>
+ <div className={'attachment__body attachment__body--no_thumb'}>
+ <div
+ dangerouslySetInnerHTML={{__html: this.state.data.html}}
+ >
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+PostAttachmentOEmbed.propTypes = {
+ link: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 61a0c3e2d..975ac64dc 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -9,18 +9,20 @@ const TextFormatting = require('../utils/text_formatting.jsx');
const twemoji = require('twemoji');
const PostBodyAdditionalContent = require('./post_body_additional_content.jsx');
+const providers = require('./providers.json');
+
export default class PostBody extends React.Component {
constructor(props) {
super(props);
this.receivedYoutubeData = false;
- this.isGifLoading = false;
+ this.isImgLoading = false;
this.handleUserChange = this.handleUserChange.bind(this);
this.parseEmojis = this.parseEmojis.bind(this);
this.createEmbed = this.createEmbed.bind(this);
- this.createGifEmbed = this.createGifEmbed.bind(this);
- this.loadGif = this.loadGif.bind(this);
+ this.createImageEmbed = this.createImageEmbed.bind(this);
+ this.loadImg = this.loadImg.bind(this);
this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
const linkData = Utils.extractLinks(this.props.post.message);
@@ -29,6 +31,7 @@ export default class PostBody extends React.Component {
this.state = {
links: linkData.links,
message: linkData.text,
+ post: this.props.post,
hasUserProfiles: profiles && Object.keys(profiles).length > 1
};
}
@@ -52,6 +55,12 @@ export default class PostBody extends React.Component {
twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE});
}
+ componentWillMount() {
+ if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) {
+ this.embed = this.createEmbed(this.state.links[0]);
+ }
+ }
+
componentDidMount() {
this.parseEmojis();
@@ -76,47 +85,83 @@ export default class PostBody extends React.Component {
componentWillReceiveProps(nextProps) {
const linkData = Utils.extractLinks(nextProps.post.message);
+ if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) {
+ this.embed = this.createEmbed(linkData.links[0]);
+ }
this.setState({links: linkData.links, message: linkData.text});
}
createEmbed(link) {
- let embed = this.createYoutubeEmbed(link);
+ const post = this.state.post;
+
+ if (!link) {
+ if (post.type === 'oEmbed') {
+ post.props.oEmbedLink = '';
+ post.type = '';
+ }
+ return null;
+ }
+
+ const trimmedLink = link.trim();
+
+ if (this.checkForOembedContent(trimmedLink)) {
+ post.props.oEmbedLink = trimmedLink;
+ post.type = 'oEmbed';
+ this.setState({post});
+ return '';
+ }
+
+ const embed = this.createYoutubeEmbed(link);
if (embed != null) {
return embed;
}
- embed = this.createGifEmbed(link);
+ for (let i = 0; i < Constants.IMAGE_TYPES.length; i++) {
+ const imageType = Constants.IMAGE_TYPES[i];
+ const suffix = link.substring(link.length - (imageType.length + 1));
+ if (suffix === '.' + imageType || suffix === '=' + imageType) {
+ return this.createImageEmbed(link, this.state.imgLoaded);
+ }
+ }
- return embed;
+ return null;
}
- loadGif(src) {
- if (this.isGifLoading) {
+ checkForOembedContent(link) {
+ for (let i = 0; i < providers.length; i++) {
+ for (let j = 0; j < providers[i].patterns.length; j++) {
+ if (link.match(providers[i].patterns[j])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ loadImg(src) {
+ if (this.isImgLoading) {
return;
}
- this.isGifLoading = true;
+ this.isImgLoading = true;
- const gif = new Image();
- gif.onload = (
+ const img = new Image();
+ img.onload = (
() => {
- this.setState({gifLoaded: true});
+ this.embed = this.createImageEmbed(src, true);
+ this.setState({imgLoaded: true});
}
);
- gif.src = src;
+ img.src = src;
}
- createGifEmbed(link) {
- if (link.substring(link.length - 4) !== '.gif') {
- return null;
- }
-
- if (!this.state.gifLoaded) {
- this.loadGif(link);
+ createImageEmbed(link, isLoaded) {
+ if (!isLoaded) {
+ this.loadImg(link);
return (
<img
- className='gif-div placeholder'
+ className='img-div placeholder'
height='500px'
/>
);
@@ -124,7 +169,7 @@ export default class PostBody extends React.Component {
return (
<img
- className='gif-div'
+ className='img-div'
src={link}
/>
);
@@ -133,7 +178,7 @@ export default class PostBody extends React.Component {
handleYoutubeTime(link) {
const timeRegex = /[\\?&]t=([0-9hms]+)/;
- const time = link.trim().match(timeRegex);
+ const time = link.match(timeRegex);
if (!time || !time[1]) {
return '';
}
@@ -322,11 +367,6 @@ export default class PostBody extends React.Component {
);
}
- let embed;
- if (filenames.length === 0 && this.state.links && this.state.links.length > 0) {
- embed = this.createEmbed(this.state.links[0]);
- }
-
let fileAttachmentHolder = '';
if (filenames && filenames.length > 0) {
fileAttachmentHolder = (
@@ -354,10 +394,10 @@ export default class PostBody extends React.Component {
/>
</div>
<PostBodyAdditionalContent
- post={post}
+ post={this.state.post}
/>
{fileAttachmentHolder}
- {embed}
+ {this.embed}
</div>
);
}
diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx
index 8189ba2d3..0c2c44286 100644
--- a/web/react/components/post_body_additional_content.jsx
+++ b/web/react/components/post_body_additional_content.jsx
@@ -2,12 +2,14 @@
// See License.txt for license information.
const PostAttachmentList = require('./post_attachment_list.jsx');
+const PostAttachmentOEmbed = require('./post_attachment_oembed.jsx');
export default class PostBodyAdditionalContent extends React.Component {
constructor(props) {
super(props);
this.getSlackAttachment = this.getSlackAttachment.bind(this);
+ this.getOembedAttachment = this.getOembedAttachment.bind(this);
this.getComponent = this.getComponent.bind(this);
}
@@ -25,17 +27,31 @@ export default class PostBodyAdditionalContent extends React.Component {
);
}
+ getOembedAttachment() {
+ const link = this.props.post.props && this.props.post.props.oEmbedLink || '';
+ return (
+ <PostAttachmentOEmbed
+ key={'post_body_additional_content' + this.props.post.id}
+ link={link}
+ />
+ );
+ }
+
getComponent() {
- switch (this.state.type) {
+ switch (this.props.post.type) {
case 'slack_attachment':
return this.getSlackAttachment();
+ case 'oEmbed':
+ return this.getOembedAttachment();
+ default:
+ return '';
}
}
render() {
let content = [];
- if (this.state.shouldRender) {
+ if (Boolean(this.props.post.type)) {
const component = this.getComponent();
if (component) {
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index b782268fa..087ca1df2 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -104,11 +104,13 @@ export default class PostsView extends React.Component {
// check if it's the last comment in a consecutive string of comments on the same post
// it is the last comment if it is last post in the channel or the next post has a different root post
- var isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
+ const isLastComment = Utils.isComment(post) && (i === 0 || posts[order[i - 1]].root_id !== post.root_id);
- var postCtl = (
+ const keyPrefix = post.id ? post.id : i;
+
+ const postCtl = (
<Post
- key={post.id + 'postKey'}
+ key={keyPrefix + 'postKey'}
ref={post.id}
sameUser={sameUser}
sameRoot={sameRoot}
@@ -240,7 +242,7 @@ export default class PostsView extends React.Component {
if (this.props.messageSeparatorTime !== nextProps.messageSeparatorTime) {
return true;
}
- if (!Utils.areStatesEqual(this.props.postList, nextProps.postList)) {
+ if (!Utils.areObjectsEqual(this.props.postList, nextProps.postList)) {
return true;
}
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 8b92a26a7..2cb56cd47 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -225,7 +225,7 @@ export default class PostsViewContainer extends React.Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
- if (Utils.areStatesEqual(this.state, nextState)) {
+ if (Utils.areObjectsEqual(this.state, nextState)) {
return false;
}
diff --git a/web/react/components/providers.json b/web/react/components/providers.json
new file mode 100644
index 000000000..5e4cbd656
--- /dev/null
+++ b/web/react/components/providers.json
@@ -0,0 +1,324 @@
+[
+ {
+ "patterns": [
+ "http://(?:www\\.)?xkcd\\.com/\\d+/?"
+ ],
+ "name": "XKCD"
+ },
+ {
+ "patterns": [
+ "https?://soundcloud.com/.*/.*"
+ ],
+ "name": "SoundCloud"
+ },
+ {
+ "patterns": [
+ "https?://(?:www\\.)?flickr\\.com/.*",
+ "https?://flic\\.kr/p/[a-zA-Z0-9]+"
+ ],
+ "name": "Flickr"
+ },
+ {
+ "patterns": [
+ "http://www\\.ted\\.com/talks/.+\\.html"
+ ],
+ "name": "TED"
+ },
+ {
+ "patterns": [
+ "http://(?:www\\.)?theverge\\.com/\\d{4}/\\d{1,2}/\\d{1,2}/\\d+/[^/]+/?$"
+ ],
+ "name": "The Verge"
+ },
+ {
+ "patterns": [
+ "http://.*\\.viddler\\.com/.*"
+ ],
+ "name": "Viddler"
+ },
+ {
+ "patterns": [
+ "https?://(?:www\\.)?avclub\\.com/article/[^/]+/?$"
+ ],
+ "name": "The AV Club"
+ },
+ {
+ "patterns": [
+ "https?://(?:www\\.)?wired\\.com/([^/]+/)?\\d+/\\d+/[^/]+/?$"
+ ],
+ "name": "Wired"
+ },
+ {
+ "patterns": [
+ "http://www\\.theonion\\.com/articles/[^/]+/?"
+ ],
+ "name": "The Onion"
+ },
+ {
+ "patterns": [
+ "http://yfrog\\.com/[0-9a-zA-Z]+/?$"
+ ],
+ "name": "YFrog"
+ },
+ {
+ "patterns": [
+ "http://www\\.duffelblog\\.com/\\d{4}/\\d{1,2}/[^/]+/?$"
+ ],
+ "name": "The Duffel Blog"
+ },
+ {
+ "patterns": [
+ "http://www\\.clickhole\\.com/article/[^/]+/?"
+ ],
+ "name": "Clickhole"
+ },
+ {
+ "patterns": [
+ "https?://(?:www.)?skitch.com/([^/]+)/[^/]+/.+",
+ "http://skit.ch/[^/]+"
+ ],
+ "name": "Skitch"
+ },
+ {
+ "patterns": [
+ "https?://(alpha|posts|photos)\\.app\\.net/.*"
+ ],
+ "name": "ADN"
+ },
+ {
+ "patterns": [
+ "https?://gist\\.github\\.com/(?:[-0-9a-zA-Z]+/)?([0-9a-fA-f]+)"
+ ],
+ "name": "Gist"
+ },
+ {
+ "patterns": [
+ "https?://www\\.(dropbox\\.com/s/.+\\.(?:jpg|png|gif))",
+ "https?://db\\.tt/[a-zA-Z0-9]+"
+ ],
+ "name": "Dropbox"
+ },
+ {
+ "patterns": [
+ "https?://[^\\.]+\\.wikipedia\\.org/wiki/(?!Talk:)[^#]+(?:#(.+))?"
+ ],
+ "name": "Wikipedia"
+ },
+ {
+ "patterns": [
+ "http://www.traileraddict.com/trailer/[^/]+/trailer"
+ ],
+ "name": "TrailerAddict"
+ },
+ {
+ "patterns": [
+ "http://lockerz\\.com/[sd]/\\d+"
+ ],
+ "name": "Lockerz"
+ },
+ {
+ "patterns": [
+ "http://gifuk\\.com/s/[0-9a-f]{16}"
+ ],
+ "name": "GIFUK"
+ },
+ {
+ "patterns": [
+ "http://trailers\\.apple\\.com/trailers/[^/]+/[^/]+"
+ ],
+ "name": "iTunes Movie Trailers"
+ },
+ {
+ "patterns": [
+ "http://gfycat\\.com/([a-zA-Z]+)"
+ ],
+ "name": "Gfycat"
+ },
+ {
+ "patterns": [
+ "http://bash\\.org/\\?(\\d+)"
+ ],
+ "name": "Bash.org"
+ },
+ {
+ "patterns": [
+ "http://arstechnica\\.com/[^/]+/\\d+/\\d+/[^/]+/?$"
+ ],
+ "name": "Ars Technica"
+ },
+ {
+ "patterns": [
+ "http://imgur\\.com/gallery/[0-9a-zA-Z]+"
+ ],
+ "name": "Imgur"
+ },
+ {
+ "patterns": [
+ "http://www\\.asciiartfarts\\.com/[0-9]+\\.html"
+ ],
+ "name": "ASCII Art Farts"
+ },
+ {
+ "patterns": [
+ "http://www\\.monoprice\\.com/products/product\\.asp\\?.*p_id=\\d+"
+ ],
+ "name": "Monoprice"
+ },
+ {
+ "patterns": [
+ "http://boingboing\\.net/\\d{4}/\\d{2}/\\d{2}/[^/]+\\.html"
+ ],
+ "name": "Boing Boing"
+ },
+ {
+ "patterns": [
+ "https?://github\\.com/([^/]+)/([^/]+)/commit/(.+)",
+ "http://git\\.io/[_0-9a-zA-Z]+"
+ ],
+ "name": "Github Commit"
+ },
+ {
+ "patterns": [
+ "https?://open\\.spotify\\.com/(track|album)/([0-9a-zA-Z]{22})"
+ ],
+ "name": "Spotify"
+ },
+ {
+ "patterns": [
+ "https?://path\\.com/p/([0-9a-zA-Z]+)$"
+ ],
+ "name": "Path"
+ },
+ {
+ "patterns": [
+ "http://www.funnyordie.com/videos/[^/]+/.+"
+ ],
+ "name": "Funny or Die"
+ },
+ {
+ "patterns": [
+ "http://(?:www\\.)?twitpic\\.com/([^/]+)"
+ ],
+ "name": "Twitpic"
+ },
+ {
+ "patterns": [
+ "https?://www\\.giantbomb\\.com/videos/[^/]+/\\d+-\\d+/?"
+ ],
+ "name": "GiantBomb"
+ },
+ {
+ "patterns": [
+ "http://(?:www\\.)?beeradvocate\\.com/beer/profile/\\d+/\\d+"
+ ],
+ "name": "Beer Advocate"
+ },
+ {
+ "patterns": [
+ "http://(?:www\\.)?imdb.com/title/(tt\\d+)"
+ ],
+ "name": "IMDB"
+ },
+ {
+ "patterns": [
+ "http://cl\\.ly/(?:image/)?[0-9a-zA-Z]+/?$"
+ ],
+ "name": "CloudApp"
+ },
+ {
+ "patterns": [
+ "http://clyp\\.it/.*"
+ ],
+ "name": "Clyp"
+ },
+ {
+ "patterns": [
+ "http://www\\.hulu\\.com/watch/.*"
+ ],
+ "name": "Hulu"
+ },
+ {
+ "patterns": [
+ "https?://(?:www|mobile\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/?$",
+ "https?://t\\.co/[a-zA-Z0-9]+"
+ ],
+ "name": "Twitter"
+ },
+ {
+ "patterns": [
+ "https?://(?:www\\.)?vimeo\\.com/.+"
+ ],
+ "name": "Vimeo"
+ },
+ {
+ "patterns": [
+ "http://www\\.amazon\\.com/(?:.+/)?[gd]p/(?:product/)?(?:tags-on-product/)?([a-zA-Z0-9]+)",
+ "http://amzn\\.com/([^/]+)"
+ ],
+ "name": "Amazon"
+ },
+ {
+ "patterns": [
+ "http://qik\\.com/video/.*"
+ ],
+ "name": "Qik"
+ },
+ {
+ "patterns": [
+ "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/?",
+ "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/track/[^/]+/?",
+ "http://www\\.rdio\\.com/people/[^/]+/playlists/\\d+/[^/]+"
+ ],
+ "name": "Rdio"
+ },
+ {
+ "patterns": [
+ "http://www\\.slideshare\\.net/.*/.*"
+ ],
+ "name": "SlideShare"
+ },
+ {
+ "patterns": [
+ "http://imgur\\.com/([0-9a-zA-Z]+)$"
+ ],
+ "name": "Imgur"
+ },
+ {
+ "patterns": [
+ "https?://instagr(?:\\.am|am\\.com)/p/.+"
+ ],
+ "name": "Instagram"
+ },
+ {
+ "patterns": [
+ "http://www\\.twitlonger\\.com/show/[a-zA-Z0-9]+",
+ "http://tl\\.gd/[^/]+"
+ ],
+ "name": "Twitlonger"
+ },
+ {
+ "patterns": [
+ "https?://vine.co/v/[a-zA-Z0-9]+"
+ ],
+ "name": "Vine"
+ },
+ {
+ "patterns": [
+ "http://www\\.urbandictionary\\.com/define\\.php\\?term=.+"
+ ],
+ "name": "Urban Dictionary"
+ },
+ {
+ "patterns": [
+ "http://picplz\\.com/user/[^/]+/pic/[^/]+"
+ ],
+ "name": "Picplz"
+ },
+ {
+ "patterns": [
+ "https?://(?:www\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/photo/\\d+(?:/large|/)?$",
+ "https?://pic\\.twitter\\.com/.+"
+ ],
+ "name": "Twitter"
+ }
+] \ No newline at end of file
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 8c6324c72..58cc1cac7 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -61,7 +61,7 @@ export default class RhsComment extends React.Component {
this.parseEmojis();
}
shouldComponentUpdate(nextProps) {
- if (!Utils.areStatesEqual(nextProps.post, this.props.post)) {
+ if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index e3b023841..69de5d523 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -26,7 +26,7 @@ export default class RhsRootPost extends React.Component {
this.parseEmojis();
}
shouldComponentUpdate(nextProps) {
- if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
+ if (!utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index fe57bed28..7c11de7cf 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -82,7 +82,7 @@ export default class RhsThread extends React.Component {
}
onChange() {
var newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
@@ -112,7 +112,7 @@ export default class RhsThread extends React.Component {
}
var newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/search_autocomplete.jsx b/web/react/components/search_autocomplete.jsx
index d245c6bac..04384203f 100644
--- a/web/react/components/search_autocomplete.jsx
+++ b/web/react/components/search_autocomplete.jsx
@@ -46,7 +46,7 @@ export default class SearchAutocomplete extends React.Component {
componentDidUpdate(prevProps, prevState) {
const content = $(ReactDOM.findDOMNode(this.refs.searchPopover)).find('.popover-content');
- if (this.state.show) {
+ if (this.state.show && this.state.suggestions.length > 0) {
if (!prevState.show) {
content.perfectScrollbar();
content.css('max-height', $(window).height() - 200);
@@ -143,10 +143,12 @@ export default class SearchAutocomplete extends React.Component {
}
getSelection() {
- if (this.state.mode === 'channels') {
- return this.state.suggestions[this.state.selection].name;
- } else if (this.state.mode === 'users') {
- return this.state.suggestions[this.state.selection].username;
+ if (this.state.suggestions.length > 0) {
+ if (this.state.mode === 'channels') {
+ return this.state.suggestions[this.state.selection].name;
+ } else if (this.state.mode === 'users') {
+ return this.state.suggestions[this.state.selection].username;
+ }
}
return '';
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index 90865475b..0f749f2cf 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -46,7 +46,7 @@ export default class SearchBar extends React.Component {
onListenerChange(doSearch, isMentionSearch) {
if (this.mounted) {
var newState = this.getSearchTermStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ if (!utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
if (doSearch) {
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index b56a7b006..2f0068908 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -55,7 +55,7 @@ export default class SearchResults extends React.Component {
onChange() {
if (this.mounted) {
var newState = getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/settings_sidebar.jsx b/web/react/components/settings_sidebar.jsx
index 68d9cea48..4af46c35a 100644
--- a/web/react/components/settings_sidebar.jsx
+++ b/web/react/components/settings_sidebar.jsx
@@ -1,14 +1,10 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var utils = require('../utils/utils.jsx');
export default class SettingsSidebar extends React.Component {
componentDidUpdate() {
$('.settings-modal').find('.modal-body').scrollTop(0);
$('.settings-modal').find('.modal-body').perfectScrollbar('update');
- if (utils.isSafari()) {
- $('.settings-modal .settings-links .nav').addClass('absolute');
- }
}
constructor(props) {
super(props);
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 0b1abe4fe..542f433f3 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -106,6 +106,8 @@ export default class Sidebar extends React.Component {
const currentChannelId = ChannelStore.getCurrentId();
const channels = Object.assign([], ChannelStore.getAll());
+ channels.sort((a, b) => a.display_name.localeCompare(b.display_name));
+
const publicChannels = channels.filter((channel) => channel.type === Constants.OPEN_CHANNEL);
const privateChannels = channels.filter((channel) => channel.type === Constants.PRIVATE_CHANNEL);
const directChannels = channels.filter((channel) => channel.type === Constants.DM_CHANNEL);
@@ -173,7 +175,7 @@ export default class Sidebar extends React.Component {
window.addEventListener('resize', this.handleResize);
}
shouldComponentUpdate(nextProps, nextState) {
- if (!Utils.areStatesEqual(nextState, this.state)) {
+ if (!Utils.areObjectsEqual(nextState, this.state)) {
return true;
}
return false;
@@ -205,10 +207,7 @@ export default class Sidebar extends React.Component {
}
}
onChange() {
- var newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
- this.setState(newState);
- }
+ this.setState(this.getStateFromStores());
}
updateTitle() {
const channel = ChannelStore.getCurrent();
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index e2ef60959..ab558ad0f 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -66,13 +66,13 @@ export default class SidebarRight extends React.Component {
onSelectedChange(fromSearch) {
var newState = getStateFromStores(fromSearch);
newState.from_search = fromSearch;
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
onSearchChange() {
var newState = getStateFromStores();
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 587ef5ec2..a50859489 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -393,7 +393,7 @@ export default class GeneralTab extends React.Component {
</div>
</div>
</div>
- <div className='setting-list__hint'>{'When allowing open invites this code is used as part of the signup process. Changing this code will invalidate the previous open signup link.'}</div>
+ <div className='setting-list__hint'>{'Your Invite Code is used in the URL sent to people to join your team. Regenerating your Invite Code will invalidate the URLs in previous invitations, unless "Allow anyone to sign-up from login page" is enabled.'}</div>
</div>
);
@@ -452,6 +452,7 @@ export default class GeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateNameSection}
+ extraInfo='Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
/>
);
} else {
diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx
index ac1ebf52d..afe7f46ec 100644
--- a/web/react/components/team_members.jsx
+++ b/web/react/components/team_members.jsx
@@ -59,7 +59,7 @@ export default class TeamMembers extends React.Component {
onChange() {
var newState = getStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ if (!utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx
index 09674f1ef..862f3c528 100644
--- a/web/react/components/team_settings.jsx
+++ b/web/react/components/team_settings.jsx
@@ -23,7 +23,7 @@ export default class TeamSettings extends React.Component {
}
onChange() {
var team = TeamStore.getCurrent();
- if (!Utils.areStatesEqual(this.state.team, team)) {
+ if (!Utils.areObjectsEqual(this.state.team, team)) {
this.setState({team});
}
}
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index 707033d8f..e6530b941 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -6,6 +6,7 @@ const SearchStore = require('../stores/search_store.jsx');
const CommandList = require('./command_list.jsx');
const ErrorStore = require('../stores/error_store.jsx');
+const TextFormatting = require('../utils/text_formatting.jsx');
const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -30,6 +31,7 @@ export default class Textbox extends React.Component {
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handlePaste = this.handlePaste.bind(this);
+ this.showPreview = this.showPreview.bind(this);
this.state = {
mentionText: '-1',
@@ -118,7 +120,8 @@ export default class Textbox extends React.Component {
}
handleChange() {
- this.props.onUserInput(ReactDOM.findDOMNode(this.refs.message).value);
+ const text = ReactDOM.findDOMNode(this.refs.message).value;
+ this.props.onUserInput(text);
}
handleKeyPress(e) {
@@ -250,10 +253,16 @@ export default class Textbox extends React.Component {
$(e).css({height: 'auto', 'overflow-y': 'hidden'}).height(e.scrollHeight - mod);
$(w).css({height: 'auto'}).height(e.scrollHeight + 2);
$(w).closest('.post-body__cell').removeClass('scroll');
+ if (this.state.preview) {
+ $(ReactDOM.findDOMNode(this.refs.preview)).css({height: 'auto', 'overflow-y': 'auto'}).height(e.scrollHeight - mod);
+ }
} else {
- $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
- $(w).css({height: 'auto'}).height(167);
+ $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167 - mod);
+ $(w).css({height: 'auto'}).height(163);
$(w).closest('.post-body__cell').addClass('scroll');
+ if (this.state.preview) {
+ $(ReactDOM.findDOMNode(this.refs.preview)).css({height: 'auto', 'overflow-y': 'scroll'}).height(163);
+ }
}
if (prevHeight !== $(e).height() && this.props.onHeightChange) {
@@ -279,7 +288,16 @@ export default class Textbox extends React.Component {
this.doProcessMentions = true;
}
+ showPreview(e) {
+ e.preventDefault();
+ e.target.blur();
+ this.setState({preview: !this.state.preview});
+ this.resize();
+ }
+
render() {
+ const previewLinkVisible = this.props.messageText.length > 0;
+
return (
<div
ref='wrapper'
@@ -308,7 +326,22 @@ export default class Textbox extends React.Component {
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onPaste={this.handlePaste}
+ style={{visibility: this.state.preview ? 'hidden' : 'visible'}}
/>
+ <div
+ ref='preview'
+ className='form-control custom-textarea textbox-preview-area'
+ style={{display: this.state.preview ? 'block' : 'none'}}
+ dangerouslySetInnerHTML={{__html: this.state.preview ? TextFormatting.formatText(this.props.messageText) : ''}}
+ >
+ </div>
+ <a
+ style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}}
+ onClick={this.showPreview}
+ className='textbox-preview-link'
+ >
+ {this.state.preview ? 'Edit message' : 'Preview'}
+ </a>
</div>
);
}
diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx
index 66ca556c6..3afc5145d 100644
--- a/web/react/components/tutorial/tutorial_intro_screens.jsx
+++ b/web/react/components/tutorial/tutorial_intro_screens.jsx
@@ -41,6 +41,11 @@ export default class TutorialIntroScreens extends React.Component {
componentDidMount() {
$('.tutorials__scroll').perfectScrollbar();
}
+ skipTutorial(e) {
+ e.preventDefault();
+ const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999');
+ AsyncClient.savePreferences([preference]);
+ }
createScreen() {
switch (this.state.currentScreen) {
case 0:
@@ -176,6 +181,13 @@ export default class TutorialIntroScreens extends React.Component {
>
{'Next'}
</button>
+ <a
+ className='tutorial-skip'
+ href='#'
+ onClick={this.skipTutorial}
+ >
+ {'Skip tutorial'}
+ </a>
</div>
</div>
</div>
diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx
index 75d73e920..dd231b816 100644
--- a/web/react/components/tutorial/tutorial_tip.jsx
+++ b/web/react/components/tutorial/tutorial_tip.jsx
@@ -51,21 +51,22 @@ export default class TutorialTip extends React.Component {
const dots = [];
if (this.props.screens.length > 1) {
for (let i = 0; i < this.props.screens.length; i++) {
+ let className = 'circle';
if (i === this.state.currentScreen) {
- dots.push(
- <div
- className='circle active'
- key={'dotactive' + i}
- />
- );
- } else {
- dots.push(
- <div
- className='circle'
- key={'dotinactive' + i}
- />
- );
+ className += ' active';
}
+
+ dots.push(
+ <a
+ href='#'
+ key={'dotactive' + i}
+ className={className}
+ onClick={(e) => { //eslint-disable-line no-loop-func
+ e.preventDefault();
+ this.setState({currentScreen: i});
+ }}
+ />
+ );
}
}
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index eb0a8f0ca..a2523ef68 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -29,7 +29,7 @@ export default class UserProfile extends React.Component {
return {profile: {id: '0', username: '...'}};
}
- return {profile: profile};
+ return {profile};
}
componentDidMount() {
UserStore.addChangeListener(this.onChange);
@@ -43,7 +43,7 @@ export default class UserProfile extends React.Component {
onChange(userId) {
if (!userId || userId === this.props.userId) {
var newState = this.getStateFromStores(this.props.userId);
- if (!Utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}
diff --git a/web/react/components/user_settings/user_settings.jsx b/web/react/components/user_settings/user_settings.jsx
index e089ce973..40825ba93 100644
--- a/web/react/components/user_settings/user_settings.jsx
+++ b/web/react/components/user_settings/user_settings.jsx
@@ -36,7 +36,7 @@ export default class UserSettings extends React.Component {
onListenerChange() {
var user = UserStore.getCurrentUser();
- if (!utils.areStatesEqual(this.state.user, user)) {
+ if (!utils.areObjectsEqual(this.state.user, user)) {
this.setState({user});
}
}
diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx
index d73b5f476..029a1af5e 100644
--- a/web/react/components/user_settings/user_settings_appearance.jsx
+++ b/web/react/components/user_settings/user_settings_appearance.jsx
@@ -1,13 +1,15 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../../stores/user_store.jsx');
-var Client = require('../../utils/client.jsx');
-var Utils = require('../../utils/utils.jsx');
-
const CustomThemeChooser = require('./custom_theme_chooser.jsx');
const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
+
+const UserStore = require('../../stores/user_store.jsx');
+
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
+const Client = require('../../utils/client.jsx');
+const Utils = require('../../utils/utils.jsx');
+
const Constants = require('../../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
@@ -66,7 +68,7 @@ export default class UserSettingsAppearance extends React.Component {
onChange() {
const newState = this.getStateFromStores();
- if (!Utils.areStatesEqual(this.state, newState)) {
+ if (!Utils.areObjectsEqual(this.state, newState)) {
this.setState(newState);
}
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 1bfae6930..b363f0673 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -1,15 +1,16 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../../stores/user_store.jsx');
-var ErrorStore = require('../../stores/error_store.jsx');
-var SettingItemMin = require('../setting_item_min.jsx');
-var SettingItemMax = require('../setting_item_max.jsx');
-var SettingPicture = require('../setting_picture.jsx');
-var client = require('../../utils/client.jsx');
-var AsyncClient = require('../../utils/async_client.jsx');
-var utils = require('../../utils/utils.jsx');
-var assign = require('object-assign');
+const SettingItemMin = require('../setting_item_min.jsx');
+const SettingItemMax = require('../setting_item_max.jsx');
+const SettingPicture = require('../setting_picture.jsx');
+
+const UserStore = require('../../stores/user_store.jsx');
+const ErrorStore = require('../../stores/error_store.jsx');
+
+const Client = require('../../utils/client.jsx');
+const AsyncClient = require('../../utils/async_client.jsx');
+const Utils = require('../../utils/utils.jsx');
export default class UserSettingsGeneralTab extends React.Component {
constructor(props) {
@@ -32,17 +33,15 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updatePicture = this.updatePicture.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.setupInitialState = this.setupInitialState.bind(this);
-
this.state = this.setupInitialState(props);
}
submitUsername(e) {
e.preventDefault();
- var user = this.props.user;
- var username = this.state.username.trim().toLowerCase();
+ const user = Object.assign({}, this.props.user);
+ const username = this.state.username.trim().toLowerCase();
- var usernameError = utils.isValidUsername(username);
+ const usernameError = Utils.isValidUsername(username);
if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({clientError: 'This username is reserved, please choose a new one.'});
return;
@@ -52,7 +51,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
if (user.username === username) {
- this.setState({clientError: 'You must submit a new username'});
+ this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''});
return;
}
@@ -63,11 +62,11 @@ export default class UserSettingsGeneralTab extends React.Component {
submitNickname(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var nickname = this.state.nickname.trim();
+ const user = Object.assign({}, this.props.user);
+ const nickname = this.state.nickname.trim();
if (user.nickname === nickname) {
- this.setState({clientError: 'You must submit a new nickname'});
+ this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''});
return;
}
@@ -78,12 +77,12 @@ export default class UserSettingsGeneralTab extends React.Component {
submitName(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var firstName = this.state.firstName.trim();
- var lastName = this.state.lastName.trim();
+ const user = Object.assign({}, this.props.user);
+ const firstName = this.state.firstName.trim();
+ const lastName = this.state.lastName.trim();
if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({clientError: 'You must submit a new first or last name'});
+ this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''});
return;
}
@@ -95,21 +94,21 @@ export default class UserSettingsGeneralTab extends React.Component {
submitEmail(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var email = this.state.email.trim().toLowerCase();
- var confirmEmail = this.state.confirmEmail.trim().toLowerCase();
+ const user = Object.assign({}, this.props.user);
+ const email = this.state.email.trim().toLowerCase();
+ const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
if (user.email === email) {
return;
}
- if (email === '' || !utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address'});
+ if (email === '' || !Utils.isEmail(email)) {
+ this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
return;
}
if (email !== confirmEmail) {
- this.setState({emailError: 'The new emails you entered do not match'});
+ this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''});
return;
}
@@ -117,7 +116,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.submitUser(user, true);
}
submitUser(user, emailUpdated) {
- client.updateUser(user,
+ Client.updateUser(user,
() => {
this.updateSection('');
AsyncClient.getMe();
@@ -130,13 +129,13 @@ export default class UserSettingsGeneralTab extends React.Component {
}
},
(err) => {
- var state = this.setupInitialState(this.props);
+ let serverError;
if (err.message) {
- state.serverError = err.message;
+ serverError = err.message;
} else {
- state.serverError = err;
+ serverError = err;
}
- this.setState(state);
+ this.setState({serverError, emailError: '', clientError: ''});
}
);
}
@@ -151,10 +150,10 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
- var picture = this.state.picture;
+ const picture = this.state.picture;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'});
+ this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'});
return;
}
@@ -162,17 +161,17 @@ export default class UserSettingsGeneralTab extends React.Component {
formData.append('image', picture, picture.name);
this.setState({loadingPicture: true});
- client.uploadProfileImage(formData,
- function imageUploadSuccess() {
+ Client.uploadProfileImage(formData,
+ () => {
this.submitActive = false;
AsyncClient.getMe();
window.location.reload();
- }.bind(this),
- function imageUploadFailure(err) {
+ },
+ (err) => {
var state = this.setupInitialState(this.props);
state.serverError = err.message;
this.setState(state);
- }.bind(this)
+ }
);
}
updateUsername(e) {
@@ -205,34 +204,34 @@ export default class UserSettingsGeneralTab extends React.Component {
}
updateSection(section) {
const emailChangeInProgress = this.state.emailChangeInProgress;
- this.setState(assign({}, this.setupInitialState(this.props), {emailChangeInProgress: emailChangeInProgress, clientError: '', serverError: '', emailError: ''}));
+ this.setState(Object.assign({}, this.setupInitialState(this.props), {emailChangeInProgress, clientError: '', serverError: '', emailError: ''}));
this.submitActive = false;
this.props.updateSection(section);
}
setupInitialState(props) {
- var user = props.user;
+ const user = props.user;
return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
email: user.email, confirmEmail: '', picture: null, loadingPicture: false, emailChangeInProgress: false};
}
render() {
- var user = this.props.user;
+ const user = this.props.user;
- var clientError = null;
+ let clientError = null;
if (this.state.clientError) {
clientError = this.state.clientError;
}
- var serverError = null;
+ let serverError = null;
if (this.state.serverError) {
serverError = this.state.serverError;
}
- var emailError = null;
+ let emailError = null;
if (this.state.emailError) {
emailError = this.state.emailError;
}
- var nameSection;
- var inputs = [];
+ let nameSection;
+ const inputs = [];
if (this.props.activeSection === 'name') {
inputs.push(
@@ -298,15 +297,15 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitName}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
} else {
- var fullName = '';
+ let fullName = '';
if (user.first_name && user.last_name) {
fullName = user.first_name + ' ' + user.last_name;
@@ -320,17 +319,17 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Full Name'
describe={fullName}
- updateSection={function updateNameSection() {
+ updateSection={() => {
this.updateSection('name');
- }.bind(this)}
+ }}
/>
);
}
- var nicknameSection;
+ let nicknameSection;
if (this.props.activeSection === 'nickname') {
let nicknameLabel = 'Nickname';
- if (utils.isMobile()) {
+ if (Utils.isMobile()) {
nicknameLabel = '';
}
@@ -364,10 +363,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitNickname}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
@@ -376,17 +375,17 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Nickname'
describe={UserStore.getCurrentUser().nickname}
- updateSection={function updateNicknameSection() {
+ updateSection={() => {
this.updateSection('nickname');
- }.bind(this)}
+ }}
/>
);
}
- var usernameSection;
+ let usernameSection;
if (this.props.activeSection === 'username') {
let usernameLabel = 'Username';
- if (utils.isMobile()) {
+ if (Utils.isMobile()) {
usernameLabel = '';
}
@@ -416,10 +415,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitUsername}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
@@ -428,13 +427,14 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Username'
describe={UserStore.getCurrentUser().username}
- updateSection={function updateUsernameSection() {
+ updateSection={() => {
this.updateSection('username');
- }.bind(this)}
+ }}
/>
);
}
- var emailSection;
+
+ let emailSection;
if (this.props.activeSection === 'email') {
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
@@ -507,10 +507,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={submit}
server_error={serverError}
client_error={emailError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
/>
);
} else {
@@ -534,26 +534,26 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Email'
describe={describe}
- updateSection={function updateEmailSection() {
+ updateSection={() => {
this.updateSection('email');
- }.bind(this)}
+ }}
/>
);
}
- var pictureSection;
+ let pictureSection;
if (this.props.activeSection === 'picture') {
pictureSection = (
<SettingPicture
title='Profile Picture'
submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + utils.getSessionIndex()}
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
@@ -561,17 +561,17 @@ export default class UserSettingsGeneralTab extends React.Component {
/>
);
} else {
- var minMessage = 'Click \'Edit\' to upload an image.';
+ let minMessage = 'Click \'Edit\' to upload an image.';
if (user.last_picture_update) {
- minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update);
+ minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update);
}
pictureSection = (
<SettingItemMin
title='Profile Picture'
describe={minMessage}
- updateSection={function updatePictureSection() {
+ updateSection={() => {
this.updateSection('picture');
- }.bind(this)}
+ }}
/>
);
}
@@ -619,10 +619,10 @@ export default class UserSettingsGeneralTab extends React.Component {
}
UserSettingsGeneralTab.propTypes = {
- user: React.PropTypes.object,
- updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string,
+ user: React.PropTypes.object.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ updateTab: React.PropTypes.func.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index c6f47804f..c958bf5bc 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -1,16 +1,18 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-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 AsyncClient = require('../../utils/async_client.jsx');
-var utils = require('../../utils/utils.jsx');
+const SettingItemMin = require('../setting_item_min.jsx');
+const SettingItemMax = require('../setting_item_max.jsx');
+
+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');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
- var soundNeeded = !utils.isBrowserFirefox();
+ var soundNeeded = !Utils.isBrowserFirefox();
var sound = 'true';
if (user.notify_props && user.notify_props.desktop_sound) {
@@ -116,7 +118,7 @@ export default class NotificationsTab extends React.Component {
data.all = this.state.allKey.toString();
data.channel = this.state.channelKey.toString();
- client.updateUserNotifyProps(data,
+ Client.updateUserNotifyProps(data,
function success() {
this.props.updateSection('');
AsyncClient.getMe();
@@ -138,7 +140,7 @@ export default class NotificationsTab extends React.Component {
}
onListenerChange() {
var newState = getNotificationsStateFromStores();
- if (!utils.areStatesEqual(newState, this.state)) {
+ if (!Utils.areObjectsEqual(newState, this.state)) {
this.setState(newState);
}
}