summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/access_history_modal.jsx10
-rw-r--r--web/react/components/activity_log_modal.jsx8
-rw-r--r--web/react/components/channel_header.jsx77
-rw-r--r--web/react/components/channel_info_modal.jsx111
-rw-r--r--web/react/components/channel_notifications_modal.jsx (renamed from web/react/components/channel_notifications.jsx)108
-rw-r--r--web/react/components/create_comment.jsx3
-rw-r--r--web/react/components/create_post.jsx3
-rw-r--r--web/react/components/delete_channel_modal.jsx116
-rw-r--r--web/react/components/delete_post_modal.jsx155
-rw-r--r--web/react/components/docs.jsx41
-rw-r--r--web/react/components/edit_post_modal.jsx3
-rw-r--r--web/react/components/login.jsx16
-rw-r--r--web/react/components/navbar.jsx80
-rw-r--r--web/react/components/post_info.jsx14
-rw-r--r--web/react/components/posts_view.jsx17
-rw-r--r--web/react/components/rename_channel_modal.jsx51
-rw-r--r--web/react/components/rhs_comment.jsx8
-rw-r--r--web/react/components/rhs_root_post.jsx12
-rw-r--r--web/react/components/rhs_thread.jsx2
-rw-r--r--web/react/components/search_results.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx18
-rw-r--r--web/react/components/textbox.jsx15
-rw-r--r--web/react/components/toggle_modal_button.jsx62
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx21
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx48
-rw-r--r--web/react/pages/channel.jsx18
-rw-r--r--web/react/pages/docs.jsx16
-rw-r--r--web/react/stores/browser_store.jsx2
-rw-r--r--web/react/stores/modal_store.jsx8
-rw-r--r--web/react/stores/post_store.jsx19
-rw-r--r--web/react/utils/async_client.jsx2
-rw-r--r--web/react/utils/channel_intro_mssages.jsx1
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/react/utils/utils.jsx15
-rw-r--r--web/sass-files/sass/partials/_content.scss1
-rw-r--r--web/sass-files/sass/partials/_files.scss8
-rw-r--r--web/sass-files/sass/partials/_forms.scss4
-rw-r--r--web/sass-files/sass/partials/_headers.scss31
-rw-r--r--web/sass-files/sass/partials/_post.scss68
-rw-r--r--web/sass-files/sass/partials/_post_right.scss17
-rw-r--r--web/sass-files/sass/partials/_responsive.scss152
-rw-r--r--web/sass-files/sass/partials/_search.scss11
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss2
-rw-r--r--web/sass-files/sass/partials/_sidebar--menu.scss5
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss26
l---------web/static/help/Messaging.md1
-rw-r--r--web/templates/docs.html24
-rw-r--r--web/web.go11
48 files changed, 821 insertions, 625 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index ab5686720..65b80dfb7 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -14,8 +14,8 @@ export default class AccessHistoryModal extends React.Component {
this.onAuditChange = this.onAuditChange.bind(this);
this.handleMoreInfo = this.handleMoreInfo.bind(this);
- this.onHide = this.onHide.bind(this);
this.onShow = this.onShow.bind(this);
+ this.onHide = this.onHide.bind(this);
this.formatAuditInfo = this.formatAuditInfo.bind(this);
this.handleRevokedSession = this.handleRevokedSession.bind(this);
@@ -39,10 +39,14 @@ export default class AccessHistoryModal extends React.Component {
}
onHide() {
this.setState({moreInfo: []});
- this.props.onModalDismissed();
+ this.props.onHide();
}
componentDidMount() {
UserStore.addAuditsChangeListener(this.onAuditChange);
+
+ if (this.props.show) {
+ this.onShow();
+ }
}
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
@@ -406,5 +410,5 @@ export default class AccessHistoryModal extends React.Component {
AccessHistoryModal.propTypes = {
show: React.PropTypes.bool.isRequired,
- onModalDismissed: React.PropTypes.func.isRequired
+ onHide: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index af423a601..5824ce7e2 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -58,10 +58,14 @@ export default class ActivityLogModal extends React.Component {
}
onHide() {
this.setState({moreInfo: []});
- this.props.onModalDismissed();
+ this.props.onHide();
}
componentDidMount() {
UserStore.addSessionsChangeListener(this.onListenerChange);
+
+ if (this.props.show) {
+ this.onShow();
+ }
}
componentDidUpdate(prevProps) {
if (this.props.show && !prevProps.show) {
@@ -178,5 +182,5 @@ export default class ActivityLogModal extends React.Component {
ActivityLogModal.propTypes = {
show: React.PropTypes.bool.isRequired,
- onModalDismissed: React.PropTypes.func.isRequired
+ onHide: React.PropTypes.func.isRequired
};
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index a8d4ec100..ffe7cbb5d 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -5,8 +5,12 @@ const NavbarSearchBox = require('./search_bar.jsx');
const MessageWrapper = require('./message_wrapper.jsx');
const PopoverListMembers = require('./popover_list_members.jsx');
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
+const ChannelInfoModal = require('./channel_info_modal.jsx');
const ChannelInviteModal = require('./channel_invite_modal.jsx');
const ChannelMembersModal = require('./channel_members_modal.jsx');
+const ChannelNotificationsModal = require('./channel_notifications_modal.jsx');
+const DeleteChannelModal = require('./delete_channel_modal.jsx');
+const ToggleModalButton = require('./toggle_modal_button.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
const UserStore = require('../stores/user_store.jsx');
@@ -180,15 +184,13 @@ export default class ChannelHeader extends React.Component {
key='view_info'
role='presentation'
>
- <a
+ <ToggleModalButton
role='menuitem'
- data-toggle='modal'
- data-target='#channel_info'
- data-channelid={channel.id}
- href='#'
+ dialogType={ChannelInfoModal}
+ dialogProps={{channel}}
>
{'View Info'}
- </a>
+ </ToggleModalButton>
</li>
);
@@ -263,58 +265,55 @@ export default class ChannelHeader extends React.Component {
key='notification_preferences'
role='presentation'
>
- <a
+ <ToggleModalButton
role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#channel_notifications'
- data-title={channel.display_name}
- data-channelid={channel.id}
+ dialogType={ChannelNotificationsModal}
+ dialogProps={{channel}}
>
{'Notification Preferences'}
- </a>
+ </ToggleModalButton>
</li>
);
- if (!ChannelStore.isDefault(channel)) {
- if (isAdmin) {
- dropdownContents.push(
- <li
- key='rename_channel'
- role='presentation'
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='rename_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#rename_channel'
+ data-display={channel.display_name}
+ data-name={channel.name}
+ data-channelid={channel.id}
>
- <a
- role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#rename_channel'
- data-display={channel.display_name}
- data-name={channel.name}
- data-channelid={channel.id}
- >
- {'Rename '}{channelTerm}{'...'}
- </a>
- </li>
- );
+ {'Rename '}{channelTerm}{'...'}
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
dropdownContents.push(
<li
key='delete_channel'
role='presentation'
>
- <a
+ <ToggleModalButton
role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#delete_channel'
- data-title={channel.display_name}
- data-channelid={channel.id}
+ dialogType={DeleteChannelModal}
+ dialogProps={{channel}}
>
{'Delete '}{channelTerm}{'...'}
- </a>
+ </ToggleModalButton>
</li>
);
}
+ }
+ if (!ChannelStore.isDefault(channel)) {
dropdownContents.push(
<li
key='leave_channel'
diff --git a/web/react/components/channel_info_modal.jsx b/web/react/components/channel_info_modal.jsx
index bccd8d0b9..18e125de3 100644
--- a/web/react/components/channel_info_modal.jsx
+++ b/web/react/components/channel_info_modal.jsx
@@ -1,88 +1,57 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var ChannelStore = require('../stores/channel_store.jsx');
-
-export default class CommandList extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- channel_id: ChannelStore.getCurrentId()
- };
- }
-
- componentDidMount() {
- var self = this;
- if (this.refs.modal) {
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function show(e) {
- var button = e.relatedTarget;
- self.setState({channel_id: $(button).attr('data-channelid')});
- });
- }
- }
+const Modal = ReactBootstrap.Modal;
+export default class ChannelInfoModal extends React.Component {
render() {
- var channel = ChannelStore.get(this.state.channel_id);
-
+ let channel = this.props.channel;
if (!channel) {
- channel = {};
- channel.display_name = 'No Channel Found';
- channel.name = 'No Channel Found';
- channel.id = 'No Channel Found';
+ channel = {
+ display_name: 'No Channel Found',
+ name: 'No Channel Found',
+ id: 'No Channel Found'
+ };
}
return (
- <div
- className='modal fade'
- ref='modal'
- id='channel_info'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onHide}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4
- className='modal-title'
- id='myModalLabel'
- >
- <span className='name'>{channel.display_name}</span>
- </h4>
- </div>
- <div className='modal-body'>
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>Channel Name: </div>
+ <Modal.Header closeButtton={true}>
+ {channel.display_name}
+ </Modal.Header>
+ <Modal.Body ref='modalBody'>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>{'Channel Name:'}</div>
<div className='col-sm-9'>{channel.display_name}</div>
- </div>
- <div className='row form-group'>
- <div className='col-sm-3 info__label'>Channel Handle:</div>
+ </div>
+ <div className='row form-group'>
+ <div className='col-sm-3 info__label'>{'Channel Handle:'}</div>
<div className='col-sm-9'>{channel.name}</div>
- </div>
- <div className='row'>
- <div className='col-sm-3 info__label'>Channel ID:</div>
- <div className='col-sm-9'>{channel.id}</div>
- </div>
</div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >Close</button>
+ <div className='row'>
+ <div className='col-sm-3 info__label'>{'Channel ID:'}</div>
+ <div className='col-sm-9'>{channel.id}</div>
</div>
- </div>
- </div>
- </div>
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onHide}
+ >
+ {'Close'}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
+
+ChannelInfoModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onHide: React.PropTypes.func.isRequired,
+ channel: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/channel_notifications.jsx b/web/react/components/channel_notifications_modal.jsx
index f57fc12c5..c8bd1c2dc 100644
--- a/web/react/components/channel_notifications.jsx
+++ b/web/react/components/channel_notifications_modal.jsx
@@ -1,15 +1,15 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var Modal = ReactBootstrap.Modal;
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
-var Utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
-export default class ChannelNotifications extends React.Component {
+export default class ChannelNotificationsModal extends React.Component {
constructor(props) {
super(props);
@@ -23,35 +23,17 @@ export default class ChannelNotifications extends React.Component {
this.handleSubmitMarkUnreadLevel = this.handleSubmitMarkUnreadLevel.bind(this);
this.handleUpdateMarkUnreadLevel = this.handleUpdateMarkUnreadLevel.bind(this);
this.createMarkUnreadLevelSection = this.createMarkUnreadLevelSection.bind(this);
- this.onShow = this.onShow.bind(this);
+ const member = ChannelStore.getMember(props.channel.id);
this.state = {
- notifyLevel: '',
- markUnreadLevel: '',
- title: '',
+ notifyLevel: member.notify_props.desktop,
+ markUnreadLevel: member.notify_props.mark_unread,
channelId: '',
activeSection: ''
};
}
- onShow(e) {
- var button = e.relatedTarget;
- var channelId = button.getAttribute('data-channelid');
-
- const member = ChannelStore.getMember(channelId);
- var notifyLevel = member.notify_props.desktop;
- var markUnreadLevel = member.notify_props.mark_unread;
-
- this.setState({
- notifyLevel,
- markUnreadLevel,
- title: button.getAttribute('data-title'),
- channelId
- });
- }
componentDidMount() {
ChannelStore.addChangeListener(this.onListenerChange);
-
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
}
componentWillUnmount() {
ChannelStore.removeChangeListener(this.onListenerChange);
@@ -62,15 +44,12 @@ export default class ChannelNotifications extends React.Component {
}
const member = ChannelStore.getMember(this.state.channelId);
- var notifyLevel = member.notify_props.desktop;
- var markUnreadLevel = member.notify_props.mark_unread;
- var newState = this.state;
- newState.notifyLevel = notifyLevel;
- newState.markUnreadLevel = markUnreadLevel;
-
- if (!Utils.areObjectsEqual(this.state, newState)) {
- this.setState(newState);
+ if (member.notify_props.desktop !== this.state.notifyLevel || member.notify_props.mark_unread !== this.state.mark_unread) {
+ this.setState({
+ notifyLevel: member.notify_props.desktop,
+ markUnreadLevel: member.notify_props.mark_unread
+ });
}
}
updateSection(section) {
@@ -104,7 +83,6 @@ export default class ChannelNotifications extends React.Component {
}
handleUpdateNotifyLevel(notifyLevel) {
this.setState({notifyLevel});
- ReactDOM.findDOMNode(this.refs.modal).focus();
}
createNotifyLevelSection(serverError) {
var handleUpdateSection;
@@ -262,7 +240,6 @@ export default class ChannelNotifications extends React.Component {
handleUpdateMarkUnreadLevel(markUnreadLevel) {
this.setState({markUnreadLevel});
- ReactDOM.findDOMNode(this.refs.modal).focus();
}
createMarkUnreadLevelSection(serverError) {
@@ -347,48 +324,39 @@ export default class ChannelNotifications extends React.Component {
}
return (
- <div
- className='modal fade'
- id='channel_notifications'
- ref='modal'
- tabIndex='-1'
- role='dialog'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ dialogClassName='settings-modal'
+ onHide={this.props.onHide}
>
- <div className='modal-dialog settings-modal'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
+ <Modal.Header closeButton={true}>
+ {'Notification Preferences for '}<span className='name'>{this.props.channel.display_name}</span>
+ </Modal.Header>
+ <Modal.Body>
+ <div className='settings-table'>
+ <div className='settings-content'>
+ <div
+ ref='wrapper'
+ className='user-settings'
>
- <span aria-hidden='true'>&times;</span>
- <span className='sr-only'>{'Close'}</span>
- </button>
- <h4 className='modal-title'>Notification Preferences for <span className='name'>{this.state.title}</span></h4>
- </div>
- <div className='modal-body'>
- <div className='settings-table'>
- <div className='settings-content'>
- <div
- ref='wrapper'
- className='user-settings'
- >
- <br/>
- <div className='divider-dark first'/>
- {this.createNotifyLevelSection(serverError)}
- <div className='divider-light'/>
- {this.createMarkUnreadLevelSection(serverError)}
- <div className='divider-dark'/>
- </div>
+ <br/>
+ <div className='divider-dark first'/>
+ {this.createNotifyLevelSection(serverError)}
+ <div className='divider-light'/>
+ {this.createMarkUnreadLevelSection(serverError)}
+ <div className='divider-dark'/>
</div>
- </div>
- {serverError}
</div>
</div>
- </div>
- </div>
+ {serverError}
+ </Modal.Body>
+ </Modal>
);
}
}
+
+ChannelNotificationsModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onHide: React.PropTypes.func.isRequired,
+ channel: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index 058594165..22a659ed5 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -194,7 +194,8 @@ export default class CreateComment extends React.Component {
title: 'Comment',
message: lastPost.message,
postId: lastPost.id,
- channelId: lastPost.channel_id
+ channelId: lastPost.channel_id,
+ comments: PostStore.getCommentCount(lastPost)
});
}
}
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 5a69c9bfb..6f25ef608 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -372,7 +372,8 @@ export default class CreatePost extends React.Component {
title: type,
message: lastPost.message,
postId: lastPost.id,
- channelId: lastPost.channel_id
+ channelId: lastPost.channel_id,
+ comments: PostStore.getCommentCount(lastPost)
});
}
}
diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx
index b7d633b38..271f21c3a 100644
--- a/web/react/components/delete_channel_modal.jsx
+++ b/web/react/components/delete_channel_modal.jsx
@@ -1,102 +1,72 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
-const ChannelStore = require('../stores/channel_store.jsx');
-var TeamStore = require('../stores/team_store.jsx');
+const Client = require('../utils/client.jsx');
+const Modal = ReactBootstrap.Modal;
+const TeamStore = require('../stores/team_store.jsx');
+const Utils = require('../utils/utils.jsx');
export default class DeleteChannelModal extends React.Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
- this.onShow = this.onShow.bind(this);
-
- this.state = {
- title: '',
- channelId: ''
- };
}
+
handleDelete() {
- if (this.state.channelId.length !== 26) {
+ if (this.props.channel.id.length !== 26) {
return;
}
- Client.deleteChannel(this.state.channelId,
- function handleDeleteSuccess() {
+ Client.deleteChannel(
+ this.props.channel.id,
+ () => {
AsyncClient.getChannels(true);
window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square';
},
- function handleDeleteError(err) {
+ (err) => {
AsyncClient.dispatchError(err, 'handleDelete');
}
);
}
- onShow(e) {
- var button = $(e.relatedTarget);
- this.setState({
- title: button.attr('data-title'),
- channelId: button.attr('data-channelid')
- });
- }
- componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
- }
+
render() {
- const channel = ChannelStore.getCurrent();
- let channelType = 'channel';
- if (channel && channel.type === 'P') {
- channelType = 'private group';
- }
+ const channelTerm = Utils.getChannelTerm(this.props.channel.type).toLowerCase();
return (
- <div
- className='modal fade'
- ref='modal'
- id='delete_channel'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
+ <Modal
+ show={this.props.show}
+ onHide={this.props.onHide}
>
- <div className='modal-dialog'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4 className='modal-title'>Confirm DELETE Channel</h4>
- </div>
- <div className='modal-body'>
- <p>
- Are you sure you wish to delete the {this.state.title} {channelType}?
- </p>
- </div>
- <div className='modal-footer'>
- <button
- type='button'
- className='btn btn-default'
- data-dismiss='modal'
- >
- Cancel
- </button>
- <button
- type='button'
- className='btn btn-danger'
- data-dismiss='modal'
- onClick={this.handleDelete}
- >
- Delete
- </button>
- </div>
- </div>
- </div>
- </div>
+ <Modal.Header closeButton={true}>{'Confirm DELETE Channel'}</Modal.Header>
+ <Modal.Body>
+ {`Are you sure you wish to delete the ${this.props.channel.display_name} ${channelTerm}?`}
+ </Modal.Body>
+ <Modal.Footer>
+ <button
+ type='button'
+ className='btn btn-default'
+ onClick={this.props.onHide}
+ >
+ {'Cancel'}
+ </button>
+ <button
+ type='button'
+ className='btn btn-danger'
+ data-dismiss='modal'
+ onClick={this.handleDelete}
+ >
+ {'Delete'}
+ </button>
+ </Modal.Footer>
+ </Modal>
);
}
}
+
+DeleteChannelModal.propTypes = {
+ show: React.PropTypes.bool.isRequired,
+ onHide: React.PropTypes.func.isRequired,
+ channel: React.PropTypes.object.isRequired
+};
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index f3bead1c2..e0489856f 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -3,7 +3,8 @@
var Client = require('../utils/client.jsx');
var PostStore = require('../stores/post_store.jsx');
-var BrowserStore = require('../stores/browser_store.jsx');
+var ModalStore = require('../stores/modal_store.jsx');
+var Modal = ReactBootstrap.Modal;
var Utils = require('../utils/utils.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
@@ -15,18 +16,40 @@ export default class DeletePostModal extends React.Component {
super(props);
this.handleDelete = this.handleDelete.bind(this);
+ this.handleToggle = this.handleToggle.bind(this);
+ this.handleHide = this.handleHide.bind(this);
this.onListenerChange = this.onListenerChange.bind(this);
- this.onShow = this.onShow.bind(this);
- this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0};
+ this.selectedList = null;
+
+ this.state = {
+ show: true,
+ post: null,
+ commentCount: 0,
+ error: ''
+ };
+ }
+
+ componentDidMount() {
+ ModalStore.addModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
+ PostStore.addSelectedPostChangeListener(this.onListenerChange);
+ }
+
+ componentWillUnmount() {
+ PostStore.removeSelectedPostChangeListener(this.onListenerChange);
+ ModalStore.removeModalListener(ActionTypes.TOGGLE_DELETE_POST_MODAL, this.handleToggle);
}
+
handleDelete() {
- Client.deletePost(this.state.channelId, this.state.postId,
- function deleteSuccess() {
- var selectedList = this.state.selectedList;
+ Client.deletePost(
+ this.state.post.channel_id,
+ this.state.post.id,
+ () => {
+ var selectedList = this.selectedList;
+
if (selectedList && selectedList.order && selectedList.order.length > 0) {
var selectedPost = selectedList.posts[selectedList.order[0]];
- if ((selectedPost.id === this.state.postId && this.state.title === 'Post') || selectedPost.root_id === this.state.postId) {
+ if ((selectedPost.id === this.state.post.id && !this.state.root_id) || selectedPost.root_id === this.state.post.id) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH,
results: null
@@ -36,7 +59,7 @@ export default class DeletePostModal extends React.Component {
type: ActionTypes.RECIEVED_POST_SELECTED,
results: null
});
- } else if (selectedPost.id === this.state.postId && this.state.title === 'Comment') {
+ } 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]) {
selectedList.order = [selectedPost.root_id];
delete selectedList.posts[selectedPost.id];
@@ -53,98 +76,96 @@ export default class DeletePostModal extends React.Component {
}
}
}
- PostStore.removePost(this.state.postId, this.state.channelId);
- AsyncClient.getPosts(this.state.channelId);
- }.bind(this),
- function deleteFailed(err) {
+
+ PostStore.removePost(this.state.post.id, this.state.post.channel_id);
+ AsyncClient.getPosts(this.state.post.channel_id);
+ },
+ (err) => {
AsyncClient.dispatchError(err, 'deletePost');
}
);
+
+ this.handleHide();
}
- onShow(e) {
- var newState = {};
- if (BrowserStore.getItem('edit_state_transfer')) {
- newState = BrowserStore.getItem('edit_state_transfer');
- BrowserStore.removeItem('edit_state_transfer');
- } else {
- var button = e.relatedTarget;
- newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')};
- }
- this.setState(newState);
- }
- componentDidMount() {
- $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow);
- PostStore.addSelectedPostChangeListener(this.onListenerChange);
+
+ handleToggle(value, args) {
+ this.setState({
+ show: value,
+ post: args.post,
+ commentCount: args.commentCount,
+ error: ''
+ });
}
- componentWillUnmount() {
- PostStore.removeSelectedPostChangeListener(this.onListenerChange);
+
+ handleHide() {
+ this.setState({show: false});
}
+
onListenerChange() {
var newList = PostStore.getSelectedPost();
- if (!Utils.areObjectsEqual(this.state.selectedList, newList)) {
- this.setState({selectedList: newList});
+ if (!Utils.areObjectsEqual(this.selectedList, newList)) {
+ this.selectedList = newList;
}
}
+
render() {
+ if (!this.state.post) {
+ return null;
+ }
+
var error = null;
if (this.state.error) {
error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>;
}
var commentWarning = '';
- if (this.state.comments > 0) {
- commentWarning = 'This post has ' + this.state.comments + ' comment(s) on it.';
+ if (this.state.commentCount > 0) {
+ commentWarning = 'This post has ' + this.state.commentCount + ' comment(s) on it.';
}
+ const postTerm = Utils.getPostTerm(this.state.post);
+
return (
- <div
- className='modal fade'
- id='delete_post'
- ref='modal'
- role='dialog'
- tabIndex='-1'
- aria-hidden='true'
+ <Modal
+ show={this.state.show}
+ onHide={this.handleHide}
>
- <div className='modal-dialog modal-push-down'>
- <div className='modal-content'>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- >
- <span aria-hidden='true'>&times;</span>
- </button>
- <h4 className='modal-title'>Confirm {this.state.title} Delete</h4>
- </div>
- <div className='modal-body'>
- Are you sure you want to delete the {this.state.title.toLowerCase()}?
- <br/>
- <br/>
+ <Modal.Header closeButton={true}>
+ {`Confirm ${postTerm} Delete`}
+ </Modal.Header>
+ <Modal.Body>
+ {`Are you sure you want to delete this ${postTerm.toLowerCase()}?`}
+ <br />
+ <br />
{commentWarning}
- </div>
- {error}
- <div className='modal-footer'>
+ {error}
+ </Modal.Body>
+ <Modal.Footer>
<button
type='button'
className='btn btn-default'
- data-dismiss='modal'
+ onClick={this.handleHide}
>
- Cancel
+ {'Cancel'}
</button>
<button
type='button'
className='btn btn-danger'
- data-dismiss='modal'
onClick={this.handleDelete}
>
- Delete
+ {'Delete'}
</button>
- </div>
- </div>
- </div>
- </div>
+ </Modal.Footer>
+ </Modal>
);
}
+
+ static show(post, commentCount) {
+ AppDispatcher.handleViewAction({
+ type: ActionTypes.TOGGLE_DELETE_POST_MODAL,
+ value: true,
+ post,
+ commentCount: commentCount || 0
+ });
+ }
}
diff --git a/web/react/components/docs.jsx b/web/react/components/docs.jsx
new file mode 100644
index 000000000..68baa6dad
--- /dev/null
+++ b/web/react/components/docs.jsx
@@ -0,0 +1,41 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+const TextFormatting = require('../utils/text_formatting.jsx');
+const UserStore = require('../stores/user_store.jsx');
+
+export default class Docs extends React.Component {
+ constructor(props) {
+ super(props);
+ UserStore.setCurrentUser(global.window.mm_user || {});
+
+ this.state = {text: ''};
+ const errorState = {text: '## 404'};
+
+ if (props.site) {
+ $.get('/static/help/' + props.site + '.md').then((response) => {
+ this.setState({text: response});
+ }, () => {
+ this.setState(errorState);
+ });
+ } else {
+ this.setState(errorState);
+ }
+ }
+
+ render() {
+ return (
+ <div
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.state.text)}}
+ >
+ </div>
+ );
+ }
+}
+
+Docs.defaultProps = {
+ site: ''
+};
+Docs.propTypes = {
+ site: React.PropTypes.string
+};
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx
index ef32baa7d..c75da75c9 100644
--- a/web/react/components/edit_post_modal.jsx
+++ b/web/react/components/edit_post_modal.jsx
@@ -3,6 +3,7 @@
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
+var DeletePostModal = require('./delete_post_modal.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
var PostStore = require('../stores/post_store.jsx');
@@ -34,7 +35,7 @@ export default class EditPostModal extends React.Component {
delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', tempState);
$('#edit_post').modal('hide');
- $('#delete_post').modal('show');
+ DeletePostModal.show(PostStore.getPost(this.state.channel_id, this.state.post_id), this.state.comments);
return;
}
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 423ba9067..7f8820d9f 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -201,14 +201,12 @@ export default class Login extends React.Component {
if (global.window.mm_config.EnableTeamCreation === 'true') {
teamSignUp = (
<div className='margin--extra'>
- <span>{'Want to create your own team? '}
- <a
- href='/'
- className='signup-team-login'
- >
- {'Create one now'}
- </a>
- </span>
+ <a
+ href='/'
+ className='signup-team-login'
+ >
+ {'Create a new team'}
+ </a>
</div>
);
}
@@ -227,7 +225,7 @@ export default class Login extends React.Component {
{emailSignup}
{userSignUp}
<div className='form-group margin--extra form-group--small'>
- <span><a href='/find_team'>{'Find other teams'}</a></span>
+ <span><a href='/find_team'>{'Find your other teams'}</a></span>
</div>
{forgotPassword}
{teamSignUp}
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index af29f219e..1fcfabccd 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -5,7 +5,11 @@ const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
const MessageWrapper = require('./message_wrapper.jsx');
const NotifyCounts = require('./notify_counts.jsx');
const ChannelMembersModal = require('./channel_members_modal.jsx');
+const ChannelInfoModal = require('./channel_info_modal.jsx');
const ChannelInviteModal = require('./channel_invite_modal.jsx');
+const ChannelNotificationsModal = require('./channel_notifications_modal.jsx');
+const DeleteChannelModal = require('./delete_channel_modal.jsx');
+const ToggleModalButton = require('./toggle_modal_button.jsx');
const UserStore = require('../stores/user_store.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
@@ -104,15 +108,13 @@ export default class Navbar extends React.Component {
if (channel) {
var viewInfoOption = (
<li role='presentation'>
- <a
+ <ToggleModalButton
role='menuitem'
- data-toggle='modal'
- data-target='#channel_info'
- data-channelid={channel.id}
- href='#'
+ dialogType={ChannelInfoModal}
+ dialogProps={{channel}}
>
{'View Info'}
- </a>
+ </ToggleModalButton>
</li>
);
@@ -178,18 +180,32 @@ export default class Navbar extends React.Component {
var manageMembersOption;
var renameChannelOption;
var deleteChannelOption;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- manageMembersOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={() => this.setState({showMembersModal: true})}
- >
- {'Manage Members'}
- </a>
- </li>
- );
+ if (!isDirect && isAdmin) {
+ if (!ChannelStore.isDefault(channel)) {
+ manageMembersOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={() => this.setState({showMembersModal: true})}
+ >
+ {'Manage Members'}
+ </a>
+ </li>
+ );
+
+ deleteChannelOption = (
+ <li role='presentation'>
+ <ToggleModalButton
+ role='menuitem'
+ dialogType={DeleteChannelModal}
+ dialogProps={{channel}}
+ >
+ {'Delete Channel...'}
+ </ToggleModalButton>
+ </li>
+ );
+ }
renameChannelOption = (
<li role='presentation'>
@@ -206,37 +222,19 @@ export default class Navbar extends React.Component {
</a>
</li>
);
-
- deleteChannelOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#delete_channel'
- data-title={channel.display_name}
- data-channelid={channel.id}
- >
- {'Delete Channel...'}
- </a>
- </li>
- );
}
var notificationPreferenceOption;
if (!isDirect) {
notificationPreferenceOption = (
<li role='presentation'>
- <a
+ <ToggleModalButton
role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#channel_notifications'
- data-title={channel.display_name}
- data-channelid={channel.id}
+ dialogType={ChannelNotificationsModal}
+ dialogProps={{channel}}
>
- {'Notification Preferences'}
- </a>
+ {'Notification Preferences'}
+ </ToggleModalButton>
</li>
);
}
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index a01d842e5..fffa5b19a 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+var DeletePostModal = require('./delete_post_modal.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
var TimeSince = require('./time_since.jsx');
@@ -50,7 +51,7 @@ export default class PostInfo extends React.Component {
data-channelid={post.channel_id}
data-comments={dataComments}
>
- Edit
+ {'Edit'}
</a>
</li>
);
@@ -65,14 +66,9 @@ export default class PostInfo extends React.Component {
<a
href='#'
role='menuitem'
- data-toggle='modal'
- data-target='#delete_post'
- data-title={type}
- data-postid={post.id}
- data-channelid={post.channel_id}
- data-comments={dataComments}
+ onClick={() => DeletePostModal.show(post, dataComments)}
>
- Delete
+ {'Delete'}
</a>
</li>
);
@@ -89,7 +85,7 @@ export default class PostInfo extends React.Component {
href='#'
onClick={this.props.handleCommentClick}
>
- Reply
+ {'Reply'}
</a>
</li>
);
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index 087ca1df2..ec8223203 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -83,9 +83,14 @@ export default class PostsView extends React.Component {
let hideProfilePic = false;
if (prevPost) {
- sameUser = prevPost.user_id === post.user_id && post.create_at - prevPost.create_at <= 1000 * 60 * 5;
+ const postIsComment = Utils.isComment(post);
+ const prevPostIsComment = Utils.isComment(prevPost);
+ const postFromWebhook = Boolean(post.props && post.props.from_webhook);
+ const prevPostFromWebhook = Boolean(prevPost.props && prevPost.props.from_webhook);
- sameRoot = Utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
+ sameUser = prevPost.user_id === post.user_id && postFromWebhook === prevPostFromWebhook &&
+ post.create_at - prevPost.create_at <= 1000 * 60 * 5;
+ sameRoot = (postIsComment && (prevPost.id === post.root_id || prevPost.root_id === post.root_id)) || (!postIsComment && !prevPostIsComment && sameUser);
// hide the profile pic if:
// the previous post was made by the same user as the current post,
@@ -94,10 +99,10 @@ export default class PostsView extends React.Component {
// the current post is not from a webhook
// and the previous post is not from a webhook
if ((prevPost.user_id === post.user_id) &&
- !Utils.isComment(prevPost) &&
- !Utils.isComment(post) &&
- (!post.props || !post.props.from_webhook) &&
- (!prevPost.props || !prevPost.props.from_webhook)) {
+ !prevPostIsComment &&
+ !postIsComment &&
+ !postFromWebhook &&
+ !prevPostFromWebhook) {
hideProfilePic = true;
}
}
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 9fb3af035..f47009cce 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -5,6 +5,7 @@ const Utils = require('../utils/utils.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
+const Constants = require('../utils/constants.jsx');
export default class RenameChannelModal extends React.Component {
constructor(props) {
@@ -36,10 +37,10 @@ export default class RenameChannelModal extends React.Component {
return;
}
- let channel = ChannelStore.get(this.state.channelId);
+ const channel = ChannelStore.get(this.state.channelId);
const oldName = channel.name;
const oldDisplayName = channel.displayName;
- let state = {serverError: ''};
+ const state = {serverError: ''};
channel.display_name = this.state.displayName.trim();
if (!channel.display_name) {
@@ -60,7 +61,7 @@ export default class RenameChannelModal extends React.Component {
state.nameError = 'This field must be less than 22 characters';
state.invalid = true;
} else {
- let cleanedName = Utils.cleanUpUrlable(channel.name);
+ const cleanedName = Utils.cleanUpUrlable(channel.name);
if (cleanedName === channel.name) {
state.nameError = '';
} else {
@@ -76,7 +77,7 @@ export default class RenameChannelModal extends React.Component {
}
Client.updateChannel(channel,
- function handleUpdateSuccess() {
+ () => {
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
@@ -84,12 +85,12 @@ export default class RenameChannelModal extends React.Component {
ReactDOM.findDOMNode(this.refs.displayName).value = '';
ReactDOM.findDOMNode(this.refs.channelName).value = '';
- }.bind(this),
- function handleUpdateError(err) {
+ },
+ (err) => {
state.serverError = err.message;
state.invalid = true;
this.setState(state);
- }.bind(this)
+ }
);
}
onNameChange() {
@@ -99,10 +100,12 @@ export default class RenameChannelModal extends React.Component {
this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value});
}
displayNameKeyUp() {
- const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
- const channelName = Utils.cleanUpUrlable(displayName);
- ReactDOM.findDOMNode(this.refs.channelName).value = channelName;
- this.setState({channelName: channelName});
+ if (this.state.channelName !== Constants.DEFAULT_CHANNEL) {
+ const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
+ const channelName = Utils.cleanUpUrlable(displayName);
+ ReactDOM.findDOMNode(this.refs.channelName).value = channelName;
+ this.setState({channelName: channelName});
+ }
}
handleClose() {
this.setState({
@@ -150,6 +153,15 @@ export default class RenameChannelModal extends React.Component {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
+ let handleInputLabel = 'Handle';
+ let handleInputClass = 'form-control';
+ let readOnlyHandleInput = false;
+ if (this.state.channelName === Constants.DEFAULT_CHANNEL) {
+ handleInputLabel += ' - Cannot be changed for the default channel';
+ handleInputClass += ' disabled-input';
+ readOnlyHandleInput = true;
+ }
+
return (
<div
className='modal fade'
@@ -167,15 +179,15 @@ export default class RenameChannelModal extends React.Component {
className='close'
data-dismiss='modal'
>
- <span aria-hidden='true'>&times;</span>
- <span className='sr-only'>Close</span>
+ <span aria-hidden='true'>{'×'}</span>
+ <span className='sr-only'>{'Close'}</span>
</button>
- <h4 className='modal-title'>Rename Channel</h4>
+ <h4 className='modal-title'>{'Rename Channel'}</h4>
</div>
<form role='form'>
<div className='modal-body'>
<div className={displayNameClass}>
- <label className='control-label'>Display Name</label>
+ <label className='control-label'>{'Display Name'}</label>
<input
onKeyUp={this.displayNameKeyUp}
onChange={this.onDisplayNameChange}
@@ -190,15 +202,16 @@ export default class RenameChannelModal extends React.Component {
{displayNameError}
</div>
<div className={nameClass}>
- <label className='control-label'>Handle</label>
+ <label className='control-label'>{handleInputLabel}</label>
<input
onChange={this.onNameChange}
type='text'
- className='form-control'
+ className={handleInputClass}
ref='channelName'
placeholder='lowercase alphanumeric&#39;s only'
value={this.state.channelName}
maxLength='64'
+ readOnly={readOnlyHandleInput}
/>
{nameError}
</div>
@@ -210,14 +223,14 @@ export default class RenameChannelModal extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- Cancel
+ {'Cancel'}
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
- Save
+ {'Save'}
</button>
</div>
</form>
diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx
index 58cc1cac7..c16f9ff0e 100644
--- a/web/react/components/rhs_comment.jsx
+++ b/web/react/components/rhs_comment.jsx
@@ -8,6 +8,7 @@ var UserStore = require('../stores/user_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
+var DeletePostModal = require('./delete_post_modal.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -114,12 +115,7 @@ export default class RhsComment extends React.Component {
<a
href='#'
role='menuitem'
- data-toggle='modal'
- data-target='#delete_post'
- data-title='Comment'
- data-postid={post.id}
- data-channelid={post.channel_id}
- data-comments={0}
+ onClick={() => DeletePostModal.show(post, 0)}
>
{'Delete'}
</a>
diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx
index 69de5d523..84fdc014a 100644
--- a/web/react/components/rhs_root_post.jsx
+++ b/web/react/components/rhs_root_post.jsx
@@ -6,6 +6,7 @@ var UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx');
var TextFormatting = require('../utils/text_formatting.jsx');
var utils = require('../utils/utils.jsx');
+var DeletePostModal = require('./delete_post_modal.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
var twemoji = require('twemoji');
var Constants = require('../utils/constants.jsx');
@@ -86,21 +87,16 @@ export default class RhsRootPost extends React.Component {
data-postid={post.id}
data-channelid={post.channel_id}
>
- Edit
+ {'Edit'}
</a>
</li>
<li role='presentation'>
<a
href='#'
role='menuitem'
- data-toggle='modal'
- data-target='#delete_post'
- data-title={type}
- data-postid={post.id}
- data-channelid={post.channel_id}
- data-comments={this.props.commentCount}
+ onClick={() => DeletePostModal.show(post, this.props.commentCount)}
>
- Delete
+ {'Delete'}
</a>
</li>
</ul>
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index 7c11de7cf..cc062c538 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -117,8 +117,6 @@ export default class RhsThread extends React.Component {
}
}
resize() {
- var height = this.state.windowHeight - $('#error_bar').outerHeight() - 100;
- $('.post-right__scroll').css('height', height + 'px');
$('.post-right__scroll').scrollTop(100000);
if (this.state.windowWidth > 768) {
$('.post-right__scroll').perfectScrollbar();
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 2f0068908..f4d8647db 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -62,8 +62,6 @@ export default class SearchResults extends React.Component {
}
resize() {
- var height = this.state.windowHeight - $('#error_bar').outerHeight() - 100;
- $('#search-items-container').css('height', height + 'px');
$('#search-items-container').scrollTop(0);
if (this.state.windowWidth > 768) {
$('#search-items-container').perfectScrollbar();
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 2135e3ef3..6a428e884 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -48,7 +48,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={InviteMemberModal.show}
>
- <i className='glyphicon glyphicon-user'></i>Invite New Member
+ <i className='fa fa-user'></i>Invite New Member
</a>
</li>
);
@@ -61,7 +61,7 @@ export default class SidebarRightMenu extends React.Component {
data-target='#get_link'
data-title='Team Invite'
data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id}
- ><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
+ ><i className='fa fa-link'></i>Get Team Invite Link</a>
</li>
);
}
@@ -74,7 +74,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='glyphicon glyphicon-globe'></i>Team Settings</a>
+ ><i className='fa fa-globe'></i>Team Settings</a>
</li>
);
manageLink = (
@@ -84,7 +84,7 @@ export default class SidebarRightMenu extends React.Component {
data-toggle='modal'
data-target='#team_members'
>
- <i className='glyphicon glyphicon-wrench'></i>Manage Members</a>
+ <i className='fa fa-users'></i>Manage Members</a>
</li>
);
}
@@ -95,7 +95,7 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='glyphicon glyphicon-wrench'></i>System Console</a>
+ <i className='fa fa-wrench'></i>System Console</a>
</li>
);
}
@@ -125,7 +125,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='glyphicon glyphicon-cog'></i>Account Settings
+ <i className='fa fa-cog'></i>Account Settings
</a>
</li>
{teamSettingsLink}
@@ -137,18 +137,18 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='glyphicon glyphicon-log-out'></i>Logout</a></li>
+ ><i className='fa fa-sign-out'></i>Logout</a></li>
<li className='divider'></li>
<li>
<a
target='_blank'
href='/static/help/configure_links.html'
- ><i className='glyphicon glyphicon-question-sign'></i>Help</a></li>
+ ><i className='fa fa-question'></i>Help</a></li>
<li>
<a
target='_blank'
href='/static/help/configure_links.html'
- ><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
+ ><i className='fa fa-phone'></i>Report a Problem</a></li>
</ul>
</div>
<UserSettingsModal
diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx
index e6530b941..1a5269baa 100644
--- a/web/react/components/textbox.jsx
+++ b/web/react/components/textbox.jsx
@@ -295,6 +295,13 @@ export default class Textbox extends React.Component {
this.resize();
}
+ showHelp(e) {
+ e.preventDefault();
+ e.target.blur();
+
+ global.window.open('/docs/Messaging');
+ }
+
render() {
const previewLinkVisible = this.props.messageText.length > 0;
@@ -336,11 +343,17 @@ export default class Textbox extends React.Component {
>
</div>
<a
+ onClick={this.showHelp}
+ className='textbox-help-link'
+ >
+ {'Help'}
+ </a>
+ <a
style={{visibility: previewLinkVisible ? 'visible' : 'hidden'}}
onClick={this.showPreview}
className='textbox-preview-link'
>
- {this.state.preview ? 'Edit message' : 'Preview'}
+ {this.state.preview ? 'Edit' : 'Preview'}
</a>
</div>
);
diff --git a/web/react/components/toggle_modal_button.jsx b/web/react/components/toggle_modal_button.jsx
new file mode 100644
index 000000000..51c8d1f20
--- /dev/null
+++ b/web/react/components/toggle_modal_button.jsx
@@ -0,0 +1,62 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+export default class ModalToggleButton extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.show = this.show.bind(this);
+ this.hide = this.hide.bind(this);
+
+ this.state = {
+ show: false
+ };
+ }
+
+ show() {
+ this.setState({show: true});
+ }
+
+ hide() {
+ this.setState({show: false});
+ }
+
+ render() {
+ const {children, dialogType, dialogProps, ...props} = this.props;
+
+ // this assumes that all modals will have a show property and an onHide event
+ const dialog = React.createElement(this.props.dialogType, Object.assign({}, dialogProps, {
+ show: this.state.show,
+ onHide: () => {
+ this.hide();
+
+ if (dialogProps.onHide) {
+ dialogProps.onHide();
+ }
+ }
+ }));
+
+ // nesting the dialog in the anchor tag looks like it shouldn't work, but it does due to how react-bootstrap
+ // renders modals at the top level of the DOM instead of where you specify in the virtual DOM
+ return (
+ <a
+ {...props}
+ href='#'
+ onClick={this.show}
+ >
+ {children}
+ {dialog}
+ </a>
+ );
+ }
+}
+
+ModalToggleButton.propTypes = {
+ children: React.PropTypes.node.isRequired,
+ dialogType: React.PropTypes.func.isRequired,
+ dialogProps: React.PropTypes.object
+};
+
+ModalToggleButton.defaultProps = {
+ dialogProps: {}
+};
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index 4dcf32cb9..776201295 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -10,6 +10,7 @@ export default class UserSettingsModal extends React.Component {
constructor(props) {
super(props);
+ this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
this.handleHidden = this.handleHidden.bind(this);
this.handleCollapse = this.handleCollapse.bind(this);
@@ -33,12 +34,22 @@ export default class UserSettingsModal extends React.Component {
this.requireConfirm = false;
}
+ componentDidMount() {
+ if (this.props.show) {
+ this.handleShow();
+ }
+ }
+
componentDidUpdate(prevProps) {
- if (!prevProps.show && this.props.show) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
- if ($(window).width() > 768) {
- $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
- }
+ if (this.props.show && !prevProps.show) {
+ this.handleShow();
+ }
+ }
+
+ handleShow() {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).css('max-height', $(window).height() - 300);
+ if ($(window).width() > 768) {
+ $(ReactDOM.findDOMNode(this.refs.modalBody)).perfectScrollbar();
}
}
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index 61d13ed8b..16ace0abc 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -5,6 +5,7 @@ var SettingItemMin = require('../setting_item_min.jsx');
var SettingItemMax = require('../setting_item_max.jsx');
var AccessHistoryModal = require('../access_history_modal.jsx');
var ActivityLogModal = require('../activity_log_modal.jsx');
+var ToggleModalButton = require('../toggle_modal_button.jsx');
var Client = require('../../utils/client.jsx');
var AsyncClient = require('../../utils/async_client.jsx');
var Constants = require('../../utils/constants.jsx');
@@ -13,34 +14,13 @@ export default class SecurityTab extends React.Component {
constructor(props) {
super(props);
- this.showAccessHistoryModal = this.showAccessHistoryModal.bind(this);
- this.showActivityLogModal = this.showActivityLogModal.bind(this);
- this.hideModals = this.hideModals.bind(this);
this.submitPassword = this.submitPassword.bind(this);
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
this.setupInitialState = this.setupInitialState.bind(this);
- const state = this.setupInitialState();
- state.showAccessHistoryModal = false;
- state.showActivityLogModal = false;
- this.state = state;
- }
- showAccessHistoryModal() {
- this.props.setEnforceFocus(false);
- this.setState({showAccessHistoryModal: true});
- }
- showActivityLogModal() {
- this.props.setEnforceFocus(false);
- this.setState({showActivityLogModal: true});
- }
- hideModals() {
- this.props.setEnforceFocus(true);
- this.setState({
- showAccessHistoryModal: false,
- showActivityLogModal: false
- });
+ this.state = this.setupInitialState();
}
submitPassword(e) {
e.preventDefault();
@@ -258,30 +238,20 @@ export default class SecurityTab extends React.Component {
{passwordSection}
<div className='divider-dark'/>
<br></br>
- <a
+ <ToggleModalButton
className='security-links theme'
- href='#'
- onClick={this.showAccessHistoryModal}
+ dialogType={AccessHistoryModal}
>
<i className='fa fa-clock-o'></i>View Access History
- </a>
+ </ToggleModalButton>
<b> </b>
- <a
+ <ToggleModalButton
className='security-links theme'
- href='#'
- onClick={this.showActivityLogModal}
+ dialogType={ActivityLogModal}
>
- <i className='fa fa-globe'></i>View and Logout of Active Sessions
- </a>
+ <i className='fa fa-clock-o'></i>{'View and Logout of Active Sessions'}
+ </ToggleModalButton>
</div>
- <AccessHistoryModal
- show={this.state.showAccessHistoryModal}
- onModalDismissed={this.hideModals}
- />
- <ActivityLogModal
- show={this.state.showActivityLogModal}
- onModalDismissed={this.hideModals}
- />
</div>
);
}
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 8781d52a5..9aba1a564 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -10,16 +10,13 @@ var ErrorStore = require('../stores/error_store.jsx');
var MentionList = require('../components/mention_list.jsx');
var GetLinkModal = require('../components/get_link_modal.jsx');
var EditChannelModal = require('../components/edit_channel_modal.jsx');
-var DeleteChannelModal = require('../components/delete_channel_modal.jsx');
var RenameChannelModal = require('../components/rename_channel_modal.jsx');
var EditPostModal = require('../components/edit_post_modal.jsx');
var DeletePostModal = require('../components/delete_post_modal.jsx');
var MoreChannelsModal = require('../components/more_channels.jsx');
var PostDeletedModal = require('../components/post_deleted_modal.jsx');
-var ChannelNotificationsModal = require('../components/channel_notifications.jsx');
var TeamSettingsModal = require('../components/team_settings_modal.jsx');
var TeamMembersModal = require('../components/team_members.jsx');
-var ChannelInfoModal = require('../components/channel_info_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx');
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
@@ -103,26 +100,11 @@ function setupChannelPage(props) {
);
ReactDOM.render(
- <DeleteChannelModal />,
- document.getElementById('delete_channel_modal')
- );
-
- ReactDOM.render(
<RenameChannelModal />,
document.getElementById('rename_channel_modal')
);
ReactDOM.render(
- <ChannelNotificationsModal />,
- document.getElementById('channel_notifications_modal')
- );
-
- ReactDOM.render(
- <ChannelInfoModal />,
- document.getElementById('channel_info_modal')
- );
-
- ReactDOM.render(
<MoreChannelsModal />,
document.getElementById('more_channels_modal')
);
diff --git a/web/react/pages/docs.jsx b/web/react/pages/docs.jsx
new file mode 100644
index 000000000..ed2b6d0c9
--- /dev/null
+++ b/web/react/pages/docs.jsx
@@ -0,0 +1,16 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Docs = require('../components/docs.jsx');
+
+function setupDocumentationPage(props) {
+ ReactDOM.render(
+ <Docs
+ site={props.Site}
+ />,
+ document.getElementById('docs')
+ );
+}
+
+global.window.mm_user = global.window.mm_user || {};
+global.window.setup_documentation_page = setupDocumentationPage;
diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx
index 8e86ce32f..2e3a26cff 100644
--- a/web/react/stores/browser_store.jsx
+++ b/web/react/stores/browser_store.jsx
@@ -72,7 +72,7 @@ class BrowserStoreClass {
console.log('An error occurred while setting local storage, clearing all props'); //eslint-disable-line no-console
localStorage.clear();
sessionStorage.clear();
- window.location.href = window.location.href;
+ window.location.reload(true);
}
}
diff --git a/web/react/stores/modal_store.jsx b/web/react/stores/modal_store.jsx
index dc65d48da..809f83a59 100644
--- a/web/react/stores/modal_store.jsx
+++ b/web/react/stores/modal_store.jsx
@@ -27,12 +27,14 @@ class ModalStoreClass extends EventEmitter {
}
handleEventPayload(payload) {
- const action = payload.action;
+ // toggle event handlers should accept a boolean show/hide value and can accept a map of arguments
+ const {type, value, ...args} = payload.action;
- switch (action.type) {
+ switch (type) {
case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
- this.emit(action.type, action.value);
+ case ActionTypes.TOGGLE_DELETE_POST_MODAL:
+ this.emit(type, value, args);
break;
}
}
diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx
index 0fe253310..a564a2435 100644
--- a/web/react/stores/post_store.jsx
+++ b/web/react/stores/post_store.jsx
@@ -40,6 +40,7 @@ class PostStoreClass extends EventEmitter {
this.storePosts = this.storePosts.bind(this);
this.pStorePosts = this.pStorePosts.bind(this);
this.getPosts = this.getPosts.bind(this);
+ this.getPost = this.getPost.bind(this);
this.storePost = this.storePost.bind(this);
this.pStorePost = this.pStorePost.bind(this);
this.removePost = this.removePost.bind(this);
@@ -68,6 +69,7 @@ class PostStoreClass extends EventEmitter {
this.storeLatestUpdate = this.storeLatestUpdate.bind(this);
this.getLatestUpdate = this.getLatestUpdate.bind(this);
this.getCurrentUsersLatestPost = this.getCurrentUsersLatestPost.bind(this);
+ this.getCommentCount = this.getCommentCount.bind(this);
}
emitChange() {
this.emit(CHANGE_EVENT);
@@ -193,6 +195,9 @@ class PostStoreClass extends EventEmitter {
getPosts(channelId) {
return BrowserStore.getItem('posts_' + channelId);
}
+ getPost(channelId, postId) {
+ return this.getPosts(channelId).posts[postId];
+ }
getCurrentUsersLatestPost(channelId, rootId) {
const userId = UserStore.getCurrentId();
var postList = makePostListNonNull(this.getPosts(channelId));
@@ -402,6 +407,20 @@ class PostStoreClass extends EventEmitter {
getLatestUpdate(channelId) {
return BrowserStore.getItem('latest_post_' + channelId, 0);
}
+ getCommentCount(post) {
+ const posts = this.getPosts(post.channel_id).posts;
+
+ let commentCount = 0;
+ for (let id in posts) {
+ if (posts.hasOwnProperty(id)) {
+ if (posts[id].root_id === post.id) {
+ commentCount += 1;
+ }
+ }
+ }
+
+ return commentCount;
+ }
}
var PostStore = new PostStoreClass();
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 205c7461c..b39648bf0 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -63,7 +63,7 @@ export function getChannels(force, updateLastViewed, checkVersion) {
if (serverVersion !== BrowserStore.getLastServerVersion()) {
BrowserStore.setLastServerVersion(serverVersion);
- window.location.href = window.location.href;
+ window.location.reload(true);
console.log('Detected version update refreshing the page'); //eslint-disable-line no-console
}
}
diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx
index f27e23a82..11b69e28f 100644
--- a/web/react/utils/channel_intro_mssages.jsx
+++ b/web/react/utils/channel_intro_mssages.jsx
@@ -92,6 +92,7 @@ export function createOffTopicIntroMessage(channel, showInviteModal) {
</a>
<a
href='#'
+ className='intro-links'
onClick={showInviteModal}
>
<i className='fa fa-user-plus'></i>{'Invite others to this channel'}
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 58ee8e2d2..6be87254f 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -40,7 +40,8 @@ module.exports = {
RECIEVED_ALL_TEAMS: null,
TOGGLE_IMPORT_THEME_MODAL: null,
- TOGGLE_INVITE_MEMBER_MODAL: null
+ TOGGLE_INVITE_MEMBER_MODAL: null,
+ TOGGLE_DELETE_POST_MODAL: null
}),
PayloadSources: keyMirror({
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 6f3924829..5af837822 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -616,7 +616,7 @@ export function applyTheme(theme) {
if (theme.centerChannelColor) {
changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name, .tip-overlay', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
- changeCss('.channel-header__links a', 'fill:' + changeOpacity(theme.centerChannelColor, 0.7), 1);
+ changeCss('.channel-header__links a', 'fill:' + changeOpacity(theme.centerChannelColor, 0.9), 1);
changeCss('.channel-header__links a:hover, .channel-header__links a:active', 'fill:' + theme.centerChannelColor, 2);
changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
@@ -644,7 +644,7 @@ export function applyTheme(theme) {
changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2);
changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1);
changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
+ changeCss('.form-control:focus, .channel-header__links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro .channel-intro__content, .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
@@ -656,7 +656,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
- changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post:hover, .channel-header__links a:hover, .channel-header__links a:active, .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, .mentions-focus, .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);
@@ -1232,3 +1232,12 @@ export function getChannelTerm(channelType) {
return channelTerm;
}
+
+export function getPostTerm(post) {
+ let postTerm = 'Post';
+ if (post.root_id) {
+ postTerm = 'Comment';
+ }
+
+ return postTerm;
+}
diff --git a/web/sass-files/sass/partials/_content.scss b/web/sass-files/sass/partials/_content.scss
index 6228cc45e..44a959a9b 100644
--- a/web/sass-files/sass/partials/_content.scss
+++ b/web/sass-files/sass/partials/_content.scss
@@ -20,7 +20,6 @@
background: #fff;
@include display-flex;
@include flex-direction(column);
- flex-direction: column;
.channel__wrap & {
padding-top: 0;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 49fb8e847..168634b5e 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -1,9 +1,9 @@
.preview-container {
position: relative;
- margin-top: 10px;
+ margin-top: 25px;
width: 100%;
- max-height: 110px;
- height: 110px;
+ max-height: 100px;
+ height: 100px;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
@@ -11,7 +11,7 @@
display: inline-block;
width: 120px;
height: 100px;
- margin: 7px 0 0 5px;
+ margin: 0 0 0 5px;
vertical-align: top;
position: relative;
border: 1px solid #DDD;
diff --git a/web/sass-files/sass/partials/_forms.scss b/web/sass-files/sass/partials/_forms.scss
index 2d7b6cd26..685677ad0 100644
--- a/web/sass-files/sass/partials/_forms.scss
+++ b/web/sass-files/sass/partials/_forms.scss
@@ -43,3 +43,7 @@
margin: 10px 0 0;
color: #999;
}
+
+.disabled-input {
+ background-color: #dddddd !important;
+}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 67c938b8c..69bc56841 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -1,8 +1,5 @@
#channel-header {
- padding: 3px 0;
- height: 58px;
- flex: 0 0 58px;
- @include flex(0 0 50px);
+ @include flex(0 0 56px);
}
.row {
&.header {
@@ -45,9 +42,9 @@
text-overflow: ellipsis;
margin-top: 2px;
max-height: 45px;
- .markdown-inline-img {
- max-height: 45px
- }
+ .markdown-inline-img {
+ max-height: 45px
+ }
}
&.popover {
white-space: normal;
@@ -97,7 +94,7 @@
.sidebar--left, .sidebar--menu {
.team__header {
position: relative;
- padding: 10px;
+ padding: 9px 10px;
@include legacy-pie-clearfix;
&:before {
@include single-transition(all, 0.05s, linear);
@@ -125,7 +122,7 @@
.navbar-right {
font-size: 0.85em;
position: absolute;
- top: 11px;
+ top: 10px;
right: 22px;
z-index: 5;
.dropdown-toggle {
@@ -217,7 +214,7 @@
width:100%;
border-left: none;
font-size: 14px;
- line-height: 50px;
+ line-height: 56px;
#member_popover {
margin-right: 5px;
width: 45px;
@@ -296,17 +293,19 @@
.channel-header__links {
height: 32px;
- vertical-align: top;
- display: inline-block;
- width: 15px;
- margin: 9px 6px 3px 0;
- a {
+ width: 32px;
+ @include border-radius(50px);
+ border: 1px solid #ccc;
+ margin-right: 10px;
+ > a {
+ @include border-radius(50px);
height: 100%;
display: block;
+ @include single-transition(all, 0.1s, ease-in);
}
svg {
vertical-align: top;
- margin-top: 8px;
+ margin: 7px 0 0 0px;
fill: inherit;
}
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 45b7b7f23..fad6f5074 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -47,20 +47,25 @@ body.ios {
.textarea-wrapper {
position:relative;
- .textbox-preview-area {
- position: absolute;
- z-index: 2;
- top: 0;
- left: 0;
- box-shadow: none;
- }
- .textbox-preview-link {
- position: absolute;
- z-index: 3;
- bottom: -23px;
- right: 0;
+ .textbox-preview-area {
+ position: absolute;
+ z-index: 2;
+ top: 0;
+ left: 0;
+ box-shadow: none;
+ }
+ .textbox-preview-link, .textbox-help-link {
+ position: absolute;
+ z-index: 3;
+ bottom: -23px;
font-size: 13px;
- cursor: pointer;
+ cursor: pointer;
+ }
+ .textbox-preview-link {
+ right: 45px;
+ }
+ .textbox-help-link {
+ right: 0;
}
min-height:36px;
}
@@ -332,18 +337,19 @@ body.ios {
}
.post-create-footer {
@include clearfix;
- padding: 0;
+ padding: 3px 0 0 0;
+ font-size: 13px;
.has-error {
.control-label {
+ height: 0;
+ display: block;
font-weight: normal;
margin-bottom: 0;
}
}
.msg-typing {
min-height: 25px;
- line-height: 25px;
display: block;
- font-size: 13px;
@include opacity(0.7);
}
}
@@ -392,6 +398,15 @@ body.ios {
.post-body {
@include border-radius(0 4px 4px 0);
}
+ .post-body {
+ border-left: 4px solid #EEE;
+ width: 570px;
+ margin-left: 30px;
+ padding-left: 10px;
+ .post-link {
+ display: none;
+ }
+ }
}
}
&.same--root {
@@ -407,19 +422,15 @@ body.ios {
.post__content {
padding: 0;
}
- .post-body {
- border-left: 4px solid #EEE;
- width: 570px;
- margin-left: 30px;
- padding-left: 10px;
- .post-link {
- display: none;
+ &.same--user {
+ .post__content {
+ padding-left: 46px;
+ }
+ .post-header-post {
+ visibility: hidden;
}
}
}
- .post-create-footer {
- padding: 0;
- }
p {
margin: 0 0 1em;
line-height: 1.6em;
@@ -545,13 +556,14 @@ body.ios {
}
&.post-info {
.post-profile-time {
- color: #a8adb7;
vertical-align: top;
+ width: 150px;
max-width: 220px;
overflow: hidden;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
+ margin: 0 0 0 50px;
}
}
.post-header-col {
@@ -577,7 +589,7 @@ body.ios {
}
}
.post-profile-time {
- color: #a8adb7;
+ @include opacity(0.5);
}
}
.post-comment {
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index ba41d3b95..54c3bcdf8 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -1,4 +1,7 @@
.post-right__container {
+ @include display-flex;
+ @include flex-direction(column);
+ height: 100%;
.post-right-root-message {
padding: 1em 1em 0;
@@ -19,6 +22,15 @@
margin: 1em 0 0 0;
}
}
+ .post-header {
+ .post-profile-time {
+ width: 200px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
}
.post-create__container {
@@ -44,7 +56,7 @@
}
.post-create-footer {
width: 100%;
- padding-top: 5px;
+ padding: 5px 0 10px;
}
.post-right-comments-upload-in-progress {
padding: 6px 0;
@@ -103,8 +115,9 @@
.post-right__scroll {
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
+ @include flex(1 1 auto);
}
.post-right-comment-time {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 37626e303..aad991035 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -9,25 +9,26 @@
}
}
.post {
- &.same--root {
- margin-left: 60px;
- padding-left: 10px;
- border-left: 4px solid #EEE;
- div.post-profile-img__container {
- .post-profile-img {
- display: none;
- }
- }
- .post__content {
- width: 825px;
- }
- .post-body {
- width: 736px;
- border: none;
- margin: 3px 0 0;
- }
- }
&.post--comment {
+ &.same--root {
+ margin-left: 104px;
+ padding-left: 10px;
+ border-left: 4px solid #EEE;
+ div.post-profile-img__container {
+ .post-profile-img {
+ display: none;
+ }
+ }
+ .post__content {
+ width: 825px;
+ margin-left: 0;
+ }
+ .post-body {
+ width: 736px;
+ border: none;
+ margin: 3px 0 0;
+ }
+ }
&.other--root {
.post-comment {
margin-left: 0;
@@ -105,34 +106,61 @@
}
}
.post {
- &.same--root {
- margin-left: 60px;
- padding-left: 10px;
- border-left: 4px solid #EEE;
- div.post-profile-img__container {
- .post-profile-img {
- display: none;
- }
- }
- .post__content {
- width: 825px;
- }
- .post-body {
- width: 736px;
- border: none;
- margin: 3px 0 0;
- }
- }
+ &.same--root.same--user {
+ .post-header-post {
+ visibility: hidden;
+ width: 100%;
+ position: relative;
+ top: -5px;
+ .post-header-col.post-header__name {
+ display: none;
+ }
+ }
+ .post-body {
+ top: -15px;
+ margin-bottom: -10px;
+ }
+ &:hover .post-header-post {
+ visibility: visible;
+ }
+ }
+
&.post--comment {
&.other--root {
.post-comment {
margin-left: 0;
}
}
- &.same--root {
- margin-top: 5px;
- margin-bottom: 5px;
- }
+ &.same--root {
+ margin-top: 5px;
+ margin-bottom: 5px;
+ margin-left: 104px;
+ padding-left: 10px;
+ border-left: 4px solid #EEE;
+ div.post-profile-img__container {
+ .post-profile-img {
+ display: none;
+ }
+ }
+ .post-body {
+ margin-left: 0;
+ border-left: 0;
+ }
+ &.same--user {
+ .post__content {
+ margin-left: 0;
+ padding-left: 0;
+ }
+ }
+ }
+ .post__content {
+ width: 810px;
+ }
+ .post-body {
+ width: 736px;
+ border: none;
+ margin: 3px 0 0;
+ }
}
.post__content {
width: 880px;
@@ -275,6 +303,11 @@
}
}
@media screen and (max-width: 768px) {
+ .textarea-wrapper {
+ .textbox-preview-link {
+ display: none;
+ }
+ }
.form-control {
&.min-height {
min-height: 100px;
@@ -353,9 +386,6 @@
}
}
.post {
- &.same--root {
- margin-left: 25px;
- }
&:hover {
background: none;
.post-header .post-header-col.post-header__reply {
@@ -365,7 +395,11 @@
}
}
&.post--comment {
+ &.same--root {
+ margin-left: 25px;
+ }
&.other--root {
+ margin-left: 0;
&:hover {
background: none;
}
@@ -376,6 +410,9 @@
content: '...';
}
}
+ &.same--root.same--user .post__content{
+ padding-left: 0;
+ }
}
.signup-team__container {
padding: 30px 0;
@@ -503,17 +540,22 @@
.post-create__container {
.post-right__container & {
padding: 0 1em;
+ .msg-typing {
+ display: none;
+ }
}
form {
- padding: 0;
+ padding: 0.5em 0;
}
.post-create-footer {
+ padding: 0 1em;
.msg-typing {
- margin-left: 45px;
- width: 55%;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
+ display: none;
+ }
+ .has-error {
+ .control-label {
+ height: auto;
+ }
}
}
.post-create-body {
@@ -542,8 +584,8 @@
}
}
.preview-container {
- padding: 0px 10px;
- margin-top: 20px;
+ padding: 0px;
+ margin-top: 5px;
.preview-div {
margin-top: 0;
}
@@ -616,7 +658,7 @@
}
.search-bar__container {
padding: 0;
- height: 45px;
+ @include flex(0 0 44px);
background: $primary-color;
color: #fff;
&.focused {
@@ -745,6 +787,12 @@
.sidebar--right__close {
display: none;
}
+ .sidebar-right__body {
+ height: calc(100% - 44px);
+ }
+ }
+ .search-items-container {
+ height: calc(100% - 44px);
}
.inner__wrap {
&.move--right {
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 73b69c866..bedf35376 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -1,8 +1,9 @@
#channel-header .search-bar__container {
- padding: 8px 8px 8px 0;
+ padding: 0 8px 0 0;
}
.search-bar__container {
- padding: 12px 8px 12px 0;
+ padding: 12px 8px 0 0;
+ @include flex(0 0 56px);
}
.search__clear {
display: none;
@@ -71,8 +72,10 @@
.search-items-container {
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
+ @include flex(1 1 auto);
+ height: calc(100% - 56px);
}
.search-results-header {
@@ -92,9 +95,11 @@
padding: 10px 1em;
margin: 0;
cursor: pointer;
+
&:first-child {
border: none;
}
+
.search-channel__name {
font-weight: 600;
margin: 0 0 10px 0;
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index ab13d1b42..eb7c1b83f 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -47,7 +47,7 @@
.nav-pills__container {
height: 100%;
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
}
diff --git a/web/sass-files/sass/partials/_sidebar--menu.scss b/web/sass-files/sass/partials/_sidebar--menu.scss
index 6f4a0cc38..e34cd72c1 100644
--- a/web/sass-files/sass/partials/_sidebar--menu.scss
+++ b/web/sass-files/sass/partials/_sidebar--menu.scss
@@ -57,8 +57,11 @@
color: inherit;
line-height: 40px;
padding: 0 15px;
- .glyphicon {
+ .fa ,.glyphicon {
width: 25px;
+ text-align: center;
+ left: -5px;
+ position: relative;
}
}
}
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index a4267294c..2527eef28 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -3,24 +3,38 @@
width: 400px;
height: 100%;
right: 0px;
- padding: 0 0 2em 0;
+ padding: 0;
background: #fff;
@include single-transition(transform, 0.5s, ease);
right: -320px;
+
&.move--left {
right: 0;
}
+
+ .sidebar--right__content {
+ height: 100%;
+ @include display-flex;
+ @include flex-direction(column);
+ }
+
.sidebar--right__back {
- color: #666;
- width: 20px;
+ color: inherit;
+ @include opacity(0.8);
+ width: 30px;
text-align: center;
- margin: 0 0 0 -6px;
- font-size: 12px;
+ margin: 0 0 0 -14px;
+ font-size: 13px;
display: inline-block;
}
.sidebar-right__body {
+ @include flex(1 1 auto);
border-left: $border-gray;
border-top: $border-gray;
+ @include display-flex;
+ @include flex-direction(column);
+ height: calc(100% - 56px);
+ @include border-radius(2px 0 0 0);
}
.post {
.post-header {
@@ -75,7 +89,7 @@
height: 44px;
padding: 0 1em;
line-height: 44px;
- background: #F5F5F5;
+ @include flex(0 0 44px);
border-bottom: $border-gray;
}
.sidebar--right__subheader {
diff --git a/web/static/help/Messaging.md b/web/static/help/Messaging.md
new file mode 120000
index 000000000..f74c0b879
--- /dev/null
+++ b/web/static/help/Messaging.md
@@ -0,0 +1 @@
+../../../doc/help/Messaging.md \ No newline at end of file
diff --git a/web/templates/docs.html b/web/templates/docs.html
new file mode 100644
index 000000000..21659e810
--- /dev/null
+++ b/web/templates/docs.html
@@ -0,0 +1,24 @@
+{{define "docs"}}
+<!DOCTYPE html>
+<html>
+{{template "head" . }}
+<body class="white">
+<div class="container-fluid">
+ <div class="inner__wrap">
+ <div class="row content">
+ <div class="col-sm-12">
+ <div id="docs"></div>
+ </div>
+ <div class="footer-push"></div>
+ </div>
+ <div class="row footer">
+ {{template "footer" . }}
+ </div>
+ </div>
+</div>
+<script>
+ window.setup_documentation_page({{ .Props }});
+</script>
+</body>
+</html>
+{{end}}
diff --git a/web/web.go b/web/web.go
index 02ceb69ba..477bd8b27 100644
--- a/web/web.go
+++ b/web/web.go
@@ -80,6 +80,8 @@ func InitWeb() {
mainrouter.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiAppHandler(incomingWebhook)).Methods("POST")
+ mainrouter.Handle("/docs/{doc:[A-Za-z0-9]+}", api.AppHandlerIndependent(docs)).Methods("GET")
+
// ----------------------------------------------------------------------------------------------
// *ANYTHING* team specific should go below this line
// ----------------------------------------------------------------------------------------------
@@ -494,6 +496,15 @@ func findTeam(c *api.Context, w http.ResponseWriter, r *http.Request) {
page.Render(c, w)
}
+func docs(c *api.Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ doc := params["doc"]
+
+ page := NewHtmlTemplatePage("docs", "Documentation")
+ page.Props["Site"] = doc
+ page.Render(c, w)
+}
+
func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) {
isResetLink := true
hash := r.URL.Query().Get("h")