diff options
author | hmhealey <harrisonmhealey@gmail.com> | 2015-11-16 12:08:05 -0500 |
---|---|---|
committer | hmhealey <harrisonmhealey@gmail.com> | 2015-11-23 10:53:06 -0500 |
commit | 6237631a85b79311a60b87df423abbdce56c7876 (patch) | |
tree | 2fe385ee6258be1d9f83cc737fd51651359ff48c /web | |
parent | 60e2314baaa17fe972f13e6f763e08e03e356c8a (diff) | |
download | chat-6237631a85b79311a60b87df423abbdce56c7876.tar.gz chat-6237631a85b79311a60b87df423abbdce56c7876.tar.bz2 chat-6237631a85b79311a60b87df423abbdce56c7876.zip |
Ported EditChannelModal to React-Bootstrap
Diffstat (limited to 'web')
-rw-r--r-- | web/react/components/channel_header.jsx | 27 | ||||
-rw-r--r-- | web/react/components/edit_channel_modal.jsx | 174 | ||||
-rw-r--r-- | web/react/components/navbar.jsx | 56 | ||||
-rw-r--r-- | web/react/components/toggle_modal_button.jsx | 17 | ||||
-rw-r--r-- | web/react/pages/channel.jsx | 6 | ||||
-rw-r--r-- | web/react/utils/channel_intro_mssages.jsx | 51 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 17 |
7 files changed, 161 insertions, 187 deletions
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 8c721348f..06d5db8b0 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -4,6 +4,7 @@ import NavbarSearchBox from './search_bar.jsx'; import MessageWrapper from './message_wrapper.jsx'; import PopoverListMembers from './popover_list_members.jsx'; +import EditChannelModal from './edit_channel_modal.jsx'; import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx'; import ChannelInfoModal from './channel_info_modal.jsx'; import ChannelInviteModal from './channel_invite_modal.jsx'; @@ -167,17 +168,13 @@ export default class ChannelHeader extends React.Component { key='edit_header_direct' role='presentation' > - <a + <ToggleModalButton role='menuitem' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > {'Set Channel Header...'} - </a> + </ToggleModalButton> </li> ); } else { @@ -235,17 +232,13 @@ export default class ChannelHeader extends React.Component { key='set_channel_header' role='presentation' > - <a + <ToggleModalButton role='menuitem' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > - {'Set '}{channelTerm}{' Header...'} - </a> + {`Set ${channelTerm} Header...`} + </ToggleModalButton> </li> ); dropdownContents.push( diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 80dab4a57..487ae6964 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -3,39 +3,51 @@ import * as Client from '../utils/client.jsx'; import * as AsyncClient from '../utils/async_client.jsx'; +import * as Utils from '../utils/utils.jsx'; + +const Modal = ReactBootstrap.Modal; export default class EditChannelModal extends React.Component { constructor(props) { super(props); this.handleEdit = this.handleEdit.bind(this); - this.handleUserInput = this.handleUserInput.bind(this); - this.handleClose = this.handleClose.bind(this); + this.onShow = this.onShow.bind(this); - this.handleShown = this.handleShown.bind(this); + this.onHide = this.onHide.bind(this); this.state = { - header: '', - title: '', - channelId: '', serverError: '' }; } + + componentDidMount() { + if (this.props.show) { + this.onShow(); + } + } + + componentDidUpdate(prevProps) { + if (this.props.show && !prevProps.show) { + this.onShow(); + } + } + handleEdit() { var data = {}; - data.channel_id = this.state.channelId; + data.channel_id = this.props.channel.id; if (data.channel_id.length !== 26) { return; } - data.channel_header = this.state.header.trim(); + data.channel_header = ReactDOM.findDOMNode(this.refs.textarea).value; Client.updateChannelHeader(data, () => { this.setState({serverError: ''}); - AsyncClient.getChannel(this.state.channelId); - $(ReactDOM.findDOMNode(this.refs.modal)).modal('hide'); + AsyncClient.getChannel(this.props.channel.id); + this.onHide(); }, (err) => { if (err.message === 'Invalid channel_header parameter') { @@ -46,105 +58,69 @@ export default class EditChannelModal extends React.Component { } ); } - handleUserInput(e) { - this.setState({header: e.target.value}); - } - handleClose() { - this.setState({header: '', serverError: ''}); - } - onShow(e) { - const button = e.relatedTarget; - this.setState({header: $(button).attr('data-header'), title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), serverError: ''}); - } - handleShown() { - $('#edit_channel #edit_header').focus(); - } - componentDidMount() { - $(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', this.onShow); - $(ReactDOM.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); - $(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', this.handleShown); + + onShow() { + const textarea = ReactDOM.findDOMNode(this.refs.textarea); + Utils.placeCaretAtEnd(textarea); } - componentWillUnmount() { - $(ReactDOM.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); + + onHide() { + this.setState({ + serverError: '' + }); + + this.props.onHide(); } + render() { var serverError = null; if (this.state.serverError) { serverError = <div className='form-group has-error'><br/><label className='control-label'>{this.state.serverError}</label></div>; } - var editTitle = ( - <h4 - className='modal-title' - ref='title' - > - {'Edit Header'} - </h4> - ); - if (this.state.title) { - editTitle = ( - <h4 - className='modal-title' - ref='title' - > - {'Edit Header for '}<span className='name'>{this.state.title}</span> - </h4> - ); - } - return ( - <div - className='modal fade' - ref='modal' - id='edit_channel' - role='dialog' - tabIndex='-1' - aria-hidden='true' + <Modal + show={this.props.show} + onHide={this.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> - {editTitle} - </div> - <div className='modal-body'> - <p>{'Edit the text appearing next to the channel name in the channel header.'}</p> - <textarea - className='form-control no-resize' - rows='6' - id='edit_header' - maxLength='1024' - value={this.state.header} - onChange={this.handleUserInput} - /> - {serverError} - </div> - <div className='modal-footer'> - <button - type='button' - className='btn btn-default' - data-dismiss='modal' - > - {'Cancel'} - </button> - <button - type='button' - className='btn btn-primary' - onClick={this.handleEdit} - > - {'Save'} - </button> - </div> - </div> - </div> - </div> + <Modal.Header closeButton={true}> + {'Edit Header for ' + this.props.channel.display_name} + </Modal.Header> + <Modal.Body> + <p>{'Edit the text appearing next to the channel name in the channel header.'}</p> + <textarea + ref='textarea' + className='form-control no-resize' + rows='6' + id='edit_header' + maxLength='1024' + defaultValue={this.props.channel.header} + /> + {serverError} + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.props.onHide} + > + {'Cancel'} + </button> + <button + type='button' + className='btn btn-primary' + onClick={this.handleEdit} + > + {'Save'} + </button> + </Modal.Footer> + </Modal> ); } } + +EditChannelModal.propTypes = { + show: React.PropTypes.bool.isRequired, + onHide: React.PropTypes.func.isRequired, + channel: React.PropTypes.object.isRequired +}; diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 6848ee5da..8d3d779f3 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +import EditChannelModal from './edit_channel_modal.jsx'; import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx'; import MessageWrapper from './message_wrapper.jsx'; import NotifyCounts from './notify_counts.jsx'; @@ -33,11 +34,15 @@ export default class Navbar extends React.Component { this.onChange = this.onChange.bind(this); this.handleLeave = this.handleLeave.bind(this); this.showSearch = this.showSearch.bind(this); + + this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); + this.createCollapseButtons = this.createCollapseButtons.bind(this); this.createDropdown = this.createDropdown.bind(this); const state = this.getStateFromStores(); state.showEditChannelPurposeModal = false; + state.showEditChannelHeaderModal = false; state.showMembersModal = false; state.showInviteModal = false; this.state = state; @@ -110,6 +115,16 @@ export default class Navbar extends React.Component { this.setState(this.getStateFromStores()); $('#navbar .navbar-brand .description').popover({placement: 'bottom', trigger: 'click', html: true}); } + showEditChannelHeaderModal() { + // this can't be done using a ToggleModalButton because we can't use one inside an OverlayTrigger + if (this.refs.headerOverlay) { + this.refs.headerOverlay.hide(); + } + + this.setState({ + showEditChannelHeaderModal: true + }); + } createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent) { if (channel) { var viewInfoOption = ( @@ -129,11 +144,7 @@ export default class Navbar extends React.Component { <a role='menuitem' href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + onClick={this.showEditChannelHeaderModal} > {'Set Channel Header...'} </a> @@ -239,7 +250,7 @@ export default class Navbar extends React.Component { dialogType={ChannelNotificationsModal} dialogProps={{channel}} > - {'Notification Preferences'} + {'Notification Preferences'} </ToggleModalButton> </li> ); @@ -249,6 +260,7 @@ export default class Navbar extends React.Component { <div className='navbar-brand'> <div className='dropdown'> <OverlayTrigger + ref='headerOverlay' trigger='click' placement='bottom' overlay={popoverContent} @@ -358,6 +370,9 @@ export default class Navbar extends React.Component { var isAdmin = false; var isDirect = false; + var editChannelHeaderModal = null; + var editChannelPurposeModal = null; + if (channel) { popoverContent = ( <Popover @@ -400,11 +415,7 @@ export default class Navbar extends React.Component { <br/> <a href='#' - data-toggle='modal' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} - data-target='#edit_channel' + onClick={this.showEditChannelHeaderModal} > {'Click here'} </a> @@ -413,6 +424,22 @@ export default class Navbar extends React.Component { </Popover> ); } + + editChannelHeaderModal = ( + <EditChannelModal + show={this.state.showEditChannelHeaderModal} + onHide={() => this.setState({showEditChannelHeaderModal: false})} + channel={channel} + /> + ); + + editChannelPurposeModal = ( + <EditChannelPurposeModal + show={this.state.showEditChannelPurposeModal} + onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})} + channel={channel} + /> + ); } var collapseButtons = this.createCollapseButtons(currentId); @@ -443,11 +470,8 @@ export default class Navbar extends React.Component { </div> </div> </nav> - <EditChannelPurposeModal - show={this.state.showEditChannelPurposeModal} - onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})} - channel={channel} - /> + {editChannelHeaderModal} + {editChannelPurposeModal} <ChannelMembersModal show={this.state.showMembersModal} onModalDismissed={() => this.setState({showMembersModal: false})} diff --git a/web/react/components/toggle_modal_button.jsx b/web/react/components/toggle_modal_button.jsx index eae4a024d..ce8ff3f60 100644 --- a/web/react/components/toggle_modal_button.jsx +++ b/web/react/components/toggle_modal_button.jsx @@ -22,7 +22,17 @@ export default class ModalToggleButton extends React.Component { } render() { - const {children, dialogType, dialogProps, ...props} = this.props; //eslint-disable-line no-redeclare + const {children, dialogType, dialogProps, onClick, ...props} = this.props; // eslint-disable-line no-redeclare + + // allow callers to provide an onClick which will be called before the modal is shown + let clickHandler = this.show; + if (onClick) { + clickHandler = () => { + onClick(); + + this.show(); + }; + } // this assumes that all modals will have a show property and an onHide event const dialog = React.createElement(this.props.dialogType, Object.assign({}, dialogProps, { @@ -42,7 +52,7 @@ export default class ModalToggleButton extends React.Component { <a {...props} href='#' - onClick={this.show} + onClick={clickHandler} > {children} {dialog} @@ -54,7 +64,8 @@ export default class ModalToggleButton extends React.Component { ModalToggleButton.propTypes = { children: React.PropTypes.node.isRequired, dialogType: React.PropTypes.func.isRequired, - dialogProps: React.PropTypes.object + dialogProps: React.PropTypes.object, + onClick: React.PropTypes.func }; ModalToggleButton.defaultProps = { diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 5cc1be741..1c0d873d5 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -8,7 +8,6 @@ import ErrorStore from '../stores/error_store.jsx'; import MentionList from '../components/mention_list.jsx'; import GetLinkModal from '../components/get_link_modal.jsx'; -import EditChannelModal from '../components/edit_channel_modal.jsx'; import RenameChannelModal from '../components/rename_channel_modal.jsx'; import EditPostModal from '../components/edit_post_modal.jsx'; import DeletePostModal from '../components/delete_post_modal.jsx'; @@ -93,11 +92,6 @@ function setupChannelPage(props, team, channel) { ); ReactDOM.render( - <EditChannelModal />, - document.getElementById('edit_channel_modal') - ); - - ReactDOM.render( <RenameChannelModal />, document.getElementById('rename_channel_modal') ); diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx index 0bbc7366e..4047ec028 100644 --- a/web/react/utils/channel_intro_mssages.jsx +++ b/web/react/utils/channel_intro_mssages.jsx @@ -1,9 +1,10 @@ - // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. import * as Utils from './utils.jsx'; +import EditChannelModal from '../components/edit_channel_modal.jsx'; import InviteMemberModal from '../components/invite_member_modal.jsx'; +import ToggleModalButton from '../components/toggle_modal_button.jsx'; import UserProfile from '../components/user_profile.jsx'; import ChannelStore from '../stores/channel_store.jsx'; import Constants from '../utils/constants.jsx'; @@ -49,17 +50,13 @@ export function createDMIntroMessage(channel) { {'This is the start of your direct message history with ' + teammateName + '.'}<br/> {'Direct messages and files shared here are not shown to people outside this area.'} </p> - <a + <ToggleModalButton className='intro-links' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > <i className='fa fa-pencil'></i>{'Set a header'} - </a> + </ToggleModalButton> </div> ); } @@ -79,17 +76,13 @@ export function createOffTopicIntroMessage(channel, showInviteModal) { {'This is the start of ' + channel.display_name + ', a channel for non-work-related conversations.'} <br/> </p> - <a + <ToggleModalButton className='intro-links' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > <i className='fa fa-pencil'></i>{'Set a header'} - </a> + </ToggleModalButton> <a href='#' className='intro-links' @@ -138,17 +131,13 @@ export function createDefaultIntroMessage(channel) { {'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'} </p> {inviteModalLink} - <a + <ToggleModalButton className='intro-links' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > <i className='fa fa-pencil'></i>{'Set a header'} - </a> + </ToggleModalButton> <br/> </div> ); @@ -193,17 +182,13 @@ export function createStandardIntroMessage(channel, showInviteModal) { {memberMessage} <br/> </p> - <a + <ToggleModalButton className='intro-links' - href='#' - data-toggle='modal' - data-target='#edit_channel' - data-header={channel.header} - data-title={channel.display_name} - data-channelid={channel.id} + dialogType={EditChannelModal} + dialogProps={{channel}} > <i className='fa fa-pencil'></i>{'Set a header'} - </a> + </ToggleModalButton> <a className='intro-links' href='#' diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 764bdf763..9b2f7e057 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -749,19 +749,10 @@ export function updateCodeTheme(theme) { export function placeCaretAtEnd(el) { el.focus(); - if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') { - var range = document.createRange(); - range.selectNodeContents(el); - range.collapse(false); - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } else if (typeof document.body.createTextRange != 'undefined') { - var textRange = document.body.createTextRange(); - textRange.moveToElementText(el); - textRange.collapse(false); - textRange.select(); - } + el.selectionStart = el.value.length; + el.selectionEnd = el.value.length; + + return; } export function getCaretPosition(el) { |