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.jsx70
-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_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_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/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_notifications.jsx20
39 files changed, 639 insertions, 110 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..617b4b36c 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -9,6 +9,8 @@ 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);
@@ -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,19 +85,54 @@ 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);
+ if (link.substring(link.length - 4) === '.gif') {
+ return this.createGifEmbed(link, this.state.gifLoaded);
+ }
+
+ return null;
+ }
- return embed;
+ 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;
}
loadGif(src) {
@@ -101,18 +145,15 @@ export default class PostBody extends React.Component {
const gif = new Image();
gif.onload = (
() => {
+ this.embed = this.createGifEmbed(src, true);
this.setState({gifLoaded: true});
}
);
gif.src = src;
}
- createGifEmbed(link) {
- if (link.substring(link.length - 4) !== '.gif') {
- return null;
- }
-
- if (!this.state.gifLoaded) {
+ createGifEmbed(link, isLoaded) {
+ if (!isLoaded) {
this.loadGif(link);
return (
<img
@@ -133,7 +174,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 +363,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 +390,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_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_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/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_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);
}
}