mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Added ability to see/edit channel purpose in the client
This commit is contained in:
@@ -11,6 +11,7 @@ const TextFormatting = require('../utils/text_formatting.jsx');
|
||||
const Utils = require('../utils/utils.jsx');
|
||||
const MessageWrapper = require('./message_wrapper.jsx');
|
||||
const PopoverListMembers = require('./popover_list_members.jsx');
|
||||
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
|
||||
|
||||
const AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
|
||||
const Constants = require('../utils/constants.jsx');
|
||||
@@ -27,7 +28,9 @@ export default class ChannelHeader extends React.Component {
|
||||
this.handleLeave = this.handleLeave.bind(this);
|
||||
this.searchMentions = this.searchMentions.bind(this);
|
||||
|
||||
this.state = this.getStateFromStores();
|
||||
const state = this.getStateFromStores();
|
||||
state.showEditChannelPurposeModal = false;
|
||||
this.state = state;
|
||||
}
|
||||
getStateFromStores() {
|
||||
return {
|
||||
@@ -232,6 +235,20 @@ export default class ChannelHeader extends React.Component {
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='set_channel_purpose'
|
||||
role='presentation'
|
||||
>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='#'
|
||||
onClick={() => this.setState({showEditChannelPurposeModal: true})}
|
||||
>
|
||||
Set {channelTerm} Purpose...
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
dropdownContents.push(
|
||||
<li
|
||||
key='notification_preferences'
|
||||
@@ -307,84 +324,91 @@ export default class ChannelHeader extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<table className='channel-header alt'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<div className='channel-header__info'>
|
||||
<div className='dropdown'>
|
||||
<div>
|
||||
<table className='channel-header alt'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
<div className='channel-header__info'>
|
||||
<div className='dropdown'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
id='channel_header_dropdown'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<strong className='heading'>{channelTitle} </strong>
|
||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu'
|
||||
role='menu'
|
||||
aria-labelledby='channel_header_dropdown'
|
||||
>
|
||||
{dropdownContents}
|
||||
</ul>
|
||||
</div>
|
||||
<OverlayTrigger
|
||||
trigger={['hover', 'focus']}
|
||||
placement='bottom'
|
||||
overlay={popoverContent}
|
||||
ref='headerOverlay'
|
||||
>
|
||||
<div
|
||||
onClick={TextFormatting.handleClick}
|
||||
className='description'
|
||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false})}}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<PopoverListMembers
|
||||
members={this.state.users}
|
||||
channelId={channel.id}
|
||||
/>
|
||||
</th>
|
||||
<th className='search-bar__container'><NavbarSearchBox /></th>
|
||||
<th>
|
||||
<div className='dropdown channel-header__links'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
id='channel_header_dropdown'
|
||||
id='channel_header_right_dropdown'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<strong className='heading'>{channelTitle} </strong>
|
||||
<span className='glyphicon glyphicon-chevron-down header-dropdown__icon' />
|
||||
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu'
|
||||
className='dropdown-menu dropdown-menu-right'
|
||||
role='menu'
|
||||
aria-labelledby='channel_header_dropdown'
|
||||
aria-labelledby='channel_header_right_dropdown'
|
||||
>
|
||||
{dropdownContents}
|
||||
<li role='presentation'>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='#'
|
||||
onClick={this.searchMentions}
|
||||
>
|
||||
Recent Mentions
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<OverlayTrigger
|
||||
trigger={['hover', 'focus']}
|
||||
placement='bottom'
|
||||
overlay={popoverContent}
|
||||
ref='headerOverlay'
|
||||
>
|
||||
<div
|
||||
onClick={TextFormatting.handleClick}
|
||||
className='description'
|
||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(channel.header, {singleline: true, mentionHighlight: false})}}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<PopoverListMembers
|
||||
members={this.state.users}
|
||||
channelId={channel.id}
|
||||
/>
|
||||
</th>
|
||||
<th className='search-bar__container'><NavbarSearchBox /></th>
|
||||
<th>
|
||||
<div className='dropdown channel-header__links'>
|
||||
<a
|
||||
href='#'
|
||||
className='dropdown-toggle theme'
|
||||
type='button'
|
||||
id='channel_header_right_dropdown'
|
||||
data-toggle='dropdown'
|
||||
aria-expanded='true'
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
|
||||
</a>
|
||||
<ul
|
||||
className='dropdown-menu dropdown-menu-right'
|
||||
role='menu'
|
||||
aria-labelledby='channel_header_right_dropdown'
|
||||
>
|
||||
<li role='presentation'>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='#'
|
||||
onClick={this.searchMentions}
|
||||
>
|
||||
Recent Mentions
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<EditChannelPurposeModal
|
||||
show={this.state.showEditChannelPurposeModal}
|
||||
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
|
||||
channel={channel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
118
web/react/components/edit_channel_purpose_modal.jsx
Normal file
118
web/react/components/edit_channel_purpose_modal.jsx
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
const AsyncClient = require('../utils/async_client.jsx');
|
||||
const Client = require('../utils/client.jsx');
|
||||
const Modal = ReactBootstrap.Modal;
|
||||
|
||||
export default class EditChannelPurposeModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleHide = this.handleHide.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
|
||||
this.state = {serverError: ''};
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
this.setState({serverError: ''});
|
||||
|
||||
if (this.props.onModalDismissed) {
|
||||
this.props.onModalDismissed();
|
||||
}
|
||||
}
|
||||
|
||||
handleSave() {
|
||||
if (!this.props.channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
channel_id: this.props.channel.id,
|
||||
channel_purpose: ReactDOM.findDOMNode(this.refs.purpose).value.trim()
|
||||
};
|
||||
|
||||
Client.updateChannelPurpose(data,
|
||||
() => {
|
||||
AsyncClient.getChannel(this.props.channel.id);
|
||||
|
||||
this.handleHide();
|
||||
},
|
||||
(err) => {
|
||||
if (err.message === 'Invalid channel_purpose parameter') {
|
||||
this.setState({serverError: 'This channel purpose is too long, please enter a shorter one'});
|
||||
} else {
|
||||
this.setState({serverError: err.message});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let serverError = null;
|
||||
if (this.state.serverError) {
|
||||
serverError = (
|
||||
<div className='form-group has-error'>
|
||||
<br/>
|
||||
<label className='control-label'>{this.state.serverError}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let title = <span>{'Edit Purpose'}</span>;
|
||||
if (this.props.channel.display_name) {
|
||||
title = <span>{'Edit Purpose for '}<span className='name'>{this.props.channel.display_name}</span></span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='modal-edit-channel-purpose'
|
||||
show={this.props.show}
|
||||
onHide={this.handleHide}
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
<Modal.Title>
|
||||
{title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<textarea
|
||||
ref='purpose'
|
||||
className='form-control no-resize'
|
||||
rows='6'
|
||||
maxLength='128'
|
||||
defaultValue={this.props.channel.purpose}
|
||||
/>
|
||||
{serverError}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-default'
|
||||
onClick={this.handleHide}
|
||||
>
|
||||
{'Cancel'}
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary'
|
||||
onClick={this.handleSave}
|
||||
>
|
||||
{'Save'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditChannelPurposeModal.propTypes = {
|
||||
show: React.PropTypes.bool.isRequired,
|
||||
channel: React.PropTypes.object,
|
||||
onModalDismissed: React.PropTypes.func.isRequired
|
||||
};
|
||||
@@ -105,12 +105,11 @@ export default class MoreChannels extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO Switch channel.header to channel.purpose once that has been added
|
||||
return (
|
||||
<tr key={channel.id}>
|
||||
<td>
|
||||
<p className='more-name'>{channel.display_name}</p>
|
||||
<p className='more-header'>{channel.header}</p>
|
||||
<p className='more-purpose'>{channel.purpose}</p>
|
||||
</td>
|
||||
<td className='td--action'>
|
||||
{joinButton}
|
||||
|
||||
@@ -8,6 +8,7 @@ var ChannelStore = require('../stores/channel_store.jsx');
|
||||
var TeamStore = require('../stores/team_store.jsx');
|
||||
var MessageWrapper = require('./message_wrapper.jsx');
|
||||
var NotifyCounts = require('./notify_counts.jsx');
|
||||
const EditChannelPurposeModal = require('./edit_channel_purpose_modal.jsx');
|
||||
const Utils = require('../utils/utils.jsx');
|
||||
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
@@ -26,7 +27,9 @@ export default class Navbar extends React.Component {
|
||||
this.createCollapseButtons = this.createCollapseButtons.bind(this);
|
||||
this.createDropdown = this.createDropdown.bind(this);
|
||||
|
||||
this.state = this.getStateFromStores();
|
||||
const state = this.getStateFromStores();
|
||||
state.showEditChannelPurposeModal = false;
|
||||
this.state = state;
|
||||
}
|
||||
getStateFromStores() {
|
||||
return {
|
||||
@@ -122,6 +125,19 @@ export default class Navbar extends React.Component {
|
||||
</li>
|
||||
);
|
||||
|
||||
var setChannelPurposeOption = null;
|
||||
if (!isDirect) {
|
||||
setChannelPurposeOption = (
|
||||
<li role='presentation'>
|
||||
<a
|
||||
role='menuitem'
|
||||
href='#'
|
||||
onClick={() => this.setState({showEditChannelPurposeModal: true})}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
var addMembersOption;
|
||||
var leaveChannelOption;
|
||||
if (!isDirect && !ChannelStore.isDefault(channel)) {
|
||||
@@ -250,6 +266,7 @@ export default class Navbar extends React.Component {
|
||||
{addMembersOption}
|
||||
{manageMembersOption}
|
||||
{setChannelHeaderOption}
|
||||
{setChannelPurposeOption}
|
||||
{notificationPreferenceOption}
|
||||
{renameChannelOption}
|
||||
{deleteChannelOption}
|
||||
@@ -392,17 +409,24 @@ export default class Navbar extends React.Component {
|
||||
var channelMenuDropdown = this.createDropdown(channel, channelTitle, isAdmin, isDirect, popoverContent);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className='navbar navbar-default navbar-fixed-top'
|
||||
role='navigation'
|
||||
>
|
||||
<div className='container-fluid theme'>
|
||||
<div className='navbar-header'>
|
||||
{collapseButtons}
|
||||
{channelMenuDropdown}
|
||||
<div>
|
||||
<nav
|
||||
className='navbar navbar-default navbar-fixed-top'
|
||||
role='navigation'
|
||||
>
|
||||
<div className='container-fluid theme'>
|
||||
<div className='navbar-header'>
|
||||
{collapseButtons}
|
||||
{channelMenuDropdown}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
<EditChannelPurposeModal
|
||||
show={this.state.showEditChannelPurposeModal}
|
||||
onModalDismissed={() => this.setState({showEditChannelPurposeModal: false})}
|
||||
channel={channel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default class NewChannelFlow extends React.Component {
|
||||
flowState: SHOW_NEW_CHANNEL,
|
||||
channelDisplayName: '',
|
||||
channelName: '',
|
||||
channelHeader: '',
|
||||
channelPurpose: '',
|
||||
nameModified: false
|
||||
};
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export default class NewChannelFlow extends React.Component {
|
||||
flowState: SHOW_NEW_CHANNEL,
|
||||
channelDisplayName: '',
|
||||
channelName: '',
|
||||
channelHeader: '',
|
||||
channelPurpose: '',
|
||||
nameModified: false
|
||||
});
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export default class NewChannelFlow extends React.Component {
|
||||
|
||||
const cu = UserStore.getCurrentUser();
|
||||
channel.team_id = cu.team_id;
|
||||
channel.header = this.state.channelHeader;
|
||||
channel.purpose = this.state.channelPurpose;
|
||||
channel.type = this.state.channelType;
|
||||
|
||||
Client.createChannel(channel,
|
||||
@@ -109,7 +109,7 @@ export default class NewChannelFlow extends React.Component {
|
||||
channelDataChanged(data) {
|
||||
this.setState({
|
||||
channelDisplayName: data.displayName,
|
||||
channelHeader: data.header
|
||||
channelPurpose: data.purpose
|
||||
});
|
||||
if (!this.state.nameModified) {
|
||||
this.setState({channelName: Utils.cleanUpUrlable(data.displayName.trim())});
|
||||
@@ -119,7 +119,7 @@ export default class NewChannelFlow extends React.Component {
|
||||
const channelData = {
|
||||
name: this.state.channelName,
|
||||
displayName: this.state.channelDisplayName,
|
||||
header: this.state.channelHeader
|
||||
purpose: this.state.channelPurpose
|
||||
};
|
||||
|
||||
let showChannelModal = false;
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class NewChannelModal extends React.Component {
|
||||
handleChange() {
|
||||
const newData = {
|
||||
displayName: ReactDOM.findDOMNode(this.refs.display_name).value,
|
||||
header: ReactDOM.findDOMNode(this.refs.channel_header).value
|
||||
purpose: ReactDOM.findDOMNode(this.refs.channel_purpose).value
|
||||
};
|
||||
this.props.onDataChanged(newData);
|
||||
}
|
||||
@@ -136,22 +136,22 @@ export default class NewChannelModal extends React.Component {
|
||||
</div>
|
||||
<div className='form-group less'>
|
||||
<div className='col-sm-3'>
|
||||
<label className='form__label control-label'>{'Header'}</label>
|
||||
<label className='form__label control-label'>{'Purpose'}</label>
|
||||
<label className='form__label light'>{'(optional)'}</label>
|
||||
</div>
|
||||
<div className='col-sm-9'>
|
||||
<textarea
|
||||
className='form-control no-resize'
|
||||
ref='channel_header'
|
||||
ref='channel_purpose'
|
||||
rows='4'
|
||||
placeholder='Header'
|
||||
maxLength='1024'
|
||||
value={this.props.channelData.header}
|
||||
placeholder='Purpose'
|
||||
maxLength='128'
|
||||
value={this.props.channelData.purpose}
|
||||
onChange={this.handleChange}
|
||||
tabIndex='2'
|
||||
/>
|
||||
<p className='input__help'>
|
||||
{'This text is shown in the channel header and supports markdown formatting.'}
|
||||
{`Describe how this ${channelTerm} should be used.`}
|
||||
</p>
|
||||
{serverError}
|
||||
</div>
|
||||
|
||||
@@ -592,6 +592,23 @@ export function updateChannelHeader(data, success, error) {
|
||||
track('api', 'api_channels_header');
|
||||
}
|
||||
|
||||
export function updateChannelPurpose(data, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/channels/update_purpose',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
success,
|
||||
error: function onError(xhr, status, err) {
|
||||
var e = handleError('updateChannelPurpose', xhr, status, err);
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
|
||||
track('api', 'api_channels_purpose');
|
||||
}
|
||||
|
||||
export function updateNotifyProps(data, success, error) {
|
||||
$.ajax({
|
||||
url: '/api/v1/channels/update_notify_props',
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
@include opacity(0.8);
|
||||
}
|
||||
|
||||
.more-header {
|
||||
.more-purpose {
|
||||
@include opacity(0.7);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user