diff options
Diffstat (limited to 'web')
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'>×</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'>×</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'>×</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'>×</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'>×</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'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") |