summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/activity_log_modal.jsx2
-rw-r--r--web/react/components/admin_console/license_settings.jsx3
-rw-r--r--web/react/components/admin_console/service_settings.jsx4
-rw-r--r--web/react/components/center_panel.jsx13
-rw-r--r--web/react/components/channel_header.jsx42
-rw-r--r--web/react/components/channel_notifications_modal.jsx2
-rw-r--r--web/react/components/claim/email_to_sso.jsx2
-rw-r--r--web/react/components/delete_post_modal.jsx4
-rw-r--r--web/react/components/login.jsx10
-rw-r--r--web/react/components/navbar.jsx40
-rw-r--r--web/react/components/post.jsx54
-rw-r--r--web/react/components/post_body.jsx157
-rw-r--r--web/react/components/post_body_additional_content.jsx103
-rw-r--r--web/react/components/post_deleted_modal.jsx2
-rw-r--r--web/react/components/post_header.jsx10
-rw-r--r--web/react/components/post_image.jsx81
-rw-r--r--web/react/components/posts_view.jsx14
-rw-r--r--web/react/components/posts_view_container.jsx11
-rw-r--r--web/react/components/rename_channel_modal.jsx300
-rw-r--r--web/react/components/rhs_comment.jsx30
-rw-r--r--web/react/components/rhs_header_post.jsx4
-rw-r--r--web/react/components/rhs_root_post.jsx24
-rw-r--r--web/react/components/rhs_thread.jsx150
-rw-r--r--web/react/components/search_bar.jsx2
-rw-r--r--web/react/components/search_results.jsx10
-rw-r--r--web/react/components/search_results_header.jsx2
-rw-r--r--web/react/components/search_results_item.jsx8
-rw-r--r--web/react/components/setting_item_min.jsx6
-rw-r--r--web/react/components/signup_user_complete.jsx10
-rw-r--r--web/react/components/user_profile.jsx45
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx2
-rw-r--r--web/react/pages/channel.jsx2
-rw-r--r--web/react/stores/post_store.jsx128
-rw-r--r--web/react/stores/user_store.jsx52
-rw-r--r--web/react/utils/channel_intro_messages.jsx2
-rw-r--r--web/react/utils/utils.jsx67
36 files changed, 674 insertions, 724 deletions
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index 1a0c9c6d5..95b4caa12 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -254,7 +254,7 @@ export default class ActivityLogModal extends React.Component {
<p className='session-help-text'>
<FormattedMessage
id='activity_log.sessionsDescription'
- defaultMessage="Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session."
+ defaultMessage="Sessions are created when you log in to a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. If you want to log out sooner, use the 'Logout' button below to end a session."
/>
</p>
{content}
diff --git a/web/react/components/admin_console/license_settings.jsx b/web/react/components/admin_console/license_settings.jsx
index 3332f37ef..fdbe912ef 100644
--- a/web/react/components/admin_console/license_settings.jsx
+++ b/web/react/components/admin_console/license_settings.jsx
@@ -203,8 +203,7 @@ class LicenseSettings extends React.Component {
<p className='help-text'>
<FormattedHTMLMessage
id='admin.license.uploadDesc'
- defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a>
- to learn more about the benefits of Enterprise Edition or to purchase a key.'
+ defaultMessage='Upload a license key for Mattermost Enterprise Edition to upgrade this server. <a href="http://mattermost.com" target="_blank">Visit us online</a> to learn more about the benefits of Enterprise Edition or to purchase a key.'
/>
</p>
</div>
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index f232d4633..047c7eb8d 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -287,9 +287,7 @@ class ServiceSettings extends React.Component {
<p className='help-text'>
<FormattedHTMLMessage
id='admin.service.googleDescription'
- defaultMessage='Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at
- <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">https://www.youtube.com/watch?v=Im69kzhpR3I</a>.
- Leaving the field blank disables the automatic generation of YouTube video previews from links.'
+ defaultMessage='Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at <a href="https://www.youtube.com/watch?v=Im69kzhpR3I" target="_blank">https://www.youtube.com/watch?v=Im69kzhpR3I</a>. Leaving the field blank disables the automatic generation of YouTube video previews from links.'
/>
</p>
</div>
diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx
index 6cb749075..97c615768 100644
--- a/web/react/components/center_panel.jsx
+++ b/web/react/components/center_panel.jsx
@@ -27,20 +27,24 @@ export default class CenterPanel extends React.Component {
this.onPreferenceChange = this.onPreferenceChange.bind(this);
this.onChannelChange = this.onChannelChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
this.state = {
showTutorialScreens: tutorialStep === TutorialSteps.INTRO_SCREENS,
- showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS
+ showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS,
+ user: UserStore.getCurrentUser()
};
}
componentDidMount() {
PreferenceStore.addChangeListener(this.onPreferenceChange);
ChannelStore.addChangeListener(this.onChannelChange);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
PreferenceStore.removeChangeListener(this.onPreferenceChange);
ChannelStore.removeChangeListener(this.onChannelChange);
+ UserStore.removeChangeListener(this.onUserChange);
}
onPreferenceChange() {
const tutorialStep = PreferenceStore.getInt(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), 999);
@@ -49,6 +53,9 @@ export default class CenterPanel extends React.Component {
onChannelChange() {
this.setState({showPostFocus: ChannelStore.getPostMode() === ChannelStore.POST_MODE_FOCUS});
}
+ onUserChange() {
+ this.setState({user: UserStore.getCurrentUser()});
+ }
render() {
const channel = ChannelStore.getCurrent();
var handleClick = null;
@@ -108,7 +115,9 @@ export default class CenterPanel extends React.Component {
className='app__content'
>
<div id='channel-header'>
- <ChannelHeader/>
+ <ChannelHeader
+ user={this.state.user}
+ />
</div>
{postsContainer}
{createPost}
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index b5eb546cf..727f84e8e 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -11,6 +11,7 @@ import ChannelInviteModal from './channel_invite_modal.jsx';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';
+import RenameChannelModal from './rename_channel_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import ChannelStore from '../stores/channel_store.jsx';
@@ -39,10 +40,13 @@ export default class ChannelHeader extends React.Component {
this.onListenerChange = this.onListenerChange.bind(this);
this.handleLeave = this.handleLeave.bind(this);
this.searchMentions = this.searchMentions.bind(this);
+ this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
+ this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
const state = this.getStateFromStores();
state.showEditChannelPurposeModal = false;
state.showMembersModal = false;
+ state.showRenameChannelModal = false;
this.state = state;
}
getStateFromStores() {
@@ -51,7 +55,6 @@ export default class ChannelHeader extends React.Component {
return {
channel: ChannelStore.getCurrent(),
memberChannel: ChannelStore.getCurrentMember(),
- memberTeam: UserStore.getCurrentUser(),
users: extraInfo.members,
userCount: extraInfo.member_count,
searchVisible: SearchStore.getSearchResults() !== null
@@ -61,14 +64,12 @@ export default class ChannelHeader extends React.Component {
ChannelStore.addChangeListener(this.onListenerChange);
ChannelStore.addExtraInfoChangeListener(this.onListenerChange);
SearchStore.addSearchChangeListener(this.onListenerChange);
- UserStore.addChangeListener(this.onListenerChange);
PreferenceStore.addChangeListener(this.onListenerChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeExtraInfoChangeListener(this.onListenerChange);
SearchStore.removeSearchChangeListener(this.onListenerChange);
- UserStore.removeChangeListener(this.onListenerChange);
PreferenceStore.removeChangeListener(this.onListenerChange);
}
onListenerChange() {
@@ -97,11 +98,11 @@ export default class ChannelHeader extends React.Component {
searchMentions(e) {
e.preventDefault();
- const user = UserStore.getCurrentUser();
+ const user = this.props.user;
let terms = '';
if (user.notify_props && user.notify_props.mention_keys) {
- const termKeys = UserStore.getCurrentMentionKeys();
+ const termKeys = UserStore.getMentionKeys(user.id);
if (user.notify_props.all === 'true' && termKeys.indexOf('@all') !== -1) {
termKeys.splice(termKeys.indexOf('@all'), 1);
@@ -120,6 +121,18 @@ export default class ChannelHeader extends React.Component {
is_mention_search: true
});
}
+ showRenameChannelModal(e) {
+ e.preventDefault();
+
+ this.setState({
+ showRenameChannelModal: true
+ });
+ }
+ hideRenameChannelModal() {
+ this.setState({
+ showRenameChannelModal: false
+ });
+ }
render() {
if (this.state.channel === null) {
return null;
@@ -150,8 +163,8 @@ export default class ChannelHeader extends React.Component {
</Popover>
);
let channelTitle = channel.display_name;
- const currentId = UserStore.getCurrentId();
- const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.state.memberTeam.roles);
+ const currentId = this.props.user.id;
+ const isAdmin = Utils.isAdmin(this.state.memberChannel.roles) || Utils.isAdmin(this.props.user.roles);
const isDirect = (this.state.channel.type === 'D');
if (isDirect) {
@@ -326,11 +339,7 @@ export default class ChannelHeader extends React.Component {
<a
role='menuitem'
href='#'
- data-toggle='modal'
- data-target='#rename_channel'
- data-display={channel.display_name}
- data-name={channel.name}
- data-channelid={channel.id}
+ onClick={this.showRenameChannelModal}
>
<FormattedMessage
id='channel_header.rename'
@@ -470,7 +479,16 @@ export default class ChannelHeader extends React.Component {
onModalDismissed={() => this.setState({showMembersModal: false})}
channel={channel}
/>
+ <RenameChannelModal
+ show={this.state.showRenameChannelModal}
+ onHide={this.hideRenameChannelModal}
+ channel={channel}
+ />
</div>
);
}
}
+
+ChannelHeader.propTypes = {
+ user: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/channel_notifications_modal.jsx b/web/react/components/channel_notifications_modal.jsx
index 6591de687..7048434f8 100644
--- a/web/react/components/channel_notifications_modal.jsx
+++ b/web/react/components/channel_notifications_modal.jsx
@@ -153,7 +153,7 @@ export default class ChannelNotificationsModal extends React.Component {
/>
<FormattedMessage
id='channel_notifications.globalDefault'
- defaultMessage='Global default ({notifyLevel}'
+ defaultMessage='Global default ({notifyLevel})'
values={{
notifyLevel: (globalNotifyLevelName)
}}
diff --git a/web/react/components/claim/email_to_sso.jsx b/web/react/components/claim/email_to_sso.jsx
index 87e86697c..c3eea9495 100644
--- a/web/react/components/claim/email_to_sso.jsx
+++ b/web/react/components/claim/email_to_sso.jsx
@@ -84,7 +84,7 @@ class EmailToSSO extends React.Component {
<p>
<FormattedMessage
id='claim.email_to_sso.ssoType'
- defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO. You must already have a valid {type} account'
+ defaultMessage='Upon claiming your account, you will only be able to login with {type} SSO'
values={{
type: Utils.toTitleCase(this.props.type)
}}
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 773d0b420..f8e3e406a 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -68,7 +68,7 @@ export default class DeletePostModal extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
} else if (selectedPost.id === this.state.post.id && this.state.root_id) {
if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) {
@@ -77,7 +77,7 @@ export default class DeletePostModal extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- post_list: selectedList
+ postId: selectedPost.root_id
});
AppDispatcher.handleServerAction({
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 380790b8f..581b8e0b5 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -101,12 +101,10 @@ export default class Login extends React.Component {
<div>
{loginMessage}
<div className='or__container'>
- <span>
- <FormattedMessage
- id='login.or'
- defaultMessage='or'
- />
- </span>
+ <FormattedMessage
+ id='login.or'
+ defaultMessage='or'
+ />
</div>
</div>
);
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index a06fb449b..93fe6c05a 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -5,11 +5,11 @@ import EditChannelHeaderModal from './edit_channel_header_modal.jsx';
import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
import MessageWrapper from './message_wrapper.jsx';
import NotifyCounts from './notify_counts.jsx';
-import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';
+import RenameChannelModal from './rename_channel_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserStore from '../stores/user_store.jsx';
@@ -39,6 +39,8 @@ export default class Navbar extends React.Component {
this.showSearch = this.showSearch.bind(this);
this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this);
+ this.showRenameChannelModal = this.showRenameChannelModal.bind(this);
+ this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this);
this.createCollapseButtons = this.createCollapseButtons.bind(this);
this.createDropdown = this.createDropdown.bind(this);
@@ -47,6 +49,7 @@ export default class Navbar extends React.Component {
state.showEditChannelPurposeModal = false;
state.showEditChannelHeaderModal = false;
state.showMembersModal = false;
+ state.showRenameChannelModal = false;
this.state = state;
}
getStateFromStores() {
@@ -90,7 +93,7 @@ export default class Navbar extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
if (e.target.className !== 'navbar-toggle' && e.target.className !== 'icon-bar') {
@@ -128,6 +131,18 @@ export default class Navbar extends React.Component {
showEditChannelHeaderModal: true
});
}
+ showRenameChannelModal(e) {
+ e.preventDefault();
+
+ this.setState({
+ showRenameChannelModal: true
+ });
+ }
+ hideRenameChannelModal() {
+ this.setState({
+ showRenameChannelModal: false
+ });
+ }
createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent) {
if (channel) {
var viewInfoOption = (
@@ -253,11 +268,7 @@ export default class Navbar extends React.Component {
<a
role='menuitem'
href='#'
- data-toggle='modal'
- data-target='#rename_channel'
- data-display={channel.display_name}
- data-name={channel.name}
- data-channelid={channel.id}
+ onClick={this.showRenameChannelModal}
>
<FormattedMessage
id='navbar.rename'
@@ -410,6 +421,7 @@ export default class Navbar extends React.Component {
var editChannelHeaderModal = null;
var editChannelPurposeModal = null;
+ let renameChannelModal = null;
if (channel) {
popoverContent = (
@@ -492,6 +504,14 @@ export default class Navbar extends React.Component {
channel={channel}
/>
);
+
+ renameChannelModal = (
+ <RenameChannelModal
+ show={this.state.showRenameChannelModal}
+ onHide={this.hideRenameChannelModal}
+ channel={channel}
+ />
+ );
}
var collapseButtons = this.createCollapseButtons(currentId);
@@ -524,11 +544,7 @@ export default class Navbar extends React.Component {
</nav>
{editChannelHeaderModal}
{editChannelPurposeModal}
- <ChannelMembersModal
- show={this.state.showMembersModal}
- onModalDismissed={() => this.setState({showMembersModal: false})}
- channel={{channel}}
- />
+ {renameChannelModal}
</div>
);
}
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 3619a9f8f..889d4311e 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -3,15 +3,18 @@
import PostHeader from './post_header.jsx';
import PostBody from './post_body.jsx';
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import Constants from '../utils/constants.jsx';
+
import UserStore from '../stores/user_store.jsx';
import PostStore from '../stores/post_store.jsx';
import ChannelStore from '../stores/channel_store.jsx';
-import * as client from '../utils/client.jsx';
+
+import Constants from '../utils/constants.jsx';
+const ActionTypes = Constants.ActionTypes;
+
+import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-var ActionTypes = Constants.ActionTypes;
-import * as utils from '../utils/utils.jsx';
+import * as Utils from '../utils/utils.jsx';
+import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
export default class Post extends React.Component {
constructor(props) {
@@ -26,13 +29,9 @@ export default class Post extends React.Component {
handleCommentClick(e) {
e.preventDefault();
- var data = {};
- data.order = [this.props.post.id];
- data.posts = this.props.posts;
-
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- post_list: data
+ postId: Utils.getRootId(this.props.post)
});
AppDispatcher.handleServerAction({
@@ -48,14 +47,14 @@ export default class Post extends React.Component {
e.preventDefault();
var post = this.props.post;
- client.createPost(post, post.channel_id,
+ Client.createPost(post, post.channel_id,
(data) => {
AsyncClient.getPosts();
var channel = ChannelStore.get(post.channel_id);
var member = ChannelStore.getMember(post.channel_id);
member.msg_count = channel.total_msg_count;
- member.last_viewed_at = utils.getTimestamp();
+ member.last_viewed_at = Utils.getTimestamp();
ChannelStore.setChannelMember(member);
AppDispatcher.handleServerAction({
@@ -75,7 +74,7 @@ export default class Post extends React.Component {
this.forceUpdate();
}
shouldComponentUpdate(nextProps) {
- if (!utils.areObjectsEqual(nextProps.post, this.props.post)) {
+ if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
return true;
}
@@ -99,6 +98,10 @@ export default class Post extends React.Component {
return true;
}
+ if (nextProps.hasProfiles !== this.props.hasProfiles) {
+ return true;
+ }
+
return false;
}
getCommentCount(props) {
@@ -125,6 +128,7 @@ export default class Post extends React.Component {
const post = this.props.post;
const parentPost = this.props.parentPost;
const posts = this.props.posts;
+ const user = this.props.user || {};
if (!post.props) {
post.props = {};
@@ -152,15 +156,13 @@ export default class Post extends React.Component {
}
let currentUserCss = '';
- if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook && !utils.isSystemMessage(post)) {
+ if (UserStore.getCurrentId() === post.user_id && !post.props.from_webhook && !Utils.isSystemMessage(post)) {
currentUserCss = 'current--user';
}
- const userProfile = UserStore.getProfile(post.user_id);
-
- let timestamp = UserStore.getCurrentUser().update_at;
- if (userProfile) {
- timestamp = userProfile.update_at;
+ let timestamp = user.update_at;
+ if (timestamp == null) {
+ timestamp = UserStore.getCurrentUser().update_at;
}
let sameUserClass = '';
@@ -174,18 +176,18 @@ export default class Post extends React.Component {
}
let systemMessageClass = '';
- if (utils.isSystemMessage(post)) {
+ if (Utils.isSystemMessage(post)) {
systemMessageClass = 'post--system';
}
let profilePic = null;
if (!this.props.hideProfilePic) {
- let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + utils.getSessionIndex();
+ let src = '/api/v1/users/' + post.user_id + '/image?time=' + timestamp + '&' + Utils.getSessionIndex();
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
if (post.props.override_icon_url) {
src = post.props.override_icon_url;
}
- } else if (utils.isSystemMessage(post)) {
+ } else if (Utils.isSystemMessage(post)) {
src = Constants.SYSTEM_MESSAGE_PROFILE_IMAGE;
}
@@ -215,6 +217,7 @@ export default class Post extends React.Component {
handleCommentClick={this.handleCommentClick}
isLastComment={this.props.isLastComment}
sameUser={this.props.sameUser}
+ user={this.props.user}
/>
<PostBody
post={post}
@@ -223,6 +226,7 @@ export default class Post extends React.Component {
posts={posts}
handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost}
+ hasProfiles={this.props.hasProfiles}
/>
</div>
</div>
@@ -233,13 +237,15 @@ export default class Post extends React.Component {
}
Post.propTypes = {
- post: React.PropTypes.object,
+ post: React.PropTypes.object.isRequired,
posts: React.PropTypes.object,
parentPost: React.PropTypes.object,
+ user: React.PropTypes.object,
sameUser: React.PropTypes.bool,
sameRoot: React.PropTypes.bool,
hideProfilePic: React.PropTypes.bool,
isLastComment: React.PropTypes.bool,
shouldHighlight: React.PropTypes.bool,
- displayNameType: React.PropTypes.string
+ displayNameType: React.PropTypes.string,
+ hasProfiles: React.PropTypes.bool
};
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index d71ac6ec7..70cf86748 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -6,13 +6,9 @@ import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import * as Emoji from '../utils/emoticons.jsx';
import Constants from '../utils/constants.jsx';
-const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
-import YoutubeVideo from './youtube_video.jsx';
-
-import providers from './providers.json';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
@@ -31,22 +27,7 @@ class PostBody extends React.Component {
constructor(props) {
super(props);
- this.isImgLoading = false;
-
- this.handleUserChange = this.handleUserChange.bind(this);
this.parseEmojis = this.parseEmojis.bind(this);
- this.createEmbed = this.createEmbed.bind(this);
- this.createImageEmbed = this.createImageEmbed.bind(this);
- this.loadImg = this.loadImg.bind(this);
-
- const linkData = Utils.extractLinks(this.props.post.message);
- const profiles = UserStore.getProfiles();
-
- this.state = {
- links: linkData.links,
- post: this.props.post,
- hasUserProfiles: profiles && Object.keys(profiles).length > 1
- };
}
getAllChildNodes(nodeIn) {
@@ -72,132 +53,8 @@ class PostBody extends React.Component {
});
}
- 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();
-
- UserStore.addChangeListener(this.handleUserChange);
- }
-
- componentDidUpdate() {
- this.parseEmojis();
- }
-
- componentWillUnmount() {
- UserStore.removeChangeListener(this.handleUserChange);
- }
-
- handleUserChange() {
- if (!this.state.hasProfiles) {
- const profiles = UserStore.getProfiles();
-
- this.setState({hasProfiles: profiles && Object.keys(profiles).length > 1});
- }
- }
-
- 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
- });
- }
-
- createEmbed(link) {
- const post = this.state.post;
-
- if (!link) {
- if (post.type === 'oEmbed') {
- post.props.oEmbedLink = '';
- post.type = '';
- }
- return null;
- }
-
- const trimmedLink = link.trim();
-
- if (Utils.isFeatureEnabled(PreReleaseFeatures.EMBED_PREVIEW)) {
- const provider = this.getOembedProvider(trimmedLink);
- if (provider != null) {
- post.props.oEmbedLink = trimmedLink;
- post.type = 'oEmbed';
- this.setState({post, provider});
- return '';
- }
- }
-
- if (YoutubeVideo.isYoutubeLink(link)) {
- return (
- <YoutubeVideo
- channelId={post.channel_id}
- link={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 null;
- }
-
- getOembedProvider(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 providers[i];
- }
- }
- }
- return null;
- }
-
- loadImg(src) {
- if (this.isImgLoading) {
- return;
- }
-
- this.isImgLoading = true;
-
- const img = new Image();
- img.onload = (
- () => {
- this.embed = this.createImageEmbed(src, true);
- this.setState({imgLoaded: true});
- }
- );
- img.src = src;
- }
-
- createImageEmbed(link, isLoaded) {
- if (!isLoaded) {
- this.loadImg(link);
- return (
- <img
- className='img-div placeholder'
- height='500px'
- />
- );
- }
-
- return (
- <img
- className='img-div'
- src={link}
- />
- );
}
render() {
@@ -312,6 +169,7 @@ class PostBody extends React.Component {
}
let message;
+ let additionalContent = null;
if (this.props.post.state === Constants.POST_DELETED) {
message = (
<FormattedMessage
@@ -326,6 +184,10 @@ class PostBody extends React.Component {
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message)}}
/>
);
+
+ additionalContent = (
+ <PostBodyAdditionalContent post={this.props.post}/>
+ );
}
return (
@@ -340,12 +202,8 @@ class PostBody extends React.Component {
{loading}
{message}
</div>
- <PostBodyAdditionalContent
- post={this.state.post}
- provider={this.state.provider}
- />
{fileAttachmentHolder}
- {this.embed}
+ {additionalContent}
</div>
</div>
);
@@ -357,7 +215,8 @@ PostBody.propTypes = {
post: React.PropTypes.object.isRequired,
parentPost: React.PropTypes.object,
retryPost: React.PropTypes.func.isRequired,
- handleCommentClick: React.PropTypes.func.isRequired
+ handleCommentClick: React.PropTypes.func.isRequired,
+ hasProfiles: React.PropTypes.bool
};
export default injectIntl(PostBody);
diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx
index 4871eea4f..a76c59fb3 100644
--- a/web/react/components/post_body_additional_content.jsx
+++ b/web/react/components/post_body_additional_content.jsx
@@ -3,72 +3,103 @@
import PostAttachmentList from './post_attachment_list.jsx';
import PostAttachmentOEmbed from './post_attachment_oembed.jsx';
+import PostImage from './post_image.jsx';
+import YoutubeVideo from './youtube_video.jsx';
+
+import Constants from '../utils/constants.jsx';
+import OEmbedProviders from './providers.json';
+import * as Utils from '../utils/utils.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);
+ this.getOEmbedProvider = this.getOEmbedProvider.bind(this);
}
- componentWillMount() {
- this.setState({type: this.props.post.type, shouldRender: Boolean(this.props.post.type)});
+ shouldComponentUpdate(nextProps) {
+ if (!Utils.areObjectsEqual(nextProps.post, this.props.post)) {
+ return true;
+ }
+
+ return false;
}
getSlackAttachment() {
- const attachments = this.props.post.props && this.props.post.props.attachments || [];
+ let attachments = [];
+ if (this.props.post.props && this.props.post.props.attachments) {
+ attachments = this.props.post.props.attachments;
+ }
+
return (
<PostAttachmentList
- key={'post_body_additional_content' + this.props.post.id}
attachments={attachments}
/>
);
}
- getOembedAttachment() {
- const link = this.props.post.props && this.props.post.props.oEmbedLink || '';
- return (
- <PostAttachmentOEmbed
- key={'post_body_additional_content' + this.props.post.id}
- provider={this.props.provider}
- link={link}
- />
- );
+ getOEmbedProvider(link) {
+ for (let i = 0; i < OEmbedProviders.length; i++) {
+ for (let j = 0; j < OEmbedProviders[i].patterns.length; j++) {
+ if (link.match(OEmbedProviders[i].patterns[j])) {
+ return OEmbedProviders[i];
+ }
+ }
+ }
+
+ return null;
}
- getComponent() {
- switch (this.props.post.type) {
- case 'slack_attachment':
+ render() {
+ if (this.props.post.type === 'slack_attachment') {
return this.getSlackAttachment();
- case 'oEmbed':
- return this.getOembedAttachment();
- default:
- return '';
}
- }
- render() {
- let content = [];
+ const link = Utils.extractLinks(this.props.post.message)[0];
+ if (!link) {
+ return null;
+ }
- if (this.props.post.type) {
- const component = this.getComponent();
+ if (Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW)) {
+ const provider = this.getOEmbedProvider(link);
- if (component) {
- content = component;
+ if (provider) {
+ return (
+ <PostAttachmentOEmbed
+ provider={provider}
+ link={link}
+ />
+ );
}
}
- return (
- <div>
- {content}
- </div>
- );
+ if (YoutubeVideo.isYoutubeLink(link)) {
+ return (
+ <YoutubeVideo
+ channelId={this.props.post.channel_id}
+ link={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 (
+ <PostImage
+ channelId={this.props.post.channel_id}
+ link={link}
+ />
+ );
+ }
+ }
+
+ return null;
}
}
PostBodyAdditionalContent.propTypes = {
- post: React.PropTypes.object.isRequired,
- provider: React.PropTypes.object
+ post: React.PropTypes.object.isRequired
};
diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx
index be22989a6..1f5c15aa9 100644
--- a/web/react/components/post_deleted_modal.jsx
+++ b/web/react/components/post_deleted_modal.jsx
@@ -38,7 +38,7 @@ export default class PostDeletedModal extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
this.props.onHide();
diff --git a/web/react/components/post_header.jsx b/web/react/components/post_header.jsx
index c52391daa..2803fe387 100644
--- a/web/react/components/post_header.jsx
+++ b/web/react/components/post_header.jsx
@@ -13,16 +13,17 @@ export default class PostHeader extends React.Component {
this.state = {};
}
render() {
- var post = this.props.post;
+ const post = this.props.post;
+ const user = this.props.user;
- let userProfile = <UserProfile userId={post.user_id}/>;
+ let userProfile = <UserProfile user={user}/>;
let botIndicator;
if (post.props && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = (
<UserProfile
- userId={post.user_id}
+ user={user}
overwriteName={post.props.override_username}
disablePopover={true}
/>
@@ -33,7 +34,7 @@ export default class PostHeader extends React.Component {
} else if (Utils.isSystemMessage(post)) {
userProfile = (
<UserProfile
- userId={''}
+ user={{}}
overwriteName={Constants.SYSTEM_MESSAGE_PROFILE_NAME}
overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
disablePopover={true}
@@ -68,6 +69,7 @@ PostHeader.defaultProps = {
};
PostHeader.propTypes = {
post: React.PropTypes.object,
+ user: React.PropTypes.object,
commentCount: React.PropTypes.number,
isLastComment: React.PropTypes.bool,
handleCommentClick: React.PropTypes.func,
diff --git a/web/react/components/post_image.jsx b/web/react/components/post_image.jsx
new file mode 100644
index 000000000..da4a25794
--- /dev/null
+++ b/web/react/components/post_image.jsx
@@ -0,0 +1,81 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class PostImageEmbed extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleLoadComplete = this.handleLoadComplete.bind(this);
+ this.handleLoadError = this.handleLoadError.bind(this);
+
+ this.state = {
+ loaded: false,
+ errored: false
+ };
+ }
+
+ componentWillMount() {
+ this.loadImg(this.props.link);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.link !== this.props.link) {
+ this.setState({
+ loaded: false,
+ errored: false
+ });
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!this.state.loaded && prevProps.link !== this.props.link) {
+ this.loadImg(this.props.link);
+ }
+ }
+
+ loadImg(src) {
+ const img = new Image();
+ img.onload = this.handleLoadComplete;
+ img.onerror = this.handleLoadError;
+ img.src = src;
+ }
+
+ handleLoadComplete() {
+ this.setState({
+ loaded: true
+ });
+ }
+
+ handleLoadError() {
+ this.setState({
+ errored: true,
+ loaded: true
+ });
+ }
+
+ render() {
+ if (this.state.errored) {
+ return null;
+ }
+
+ if (!this.state.loaded) {
+ return (
+ <img
+ className='img-div placeholder'
+ height='500px'
+ />
+ );
+ }
+
+ return (
+ <img
+ className='img-div'
+ src={this.props.link}
+ />
+ );
+ }
+}
+
+PostImageEmbed.propTypes = {
+ link: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 15810d9cf..1ea7711ea 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -145,6 +145,7 @@ export default class PostsView extends React.Component {
const postCtls = [];
let previousPostDay = new Date(0);
const userId = UserStore.getCurrentId();
+ const profiles = this.props.profiles || {};
let renderedLastViewed = false;
@@ -228,6 +229,13 @@ export default class PostsView extends React.Component {
const shouldHighlight = this.props.postsToHighlight && this.props.postsToHighlight.hasOwnProperty(post.id);
+ let profile;
+ if (UserStore.getCurrentId() === post.user_id) {
+ profile = UserStore.getCurrentUser();
+ } else {
+ profile = profiles[post.user_id];
+ }
+
const postCtl = (
<Post
key={keyPrefix + 'postKey'}
@@ -242,6 +250,8 @@ export default class PostsView extends React.Component {
shouldHighlight={shouldHighlight}
onClick={() => EventHelpers.emitPostFocusEvent(post.id)} //eslint-disable-line no-loop-func
displayNameType={this.state.displayNameType}
+ hasProfiles={profiles && Object.keys(profiles).length > 1}
+ user={profile}
/>
);
@@ -413,6 +423,9 @@ export default class PostsView extends React.Component {
if (this.state.isScrolling !== nextState.isScrolling) {
return true;
}
+ if (!Utils.areObjectsEqual(this.props.profiles, nextProps.profiles)) {
+ return true;
+ }
return false;
}
@@ -513,6 +526,7 @@ PostsView.defaultProps = {
PostsView.propTypes = {
isActive: React.PropTypes.bool,
postList: React.PropTypes.object,
+ profiles: React.PropTypes.object,
scrollPostId: React.PropTypes.string,
scrollType: React.PropTypes.number,
postViewScrolled: React.PropTypes.func.isRequired,
diff --git a/web/react/components/posts_view_container.jsx b/web/react/components/posts_view_container.jsx
index 972342021..1b14e8681 100644
--- a/web/react/components/posts_view_container.jsx
+++ b/web/react/components/posts_view_container.jsx
@@ -6,6 +6,7 @@ import LoadingScreen from './loading_screen.jsx';
import ChannelStore from '../stores/channel_store.jsx';
import PostStore from '../stores/post_store.jsx';
+import UserStore from '../stores/user_store.jsx';
import * as Utils from '../utils/utils.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
@@ -24,11 +25,13 @@ export default class PostsViewContainer extends React.Component {
this.handlePostsViewScroll = this.handlePostsViewScroll.bind(this);
this.loadMorePostsTop = this.loadMorePostsTop.bind(this);
this.handlePostsViewJumpRequest = this.handlePostsViewJumpRequest.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
const currentChannelId = ChannelStore.getCurrentId();
const state = {
scrollType: PostsView.SCROLL_TYPE_BOTTOM,
- scrollPost: null
+ scrollPost: null,
+ profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))
};
if (currentChannelId) {
Object.assign(state, {
@@ -54,12 +57,14 @@ export default class PostsViewContainer extends React.Component {
ChannelStore.addLeaveListener(this.onChannelLeave);
PostStore.addChangeListener(this.onPostsChange);
PostStore.addPostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.addChangeListener(this.onUserChange);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onChannelChange);
ChannelStore.removeLeaveListener(this.onChannelLeave);
PostStore.removeChangeListener(this.onPostsChange);
PostStore.removePostsViewJumpListener(this.handlePostsViewJumpRequest);
+ UserStore.removeChangeListener(this.onUserChange);
}
handlePostsViewJumpRequest(type, post) {
switch (type) {
@@ -135,6 +140,9 @@ export default class PostsViewContainer extends React.Component {
atTop[this.state.currentChannelIndex] = PostStore.getVisibilityAtTop(currentChannelId);
this.setState({postLists, atTop});
}
+ onUserChange() {
+ this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
+ }
getChannelPosts(id) {
return PostStore.getVisiblePosts(id);
}
@@ -180,6 +188,7 @@ export default class PostsViewContainer extends React.Component {
showMoreMessagesBottom={false}
introText={channel ? createChannelIntroMessage(channel) : null}
messageSeparatorTime={this.state.currentLastViewed}
+ profiles={this.state.profiles}
/>
);
if (!postLists[i] && isActive) {
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index c467c0d87..e96ff0db2 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -4,11 +4,12 @@
import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import * as AsyncClient from '../utils/async_client.jsx';
-import ChannelStore from '../stores/channel_store.jsx';
import Constants from '../utils/constants.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+const Modal = ReactBootstrap.Modal;
+
const holders = defineMessages({
required: {
id: 'rename_channel.required',
@@ -44,33 +45,81 @@ export default class RenameChannelModal extends React.Component {
constructor(props) {
super(props);
+ this.handleShow = this.handleShow.bind(this);
+ this.handleHide = this.handleHide.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleCancel = this.handleCancel.bind(this);
+
this.onNameChange = this.onNameChange.bind(this);
this.onDisplayNameChange = this.onDisplayNameChange.bind(this);
this.displayNameKeyUp = this.displayNameKeyUp.bind(this);
- this.handleClose = this.handleClose.bind(this);
- this.handleShow = this.handleShow.bind(this);
- this.handleShown = this.handleShown.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
- displayName: '',
- channelName: '',
- channelId: '',
+ displayName: props.channel.display_name,
+ channelName: props.channel.name,
serverError: '',
nameError: '',
displayNameError: '',
invalid: false
};
}
- handleSubmit(e) {
- e.preventDefault();
- if (this.state.channelId.length !== 26) {
- return;
+ componentWillReceiveProps(nextProps) {
+ if (!Utils.areObjectsEqual(nextProps.channel, this.props.channel)) {
+ this.setState({
+ displayName: nextProps.channel.display_name,
+ channelName: nextProps.channel.name
+ });
}
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (!nextProps.show && !this.props.show) {
+ return false;
+ }
+
+ if (!Utils.areObjectsEqual(nextState, this.state)) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextProps, this.props)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.show && this.props.show) {
+ this.handleShow();
+ }
+ }
+
+ handleShow() {
+ const textbox = ReactDOM.findDOMNode(this.refs.displayName);
+ textbox.focus();
+ Utils.placeCaretAtEnd(textbox);
+ }
+
+ handleHide(e) {
+ if (e) {
+ e.preventDefault();
+ }
+
+ this.props.onHide();
+
+ this.setState({
+ serverError: '',
+ nameError: '',
+ displayNameError: '',
+ invalid: false
+ });
+ }
- const channel = ChannelStore.get(this.state.channelId);
+ handleSubmit(e) {
+ e.preventDefault();
+
+ const channel = Object.assign({}, this.props.channel);
const oldName = channel.name;
const oldDisplayName = channel.displayName;
const state = {serverError: ''};
@@ -110,29 +159,40 @@ export default class RenameChannelModal extends React.Component {
return;
}
- Client.updateChannel(channel,
+ Client.updateChannel(
+ channel,
() => {
- $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
-
AsyncClient.getChannel(channel.id);
Utils.updateAddressBar(channel.name);
- ReactDOM.findDOMNode(this.refs.displayName).value = '';
- ReactDOM.findDOMNode(this.refs.channelName).value = '';
+ this.handleHide();
},
(err) => {
- state.serverError = err.message;
- state.invalid = true;
- this.setState(state);
+ this.setState({
+ serverError: err.message,
+ invalid: true
+ });
}
);
}
+
+ handleCancel(e) {
+ this.setState({
+ displayName: this.props.channel.display_name,
+ channelName: this.props.channel.name
+ });
+
+ this.handleHide(e);
+ }
+
onNameChange() {
this.setState({channelName: ReactDOM.findDOMNode(this.refs.channelName).value});
}
+
onDisplayNameChange() {
this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value});
}
+
displayNameKeyUp() {
if (this.state.channelName !== Constants.DEFAULT_CHANNEL) {
const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
@@ -141,32 +201,7 @@ export default class RenameChannelModal extends React.Component {
this.setState({channelName: channelName});
}
}
- handleClose() {
- this.setState({
- displayName: '',
- channelName: '',
- channelId: '',
- serverError: '',
- nameError: '',
- displayNameError: '',
- invalid: false
- });
- }
- handleShow(e) {
- const button = $(e.relatedTarget);
- this.setState({displayName: button.attr('data-display'), channelName: button.attr('data-name'), channelId: button.attr('data-channelid')});
- }
- handleShown() {
- $('#rename_channel #display_name').focus();
- }
- componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.handleShow);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose);
- $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.handleShown);
- }
- componentWillUnmount() {
- $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose);
- }
+
render() {
let displayNameError = null;
let displayNameClass = 'form-group';
@@ -194,112 +229,93 @@ export default class RenameChannelModal extends React.Component {
let readOnlyHandleInput = false;
if (this.state.channelName === Constants.DEFAULT_CHANNEL) {
handleInputLabel += formatMessage(holders.defaultError);
- handleInputClass += ' disabled-input';
readOnlyHandleInput = true;
}
return (
- <div
- className='modal fade'
- ref='modal'
- id='rename_channel'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ onHide={this.handleCancel}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- >
- <span aria-hidden='true'>{'×'}</span>
- <span className='sr-only'>
- <FormattedMessage
- id='rename_channel.close'
- defaultMessage='Close'
- />
- </span>
- </button>
- <h4 className='modal-title'>
- <FormattedMessage
- id='rename_channel.title'
- defaultMessage='Rename Channel'
+ <Modal.Header closeButton={true}>
+ <Modal.Title>
+ <FormattedMessage
+ id='rename_channel.title'
+ defaultMessage='Rename Channel'
+ />
+ </Modal.Title>
+ </Modal.Header>
+ <form role='form'>
+ <Modal.Body>
+ <div className={displayNameClass}>
+ <label className='control-label'>
+ <FormattedMessage
+ id='rename_channel.displayName'
+ defaultMessage='Display Name'
+ />
+ </label>
+ <input
+ onKeyUp={this.displayNameKeyUp}
+ onChange={this.onDisplayNameChange}
+ type='text'
+ ref='displayName'
+ id='display_name'
+ className='form-control'
+ placeholder={formatMessage(holders.displayNameHolder)}
+ value={this.state.displayName}
+ maxLength='64'
+ />
+ {displayNameError}
+ </div>
+ <div className={nameClass}>
+ <label className='control-label'>{handleInputLabel}</label>
+ <input
+ onChange={this.onNameChange}
+ type='text'
+ className={handleInputClass}
+ ref='channelName'
+ placeholder={formatMessage(holders.handleHolder)}
+ value={this.state.channelName}
+ maxLength='64'
+ readOnly={readOnlyHandleInput}
/>
- </h4>
+ {nameError}
</div>
- <form role='form'>
- <div className='modal-body'>
- <div className={displayNameClass}>
- <label className='control-label'>
- <FormattedMessage
- id='rename_channel.displayName'
- defaultMessage='Display Name'
- />
- </label>
- <input
- onKeyUp={this.displayNameKeyUp}
- onChange={this.onDisplayNameChange}
- type='text'
- ref='displayName'
- id='display_name'
- className='form-control'
- placeholder={formatMessage(holders.displayNameHolder)}
- value={this.state.displayName}
- maxLength='64'
- />
- {displayNameError}
- </div>
- <div className={nameClass}>
- <label className='control-label'>{handleInputLabel}</label>
- <input
- onChange={this.onNameChange}
- type='text'
- className={handleInputClass}
- ref='channelName'
- placeholder={formatMessage(holders.handleHolder)}
- value={this.state.channelName}
- maxLength='64'
- readOnly={readOnlyHandleInput}
- />
- {nameError}
- </div>
- {serverError}
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- <FormattedMessage
- id='rename_channel.cancel'
- defaultMessage='Cancel'
- />
- </button>
- <button
- onClick={this.handleSubmit}
- type='submit'
- className='btn btn-primary'
- >
- <FormattedMessage
- id='rename_channel.save'
- defaultMessage='Save'
- />
- </button>
- </div>
- </form>
- </div>
- </div>
- </div>
+ {serverError}
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.handleCancel}
+ >
+ <FormattedMessage
+ id='rename_channel.cancel'
+ defaultMessage='Cancel'
+ />
+ </button>
+ <button
+ onClick={this.handleSubmit}
+ type='submit'
+ className='btn btn-primary'
+ >
+ <FormattedMessage
+ id='rename_channel.save'
+ defaultMessage='Save'
+ />
+ </button>
+ </Modal.Footer>
+ </form>
+ </Modal>
);
}
}
RenameChannelModal.propTypes = {
- intl: intlShape.isRequired
+ intl: intlShape.isRequired,
+ show: React.PropTypes.bool.isRequired,
+ onHide: React.PropTypes.func.isRequired,
+ channel: React.PropTypes.object.isRequired
};
-export default injectIntl(RenameChannelModal); \ No newline at end of file
+export default injectIntl(RenameChannelModal);
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 2ebca9b8d..9588809eb 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -194,8 +194,16 @@ class RhsComment extends React.Component {
var timestamp = UserStore.getCurrentUser().update_at;
- var loading;
- var postClass = '';
+ let loading;
+ let postClass = '';
+ let message = (
+ <div
+ ref='message_holder'
+ onClick={TextFormatting.handleClick}
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
+ />
+ );
+
if (post.state === Constants.POST_FAILED) {
postClass += ' post-fail';
loading = (
@@ -218,6 +226,13 @@ class RhsComment extends React.Component {
src='/static/images/load.gif'
/>
);
+ } else if (this.props.post.state === Constants.POST_DELETED) {
+ message = (
+ <FormattedMessage
+ id='post_body.deleted'
+ defaultMessage='(message deleted)'
+ />
+ );
}
var dropdown = this.createDropdown();
@@ -246,7 +261,7 @@ class RhsComment extends React.Component {
<div>
<ul className='post__header'>
<li className='col__name'>
- <strong><UserProfile userId={post.user_id}/></strong>
+ <strong><UserProfile user={this.props.user}/></strong>
</li>
<li className='col'>
<time className='post__time'>
@@ -268,11 +283,7 @@ class RhsComment extends React.Component {
<div className='post__body'>
<div className={postClass}>
{loading}
- <div
- ref='message_holder'
- onClick={TextFormatting.handleClick}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(post.message)}}
- />
+ {message}
</div>
{fileAttachment}
</div>
@@ -288,7 +299,8 @@ RhsComment.defaultProps = {
};
RhsComment.propTypes = {
intl: intlShape.isRequired,
- post: React.PropTypes.object
+ post: React.PropTypes.object,
+ user: React.PropTypes.object
};
export default injectIntl(RhsComment);
diff --git a/web/react/components/rhs_header_post.jsx b/web/react/components/rhs_header_post.jsx
index cd310df56..4c9f6f3f6 100644
--- a/web/react/components/rhs_header_post.jsx
+++ b/web/react/components/rhs_header_post.jsx
@@ -27,7 +27,7 @@ export default class RhsHeaderPost extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
}
handleBack(e) {
@@ -42,7 +42,7 @@ export default class RhsHeaderPost extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
}
render() {
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index 32946ef23..023f3dd2d 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -50,10 +50,10 @@ export default class RhsRootPost extends React.Component {
this.parseEmojis();
}
render() {
- var post = this.props.post;
- var currentUser = UserStore.getCurrentUser();
- var isOwner = currentUser.id === post.user_id;
- var isAdmin = Utils.isAdmin(currentUser.roles);
+ const post = this.props.post;
+ const user = this.props.user;
+ var isOwner = user.id === post.user_id;
+ var isAdmin = Utils.isAdmin(user.roles);
var timestamp = UserStore.getProfile(post.user_id).update_at;
var channel = ChannelStore.get(post.channel_id);
@@ -62,9 +62,9 @@ export default class RhsRootPost extends React.Component {
type = 'Comment';
}
- var currentUserCss = '';
+ var userCss = '';
if (UserStore.getCurrentId() === post.user_id) {
- currentUserCss = 'current--user';
+ userCss = 'current--user';
}
var systemMessageClass = '';
@@ -185,14 +185,14 @@ export default class RhsRootPost extends React.Component {
);
}
- let userProfile = <UserProfile userId={post.user_id}/>;
+ let userProfile = <UserProfile user={user}/>;
let botIndicator;
if (post.props && post.props.from_webhook) {
if (post.props.override_username && global.window.mm_config.EnablePostUsernameOverride === 'true') {
userProfile = (
<UserProfile
- userId={post.user_id}
+ user={user}
overwriteName={post.props.override_username}
disablePopover={true}
/>
@@ -203,7 +203,7 @@ export default class RhsRootPost extends React.Component {
} else if (Utils.isSystemMessage(post)) {
userProfile = (
<UserProfile
- userId={''}
+ user={{}}
overwriteName={Constants.SYSTEM_MESSAGE_PROFILE_NAME}
overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
disablePopover={true}
@@ -230,7 +230,7 @@ export default class RhsRootPost extends React.Component {
);
return (
- <div className={'post post--root ' + currentUserCss + ' ' + systemMessageClass}>
+ <div className={'post post--root ' + userCss + ' ' + systemMessageClass}>
<div className='post-right-channel__name'>{channelName}</div>
<div className='post__content'>
<div className='post__img'>
@@ -278,10 +278,10 @@ export default class RhsRootPost extends React.Component {
}
RhsRootPost.defaultProps = {
- post: null,
commentCount: 0
};
RhsRootPost.propTypes = {
- post: React.PropTypes.object,
+ post: React.PropTypes.object.isRequired,
+ user: React.PropTypes.object.isRequired,
commentCount: React.PropTypes.number
};
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index 667030b3a..4d770287c 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -19,39 +19,25 @@ export default class RhsThread extends React.Component {
this.mounted = false;
- this.onChange = this.onChange.bind(this);
- this.onChangeAll = this.onChangeAll.bind(this);
+ this.onPostChange = this.onPostChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.handleResize = this.handleResize.bind(this);
- const state = this.getStateFromStores();
+ const state = {};
state.windowWidth = Utils.windowWidth();
state.windowHeight = Utils.windowHeight();
- this.state = state;
- }
- getStateFromStores() {
- var postList = PostStore.getSelectedPost();
- if (!postList || postList.order.length < 1 || !postList.posts[postList.order[0]]) {
- return {postList: {}};
- }
-
- var channelId = postList.posts[postList.order[0]].channel_id;
- var pendingPostsList = PostStore.getPendingPosts(channelId);
-
- if (pendingPostsList) {
- for (var pid in pendingPostsList.posts) {
- if (pendingPostsList.posts.hasOwnProperty(pid)) {
- postList.posts[pid] = pendingPostsList.posts[pid];
- }
- }
- }
+ state.selected = PostStore.getSelectedPost();
+ state.posts = PostStore.getSelectedPostThread();
+ state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
- return {postList: postList};
+ this.state = state;
}
componentDidMount() {
- PostStore.addSelectedPostChangeListener(this.onChange);
- PostStore.addChangeListener(this.onChangeAll);
+ PostStore.addSelectedPostChangeListener(this.onPostChange);
+ PostStore.addChangeListener(this.onPostChange);
PreferenceStore.addChangeListener(this.forceUpdateInfo);
+ UserStore.addChangeListener(this.onUserChange);
this.resize();
window.addEventListener('resize', this.handleResize);
@@ -65,14 +51,30 @@ export default class RhsThread extends React.Component {
this.resize();
}
componentWillUnmount() {
- PostStore.removeSelectedPostChangeListener(this.onChange);
- PostStore.removeChangeListener(this.onChangeAll);
+ PostStore.removeSelectedPostChangeListener(this.onPostChange);
+ PostStore.removeChangeListener(this.onPostChange);
PreferenceStore.removeChangeListener(this.forceUpdateInfo);
+ UserStore.removeChangeListener(this.onUserChange);
window.removeEventListener('resize', this.handleResize);
this.mounted = false;
}
+ shouldComponentUpdate(nextProps, nextState) {
+ if (!Utils.areObjectsEqual(nextState.posts, this.state.posts)) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextState.selected, this.state.selected)) {
+ return true;
+ }
+
+ if (!Utils.areObjectsEqual(nextState.profiles, this.state.profiles)) {
+ return true;
+ }
+
+ return false;
+ }
forceUpdateInfo() {
if (this.state.postList) {
for (var postId in this.state.postList.posts) {
@@ -88,49 +90,14 @@ export default class RhsThread extends React.Component {
windowHeight: Utils.windowHeight()
});
}
- onChange() {
- var newState = this.getStateFromStores();
- if (this.mounted && !Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ onPostChange() {
+ const selected = PostStore.getSelectedPost();
+ const posts = PostStore.getSelectedPostThread();
+ this.setState({posts, selected});
}
- onChangeAll() {
- // if something was changed in the channel like adding a
- // comment or post then lets refresh the sidebar list
- var currentSelected = PostStore.getSelectedPost();
- if (!currentSelected || currentSelected.order.length === 0 || !currentSelected.posts[currentSelected.order[0]]) {
- return;
- }
-
- var currentPosts = PostStore.getVisiblePosts(currentSelected.posts[currentSelected.order[0]].channel_id);
-
- if (!currentPosts || currentPosts.order.length === 0) {
- return;
- }
-
- if (currentPosts.posts[currentPosts.order[0]].channel_id === currentSelected.posts[currentSelected.order[0]].channel_id) {
- for (var key in currentSelected.posts) {
- if (currentSelected.posts.hasOwnProperty(key)) {
- var post = currentSelected.posts[key];
- if (post.pending_post_id) {
- Reflect.deleteProperty(currentSelected.posts, key);
- }
- }
- }
-
- for (var postId in currentPosts.posts) {
- if (currentPosts.posts.hasOwnProperty(postId)) {
- currentSelected.posts[postId] = currentPosts.posts[postId];
- }
- }
-
- PostStore.storeSelectedPost(currentSelected);
- }
-
- var newState = this.getStateFromStores();
- if (this.mounted && !Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ onUserChange() {
+ const profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
+ this.setState({profiles});
}
resize() {
$('.post-right__scroll').scrollTop(100000);
@@ -140,29 +107,22 @@ export default class RhsThread extends React.Component {
}
}
render() {
- var postList = this.state.postList;
+ const posts = this.state.posts;
+ const selected = this.state.selected;
+ const profiles = this.state.profiles || {};
- if (postList == null || !postList.order) {
+ if (posts == null || selected == null) {
return (
<div></div>
);
}
- var selectedPost = postList.posts[postList.order[0]];
- var rootPost = null;
-
- if (selectedPost.root_id === '') {
- rootPost = selectedPost;
- } else {
- rootPost = postList.posts[selectedPost.root_id];
- }
-
var postsArray = [];
- for (var postId in postList.posts) {
- if (postList.posts.hasOwnProperty(postId)) {
- var cpost = postList.posts[postId];
- if (cpost.root_id === rootPost.id) {
+ for (const id in posts) {
+ if (posts.hasOwnProperty(id)) {
+ const cpost = posts[id];
+ if (cpost.root_id === selected.id) {
postsArray.push(cpost);
}
}
@@ -199,6 +159,13 @@ export default class RhsThread extends React.Component {
searchForm = <SearchBox/>;
}
+ let profile;
+ if (UserStore.getCurrentId() === selected.user_id) {
+ profile = UserStore.getCurrentUser();
+ } else {
+ profile = profiles[selected.user_id];
+ }
+
return (
<div className='post-right__container'>
<FileUploadOverlay overlayType='right'/>
@@ -210,26 +177,33 @@ export default class RhsThread extends React.Component {
/>
<div className='post-right__scroll'>
<RootPost
- ref={rootPost.id}
- post={rootPost}
+ ref={selected.id}
+ post={selected}
commentCount={postsArray.length}
+ user={profile}
/>
<div className='post-right-comments-container'>
{postsArray.map(function mapPosts(comPost) {
+ let p;
+ if (UserStore.getCurrentId() === selected.user_id) {
+ p = UserStore.getCurrentUser();
+ } else {
+ p = profiles[comPost.user_id];
+ }
return (
<Comment
ref={comPost.id}
key={comPost.id + 'commentKey'}
post={comPost}
- selected={(comPost.id === selectedPost.id)}
+ user={p}
/>
);
})}
</div>
<div className='post-create__container'>
<CreateComment
- channelId={rootPost.channel_id}
- rootId={rootPost.id}
+ channelId={selected.channel_id}
+ rootId={selected.id}
/>
</div>
</div>
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index 1cdd09cc8..eaf8b5069 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -87,7 +87,7 @@ class SearchBar extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
}
handleUserInput(text) {
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index d10c5be27..55ece2c97 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -40,12 +40,14 @@ export default class SearchResults extends React.Component {
this.mounted = false;
this.onChange = this.onChange.bind(this);
+ this.onUserChange = this.onUserChange.bind(this);
this.resize = this.resize.bind(this);
this.handleResize = this.handleResize.bind(this);
const state = getStateFromStores();
state.windowWidth = Utils.windowWidth();
state.windowHeight = Utils.windowHeight();
+ state.profiles = JSON.parse(JSON.stringify(UserStore.getProfiles()));
this.state = state;
}
@@ -53,6 +55,7 @@ export default class SearchResults extends React.Component {
this.mounted = true;
SearchStore.addSearchChangeListener(this.onChange);
ChannelStore.addChangeListener(this.onChange);
+ UserStore.addChangeListener(this.onUserChange);
this.resize();
window.addEventListener('resize', this.handleResize);
}
@@ -68,6 +71,7 @@ export default class SearchResults extends React.Component {
componentWillUnmount() {
SearchStore.removeSearchChangeListener(this.onChange);
ChannelStore.removeChangeListener(this.onChange);
+ UserStore.removeChangeListener(this.onUserChange);
this.mounted = false;
window.removeEventListener('resize', this.handleResize);
}
@@ -85,6 +89,10 @@ export default class SearchResults extends React.Component {
}
}
+ onUserChange() {
+ this.setState({profiles: JSON.parse(JSON.stringify(UserStore.getProfiles()))});
+ }
+
resize() {
$('#search-items-container').scrollTop(0);
if (this.state.windowWidth > 768) {
@@ -101,6 +109,7 @@ export default class SearchResults extends React.Component {
}
var noResults = (!results || !results.order || !results.order.length);
var searchTerm = SearchStore.getSearchTerm();
+ const profiles = this.state.profiles || {};
var ctls = null;
@@ -140,6 +149,7 @@ export default class SearchResults extends React.Component {
key={post.id}
channel={this.state.channels.get(post.channel_id)}
post={post}
+ user={profiles[post.user_id]}
term={searchTerm}
isMentionSearch={this.props.isMentionSearch}
/>
diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx
index 7f88eb2c7..20fe342dc 100644
--- a/web/react/components/search_results_header.jsx
+++ b/web/react/components/search_results_header.jsx
@@ -32,7 +32,7 @@ export default class SearchResultsHeader extends React.Component {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_POST_SELECTED,
- results: null
+ postId: null
});
}
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 3363c97f7..05292b7b3 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -36,9 +36,10 @@ export default class SearchResultsItem extends React.Component {
}
render() {
- var channelName = null;
+ let channelName = null;
const channel = this.props.channel;
- var timestamp = UserStore.getCurrentUser().update_at;
+ const timestamp = UserStore.getCurrentUser().update_at;
+ const user = this.props.user || {};
if (channel) {
channelName = channel.display_name;
@@ -84,7 +85,7 @@ export default class SearchResultsItem extends React.Component {
</div>
<div>
<ul className='post__header'>
- <li className='col__name'><strong><UserProfile userId={this.props.post.user_id}/></strong></li>
+ <li className='col__name'><strong><UserProfile user={user}/></strong></li>
<li className='col'>
<time className='search-item-time'>
<FormattedDate
@@ -135,6 +136,7 @@ export default class SearchResultsItem extends React.Component {
SearchResultsItem.propTypes = {
post: React.PropTypes.object,
+ user: React.PropTypes.object,
channel: React.PropTypes.object,
isMentionSearch: React.PropTypes.bool,
term: React.PropTypes.string
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 868b7e1b2..cb2ee0d8f 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -8,7 +8,7 @@ export default class SettingItemMin extends React.Component {
let editButton = null;
if (!this.props.disableOpen) {
editButton = (
- <li className='col-sm-2 section-edit'>
+ <li className='col-sm-3 section-edit'>
<a
className='theme'
href='#'
@@ -29,9 +29,9 @@ export default class SettingItemMin extends React.Component {
className='section-min'
onClick={this.props.updateSection}
>
- <li className='col-sm-10 section-title'>{this.props.title}</li>
+ <li className='col-sm-9 section-title'>{this.props.title}</li>
{editButton}
- <li className='col-sm-7 section-describe'>{this.props.describe}</li>
+ <li className='col-sm-9 section-describe'>{this.props.describe}</li>
</ul>
);
}
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 740a7b166..dbec3d02d 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -351,12 +351,10 @@ class SignupUserComplete extends React.Component {
<div>
{signupMessage}
<div className='or__container'>
- <span>
- <FormattedMessage
- id='signup_user_completed.or'
- defaultMessage='or'
- />
- </span>
+ <FormattedMessage
+ id='signup_user_completed.or'
+ defaultMessage='or'
+ />
</div>
</div>
);
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 1e393cfe9..31b2b9907 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -2,7 +2,6 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import UserStore from '../stores/user_store.jsx';
import {FormattedMessage} from 'mm-intl';
@@ -19,45 +18,15 @@ function nextId() {
export default class UserProfile extends React.Component {
constructor(props) {
super(props);
-
this.uniqueId = nextId();
- this.onChange = this.onChange.bind(this);
-
- this.state = this.getStateFromStores(this.props.userId);
- }
- getStateFromStores(userId) {
- var profile = UserStore.getProfile(userId);
-
- if (profile == null) {
- return {profile: {id: '0', username: '...'}};
- }
-
- return {profile};
}
componentDidMount() {
- UserStore.addChangeListener(this.onChange);
if (!this.props.disablePopover) {
$('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
}
}
- componentWillUnmount() {
- UserStore.removeChangeListener(this.onChange);
- }
- onChange(userId) {
- if (!userId || userId === this.props.userId) {
- var newState = this.getStateFromStores(this.props.userId);
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
- }
- }
- componentWillReceiveProps(nextProps) {
- if (this.props.userId !== nextProps.userId) {
- this.setState(this.getStateFromStores(nextProps.userId));
- }
- }
render() {
- var name = Utils.displayUsername(this.state.profile.id);
+ var name = Utils.displayUsername(this.props.user.id);
if (this.props.overwriteName) {
name = this.props.overwriteName;
} else if (!name) {
@@ -68,7 +37,7 @@ export default class UserProfile extends React.Component {
return <div>{name}</div>;
}
- var profileImg = '/api/v1/users/' + this.state.profile.id + '/image?time=' + this.state.profile.update_at + '&' + Utils.getSessionIndex();
+ var profileImg = '/api/v1/users/' + this.props.user.id + '/image?time=' + this.props.user.update_at + '&' + Utils.getSessionIndex();
if (this.props.overwriteImage) {
profileImg = this.props.overwriteImage;
}
@@ -100,14 +69,14 @@ export default class UserProfile extends React.Component {
dataContent.push(
<div
data-toggle='tooltip'
- title={this.state.profile.email}
+ title={this.props.user.email}
key='user-popover-email'
>
<a
- href={'mailto:' + this.state.profile.email}
+ href={'mailto:' + this.props.user.email}
className='text-nowrap text-lowercase user-popover__email'
>
- {this.state.profile.email}
+ {this.props.user.email}
</a>
</div>
);
@@ -139,13 +108,13 @@ export default class UserProfile extends React.Component {
}
UserProfile.defaultProps = {
- userId: '',
+ user: {},
overwriteName: '',
overwriteImage: '',
disablePopover: false
};
UserProfile.propTypes = {
- userId: React.PropTypes.string,
+ user: React.PropTypes.object.isRequired,
overwriteName: React.PropTypes.string,
overwriteImage: React.PropTypes.string,
disablePopover: React.PropTypes.bool
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index cd229775f..2f2116c2a 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -22,7 +22,7 @@ const holders = defineMessages({
},
usernameRestrictions: {
id: 'user.settings.general.usernameRestrictions',
- defaultMessage: "'Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."
+ defaultMessage: "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'."
},
validEmail: {
id: 'user.settings.general.validEmail',
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 61fa91cf8..bc78c049c 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -8,7 +8,6 @@ import * as Client from '../utils/client.jsx';
import GetPostLinkModal from '../components/get_post_link_modal.jsx';
import GetTeamInviteLinkModal from '../components/get_team_invite_link_modal.jsx';
-import RenameChannelModal from '../components/rename_channel_modal.jsx';
import EditPostModal from '../components/edit_post_modal.jsx';
import DeletePostModal from '../components/delete_post_modal.jsx';
import MoreChannelsModal from '../components/more_channels.jsx';
@@ -73,7 +72,6 @@ class Root extends React.Component {
<InviteMemberModal/>
<ImportThemeModal/>
<TeamSettingsModal/>
- <RenameChannelModal/>
<MoreChannelsModal/>
<EditPostModal/>
<DeletePostModal/>
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index f5c342163..1dc0dc9bf 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -20,72 +20,7 @@ const SELECTED_POST_CHANGE_EVENT = 'selected_post_change';
class PostStoreClass extends EventEmitter {
constructor() {
super();
-
- this.emitChange = this.emitChange.bind(this);
- this.addChangeListener = this.addChangeListener.bind(this);
- this.removeChangeListener = this.removeChangeListener.bind(this);
-
- this.emitEditPost = this.emitEditPost.bind(this);
- this.addEditPostListener = this.addEditPostListener.bind(this);
- this.removeEditPostListener = this.removeEditPostListner.bind(this);
-
- this.emitPostsViewJump = this.emitPostsViewJump.bind(this);
- this.addPostsViewJumpListener = this.addPostsViewJumpListener.bind(this);
- this.removePostsViewJumpListener = this.removePostsViewJumpListener.bind(this);
-
- this.emitPostFocused = this.emitPostFocused.bind(this);
- this.addPostFocusedListener = this.addPostFocusedListener.bind(this);
- this.removePostFocusedListener = this.removePostFocusedListener.bind(this);
-
- this.makePostsInfo = this.makePostsInfo.bind(this);
-
- this.getPost = this.getPost.bind(this);
- this.getAllPosts = this.getAllPosts.bind(this);
- this.getEarliestPost = this.getEarliestPost.bind(this);
- this.getLatestPost = this.getLatestPost.bind(this);
- this.getVisiblePosts = this.getVisiblePosts.bind(this);
- this.getVisibilityAtTop = this.getVisibilityAtTop.bind(this);
- this.getVisibilityAtBottom = this.getVisibilityAtBottom.bind(this);
- this.requestVisibilityIncrease = this.requestVisibilityIncrease.bind(this);
- this.getFocusedPostId = this.getFocusedPostId.bind(this);
-
- this.storePosts = this.storePosts.bind(this);
- this.storePost = this.storePost.bind(this);
- this.storeFocusedPost = this.storeFocusedPost.bind(this);
- this.checkBounds = this.checkBounds.bind(this);
-
- this.clearFocusedPost = this.clearFocusedPost.bind(this);
- this.clearChannelVisibility = this.clearChannelVisibility.bind(this);
-
- this.deletePost = this.deletePost.bind(this);
- this.removePost = this.removePost.bind(this);
-
- this.getPendingPosts = this.getPendingPosts.bind(this);
- this.storePendingPost = this.storePendingPost.bind(this);
- this.removePendingPost = this.removePendingPost.bind(this);
- this.clearPendingPosts = this.clearPendingPosts.bind(this);
- this.updatePendingPost = this.updatePendingPost.bind(this);
-
- // These functions are bad and work should be done to remove this system when the RHS dies
- this.storeSelectedPost = this.storeSelectedPost.bind(this);
- this.getSelectedPost = this.getSelectedPost.bind(this);
- this.emitSelectedPostChange = this.emitSelectedPostChange.bind(this);
- this.addSelectedPostChangeListener = this.addSelectedPostChangeListener.bind(this);
- this.removeSelectedPostChangeListener = this.removeSelectedPostChangeListener.bind(this);
- this.selectedPost = null;
-
- this.getEmptyDraft = this.getEmptyDraft.bind(this);
- this.storeCurrentDraft = this.storeCurrentDraft.bind(this);
- this.getCurrentDraft = this.getCurrentDraft.bind(this);
- this.storeDraft = this.storeDraft.bind(this);
- this.getDraft = this.getDraft.bind(this);
- this.storeCommentDraft = this.storeCommentDraft.bind(this);
- this.getCommentDraft = this.getCommentDraft.bind(this);
- this.clearDraftUploads = this.clearDraftUploads.bind(this);
- this.clearCommentDraftUploads = this.clearCommentDraftUploads.bind(this);
- this.getCurrentUsersLatestPost = this.getCurrentUsersLatestPost.bind(this);
- this.getCommentCount = this.getCommentCount.bind(this);
-
+ this.selectedPostId = null;
this.postsInfo = {};
this.currentFocusedPostId = null;
}
@@ -330,6 +265,12 @@ class PostStoreClass extends EventEmitter {
}
deletePost(post) {
+ const postInfo = this.postsInfo[post.channel_id];
+ if (!postInfo) {
+ // the post that has been deleted is in a channel that we haven't seen so just ignore it
+ return;
+ }
+
const postList = this.postsInfo[post.channel_id].postList;
if (isPostListNull(postList)) {
@@ -421,12 +362,59 @@ class PostStoreClass extends EventEmitter {
this.emitChange();
}
- storeSelectedPost(postList) {
- this.selectedPost = postList;
+ storeSelectedPostId(postId) {
+ this.selectedPostId = postId;
+ }
+
+ getSelectedPostId() {
+ return this.selectedPostId;
}
getSelectedPost() {
- return this.selectedPost;
+ if (this.selectedPostId == null) {
+ return null;
+ }
+
+ for (const k in this.postsInfo) {
+ if (this.postsInfo[k].postList.posts.hasOwnProperty(this.selectedPostId)) {
+ return this.postsInfo[k].postList.posts[this.selectedPostId];
+ }
+ }
+
+ return null;
+ }
+
+ getSelectedPostThread() {
+ if (this.selectedPostId == null) {
+ return null;
+ }
+
+ let posts;
+ let pendingPosts;
+ for (const k in this.postsInfo) {
+ if (this.postsInfo[k].postList.posts.hasOwnProperty(this.selectedPostId)) {
+ posts = this.postsInfo[k].postList.posts;
+ if (this.postsInfo[k].pendingPosts != null) {
+ pendingPosts = this.postsInfo[k].pendingPosts.posts;
+ }
+ }
+ }
+
+ const threadPosts = {};
+ const rootId = this.selectedPostId;
+ for (const k in posts) {
+ if (posts[k].root_id === rootId) {
+ threadPosts[k] = JSON.parse(JSON.stringify(posts[k]));
+ }
+ }
+
+ for (const k in pendingPosts) {
+ if (pendingPosts[k].root_id === rootId) {
+ threadPosts[k] = JSON.parse(JSON.stringify(pendingPosts[k]));
+ }
+ }
+
+ return threadPosts;
}
emitSelectedPostChange(fromSearch) {
@@ -565,7 +553,7 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
PostStore.emitChange();
break;
case ActionTypes.RECEIVED_POST_SELECTED:
- PostStore.storeSelectedPost(action.post_list);
+ PostStore.storeSelectedPostId(action.postId);
PostStore.emitSelectedPostChange(action.from_search);
break;
default:
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index dd60e166f..75a87d424 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -17,50 +17,6 @@ const CHANGE_EVENT_STATUSES = 'change_statuses';
class UserStoreClass extends EventEmitter {
constructor() {
super();
-
- this.emitChange = this.emitChange.bind(this);
- this.addChangeListener = this.addChangeListener.bind(this);
- this.removeChangeListener = this.removeChangeListener.bind(this);
- this.emitSessionsChange = this.emitSessionsChange.bind(this);
- this.addSessionsChangeListener = this.addSessionsChangeListener.bind(this);
- this.removeSessionsChangeListener = this.removeSessionsChangeListener.bind(this);
- this.emitAuditsChange = this.emitAuditsChange.bind(this);
- this.addAuditsChangeListener = this.addAuditsChangeListener.bind(this);
- this.removeAuditsChangeListener = this.removeAuditsChangeListener.bind(this);
- this.emitTeamsChange = this.emitTeamsChange.bind(this);
- this.addTeamsChangeListener = this.addTeamsChangeListener.bind(this);
- this.removeTeamsChangeListener = this.removeTeamsChangeListener.bind(this);
- this.emitStatusesChange = this.emitStatusesChange.bind(this);
- this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
- this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
- this.getCurrentId = this.getCurrentId.bind(this);
- this.getCurrentUser = this.getCurrentUser.bind(this);
- this.setCurrentUser = this.setCurrentUser.bind(this);
- this.getLastEmail = this.getLastEmail.bind(this);
- this.setLastEmail = this.setLastEmail.bind(this);
- this.getLastUsername = this.getLastUsername.bind(this);
- this.setLastUsername = this.setLastUsername.bind(this);
- this.hasProfile = this.hasProfile.bind(this);
- this.getProfile = this.getProfile.bind(this);
- this.getProfileByUsername = this.getProfileByUsername.bind(this);
- this.getProfilesUsernameMap = this.getProfilesUsernameMap.bind(this);
- this.getProfiles = this.getProfiles.bind(this);
- this.getActiveOnlyProfiles = this.getActiveOnlyProfiles.bind(this);
- this.getActiveOnlyProfileList = this.getActiveOnlyProfileList.bind(this);
- this.saveProfile = this.saveProfile.bind(this);
- this.setSessions = this.setSessions.bind(this);
- this.getSessions = this.getSessions.bind(this);
- this.setAudits = this.setAudits.bind(this);
- this.getAudits = this.getAudits.bind(this);
- this.setTeams = this.setTeams.bind(this);
- this.getTeams = this.getTeams.bind(this);
- this.getCurrentMentionKeys = this.getCurrentMentionKeys.bind(this);
- this.setStatuses = this.setStatuses.bind(this);
- this.pSetStatuses = this.pSetStatuses.bind(this);
- this.setStatus = this.setStatus.bind(this);
- this.getStatuses = this.getStatuses.bind(this);
- this.getStatus = this.getStatus.bind(this);
-
this.profileCache = null;
}
@@ -277,7 +233,11 @@ class UserStoreClass extends EventEmitter {
}
getCurrentMentionKeys() {
- var user = this.getCurrentUser();
+ return this.getMentionKeys(this.getCurrentId());
+ }
+
+ getMentionKeys(id) {
+ var user = this.getProfile(id);
var keys = [];
@@ -330,7 +290,7 @@ class UserStoreClass extends EventEmitter {
}
var UserStore = new UserStoreClass();
-UserStore.setMaxListeners(0);
+UserStore.setMaxListeners(15);
UserStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
diff --git a/web/react/utils/channel_intro_messages.jsx b/web/react/utils/channel_intro_messages.jsx
index 79e58147f..ed94f94b8 100644
--- a/web/react/utils/channel_intro_messages.jsx
+++ b/web/react/utils/channel_intro_messages.jsx
@@ -47,7 +47,7 @@ export function createDMIntroMessage(channel) {
</div>
<div className='channel-intro-profile'>
<strong>
- <UserProfile userId={teammate.id}/>
+ <UserProfile user={teammate}/>
</strong>
</div>
<p className='channel-intro-text'>
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index a4d2515e2..38516fae2 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -19,7 +19,7 @@ import {FormattedTime} from 'mm-intl';
export function isEmail(email) {
// writing a regex to match all valid email addresses is really, really hard (see http://stackoverflow.com/a/201378)
// so we just do a simple check and rely on a verification email to tell if it's a real address
- return email.indexOf('@') !== -1;
+ return (/^.+@.+$/).test(email);
}
export function cleanUpUrlable(input) {
@@ -313,41 +313,17 @@ export function getTimestamp() {
// extracts links not styled by Markdown
export function extractLinks(text) {
const links = [];
- let replaceText = text;
-
- // pull out the Markdown code blocks
- const codeBlocks = [];
- const splitText = replaceText.split('`'); // also handles ```
- for (let i = 1; i < splitText.length; i += 2) {
- if (splitText[i].trim() !== '') {
- codeBlocks.push(splitText[i]);
- }
- }
+ let inText = text;
+
+ // strip out code blocks
+ inText = inText.replace(/`[^`]*`/g, '');
+
+ // strip out inline markdown images
+ inText = inText.replace(/!\[[^\]]*\]\([^\)]*\)/g, '');
function replaceFn(autolinker, match) {
let link = '';
const matchText = match.getMatchedText();
- const tempText = replaceText;
-
- const start = replaceText.indexOf(matchText);
- const end = start + matchText.length;
-
- replaceText = replaceText.substring(0, start) + replaceText.substring(end);
-
- // if it's a Markdown link, just skip it
- if (start > 1) {
- if (tempText.charAt(start - 2) === ']' && tempText.charAt(start - 1) === '(' && tempText.charAt(end) === ')') {
- return;
- }
- }
-
- // if it's in a Markdown code block, skip it
- for (const i in codeBlocks) {
- if (codeBlocks[i].indexOf(matchText) === 0) {
- codeBlocks[i] = codeBlocks[i].replace(matchText, '');
- return;
- }
- }
if (matchText.trim().indexOf('http') === 0) {
link = matchText;
@@ -358,16 +334,19 @@ export function extractLinks(text) {
links.push(link);
}
- Autolinker.link(text, {
- replaceFn,
- urls: {schemeMatches: true, wwwMatches: true, tldMatches: false},
- emails: false,
- twitter: false,
- phone: false,
- hashtag: false
- });
+ Autolinker.link(
+ inText,
+ {
+ replaceFn,
+ urls: {schemeMatches: true, wwwMatches: true, tldMatches: false},
+ emails: false,
+ twitter: false,
+ phone: false,
+ hashtag: false
+ }
+ );
- return {links, text};
+ return links;
}
export function escapeRegExp(string) {
@@ -758,7 +737,7 @@ export function applyTheme(theme) {
changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.command-name:hover, .mentions-name:hover, .suggestion--selected, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover, .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
- changeCss('code', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
+ changeCss('code, .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
changeCss('@media(min-width: 960px){.post.current--user:hover .post__body ', 'background: none;', 1);
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
changeCss('.search-help-popover .search-autocomplete__item:hover, .settings-modal .settings-table .settings-content .appearance-section .theme-elements__body', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
@@ -1417,3 +1396,7 @@ export function languages() {
export function isPostEphemeral(post) {
return post.type === Constants.POST_TYPE_EPHEMERAL || post.state === Constants.POST_DELETED;
}
+
+export function getRootId(post) {
+ return post.root_id === '' ? post.id : post.root_id;
+}