From 816bfbeb91b9cd64a8a85dc37cc0e82554409c14 Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Fri, 21 Jul 2017 21:04:41 +0200 Subject: [PLT-6708] /purpose [text] slash command: Edit the channel purpose (#6569) * /purpose [text] slash command: Edit the channel purpose * update command on server side to check for direct or group channels * update stings and block the dialog when is DM or GM * update per review * remove duplicate websocker event and apply the same for /header command * update per review * update --- app/command_channel_header.go | 17 +++------- app/command_channel_purpose.go | 69 +++++++++++++++++++++++++++++++++++++++ i18n/en.json | 32 ++++++++++++++++++ webapp/actions/global_actions.jsx | 8 +++++ webapp/components/create_post.jsx | 9 ++++- webapp/components/navbar.jsx | 20 ++++++++++-- webapp/stores/modal_store.jsx | 1 + webapp/utils/constants.jsx | 1 + 8 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 app/command_channel_purpose.go diff --git a/app/command_channel_header.go b/app/command_channel_header.go index b5a70ef89..e552f9282 100644 --- a/app/command_channel_header.go +++ b/app/command_channel_header.go @@ -4,7 +4,6 @@ package app import ( - l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" goi18n "github.com/nicksnyder/go-i18n/i18n" @@ -53,21 +52,15 @@ func (me *HeaderProvider) DoCommand(args *model.CommandArgs, message string) *mo return &model.CommandResponse{Text: args.T("api.command_channel_header.message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } - oldChannelHeader := channel.Header - channel.Header = message + patch := &model.ChannelPatch{ + Header: new(string), + } + *patch.Header = message - updateChannel, err := UpdateChannel(channel) + _, err = PatchChannel(channel, patch, args.UserId) if err != nil { return &model.CommandResponse{Text: args.T("api.command_channel_header.update_channel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } - messageWs := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_UPDATED, "", channel.Id, "", nil) - messageWs.Add("channel", channel.ToJson()) - Publish(messageWs) - - if err := PostUpdateChannelHeaderMessage(args.Session.UserId, channel.Id, args.TeamId, oldChannelHeader, updateChannel.Header); err != nil { - l4g.Error(err.Error()) - } - return &model.CommandResponse{} } diff --git a/app/command_channel_purpose.go b/app/command_channel_purpose.go new file mode 100644 index 000000000..db3ab541a --- /dev/null +++ b/app/command_channel_purpose.go @@ -0,0 +1,69 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "github.com/mattermost/platform/model" + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type PurposeProvider struct { +} + +const ( + CMD_PURPOSE = "purpose" +) + +func init() { + RegisterCommandProvider(&PurposeProvider{}) +} + +func (me *PurposeProvider) GetTrigger() string { + return CMD_PURPOSE +} + +func (me *PurposeProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_PURPOSE, + AutoComplete: true, + AutoCompleteDesc: T("api.command_channel_purpose.desc"), + AutoCompleteHint: T("api.command_channel_purpose.hint"), + DisplayName: T("api.command_channel_purpose.name"), + } +} + +func (me *PurposeProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse { + channel, err := GetChannel(args.ChannelId) + if err != nil { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.channel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if channel.Type == model.CHANNEL_OPEN && !SessionHasPermissionToChannel(args.Session, args.ChannelId, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.permission.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if channel.Type == model.CHANNEL_PRIVATE && !SessionHasPermissionToChannel(args.Session, args.ChannelId, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES) { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.permission.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if channel.Type == model.CHANNEL_GROUP || channel.Type == model.CHANNEL_DIRECT { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.direct_group.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if len(message) == 0 { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + patch := &model.ChannelPatch{ + Purpose: new(string), + } + *patch.Purpose = message + + _, err = PatchChannel(channel, patch, args.UserId) + if err != nil { + return &model.CommandResponse{Text: args.T("api.command_channel_purpose.update_channel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + return &model.CommandResponse{} +} diff --git a/i18n/en.json b/i18n/en.json index 75fcf25a0..e523e0451 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -571,6 +571,38 @@ "id": "api.command_echo.name", "translation": "echo" }, + { + "id": "api.command_channel_purpose.name", + "translation": "purpose" + }, + { + "id": "api.command_channel_purpose.hint", + "translation": "[text]" + }, + { + "id": "api.command_channel_purpose.desc", + "translation": "Edit the channel purpose" + }, + { + "id": "api.command_channel_purpose.channel.app_error", + "translation": "Error to retrieve the current channel." + }, + { + "id": "api.command_channel_purpose.permission.app_error", + "translation": "You do not have the appropriate permissions to edit the channel purpose." + }, + { + "id": "api.command_channel_purpose.direct_group.app_error", + "translation": "Cannot set purpose for direct message channels. Use /header to set the header instead." + }, + { + "id": "api.command_channel_purpose.message.app_error", + "translation": "A message must be provided with the /purpose command." + }, + { + "id": "api.command_channel_purpose.update_channel.app_error", + "translation": "Error to update the current channel." + }, { "id": "api.command_expand.desc", "translation": "Turn off auto-collapsing of image previews" diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index e37e702a2..b3dc078c4 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -225,6 +225,14 @@ export function showChannelHeaderUpdateModal(channel) { }); } +export function showChannelPurposeUpdateModal(channel) { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL, + value: true, + channel + }); +} + export function showGetPostLinkModal(post) { AppDispatcher.handleViewAction({ type: ActionTypes.TOGGLE_GET_POST_LINK_MODAL, diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index f2f4d7e39..32f812aa3 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -227,12 +227,19 @@ export default class CreatePost extends React.Component { return; } - if (this.state.message.endsWith('/header ')) { + if (this.state.message.trimRight() === '/header') { GlobalActions.showChannelHeaderUpdateModal(updateChannel); this.setState({message: ''}); return; } + const isDirectOrGroup = ((updateChannel.type === Constants.DM_CHANNEL) || (updateChannel.type === Constants.GM_CHANNEL)); + if (!isDirectOrGroup && this.state.message.trimRight() === '/purpose') { + GlobalActions.showChannelPurposeUpdateModal(updateChannel); + this.setState({message: ''}); + return; + } + this.doSubmit(e); } diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index 6305f870e..fc2ade7ab 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -56,6 +56,8 @@ export default class Navbar extends React.Component { this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); this.hideEditChannelHeaderModal = this.hideEditChannelHeaderModal.bind(this); + this.showChannelPurposeModal = this.showChannelPurposeModal.bind(this); + this.hideChannelPurposeModal = this.hideChannelPurposeModal.bind(this); this.showRenameChannelModal = this.showRenameChannelModal.bind(this); this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this); this.isStateValid = this.isStateValid.bind(this); @@ -112,6 +114,7 @@ export default class Navbar extends React.Component { PreferenceStore.addChangeListener(this.onChange); ModalStore.addModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal); ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.showEditChannelHeaderModal); + ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL, this.showChannelPurposeModal); $('.inner-wrap').click(this.hideSidebars); document.addEventListener('keydown', this.handleQuickSwitchKeyPress); } @@ -124,6 +127,7 @@ export default class Navbar extends React.Component { PreferenceStore.removeChangeListener(this.onChange); ModalStore.removeModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal); ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.hideEditChannelHeaderModal); + ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL, this.hideChannelPurposeModal); document.removeEventListener('keydown', this.handleQuickSwitchKeyPress); } @@ -202,6 +206,18 @@ export default class Navbar extends React.Component { }); } + showChannelPurposeModal() { + this.setState({ + showEditChannelPurposeModal: true + }); + } + + hideChannelPurposeModal() { + this.setState({ + showEditChannelPurposeModal: false + }); + } + showRenameChannelModal(e) { e.preventDefault(); @@ -504,7 +520,7 @@ export default class Navbar extends React.Component { this.setState({showEditChannelPurposeModal: true})} + onClick={this.showChannelPurposeModal} > this.setState({showEditChannelPurposeModal: false})} + onModalDismissed={this.hideChannelPurposeModal} channel={channel} /> ); diff --git a/webapp/stores/modal_store.jsx b/webapp/stores/modal_store.jsx index 434efcf90..666219d41 100644 --- a/webapp/stores/modal_store.jsx +++ b/webapp/stores/modal_store.jsx @@ -42,6 +42,7 @@ class ModalStoreClass extends EventEmitter { case ActionTypes.TOGGLE_DM_MODAL: case ActionTypes.TOGGLE_QUICK_SWITCH_MODAL: case ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL: + case ActionTypes.TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL: this.emit(type, value, args); break; } diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index abe891e28..4ff20854f 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -175,6 +175,7 @@ export const ActionTypes = keyMirror({ TOGGLE_DM_MODAL: null, TOGGLE_QUICK_SWITCH_MODAL: null, TOGGLE_CHANNEL_HEADER_UPDATE_MODAL: null, + TOGGLE_CHANNEL_PURPOSE_UPDATE_MODAL: null, SUGGESTION_PRETEXT_CHANGED: null, SUGGESTION_RECEIVED_SUGGESTIONS: null, -- cgit v1.2.3-1-g7c22