diff options
-rw-r--r-- | app/command_channel_header.go | 73 | ||||
-rw-r--r-- | i18n/en.json | 32 | ||||
-rw-r--r-- | webapp/actions/global_actions.jsx | 8 | ||||
-rw-r--r-- | webapp/actions/websocket_actions.jsx | 10 | ||||
-rw-r--r-- | webapp/components/create_post.jsx | 15 | ||||
-rw-r--r-- | webapp/components/navbar.jsx | 9 | ||||
-rw-r--r-- | webapp/stores/modal_store.jsx | 1 | ||||
-rw-r--r-- | webapp/utils/constants.jsx | 1 |
8 files changed, 141 insertions, 8 deletions
diff --git a/app/command_channel_header.go b/app/command_channel_header.go new file mode 100644 index 000000000..b5a70ef89 --- /dev/null +++ b/app/command_channel_header.go @@ -0,0 +1,73 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + + goi18n "github.com/nicksnyder/go-i18n/i18n" +) + +type HeaderProvider struct { +} + +const ( + CMD_HEADER = "header" +) + +func init() { + RegisterCommandProvider(&HeaderProvider{}) +} + +func (me *HeaderProvider) GetTrigger() string { + return CMD_HEADER +} + +func (me *HeaderProvider) GetCommand(T goi18n.TranslateFunc) *model.Command { + return &model.Command{ + Trigger: CMD_HEADER, + AutoComplete: true, + AutoCompleteDesc: T("api.command_channel_header.desc"), + AutoCompleteHint: T("api.command_channel_header.hint"), + DisplayName: T("api.command_channel_header.name"), + } +} + +func (me *HeaderProvider) 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_header.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_header.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_header.permission.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + + if len(message) == 0 { + 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 + + updateChannel, err := UpdateChannel(channel) + 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/i18n/en.json b/i18n/en.json index 38273bfe4..a2d1637a3 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -536,6 +536,38 @@ "translation": "echo" }, { + "id": "api.command_channel_header.desc", + "translation": "Edit the channel header" + }, + { + "id": "api.command_channel_header.hint", + "translation": "[text]" + }, + { + "id": "api.command_channel_header.name", + "translation": "header" + }, + { + "id": "api.command_channel_header.message.app_error", + "translation": "Text must be provided with the /header command." + }, + { + "id": "api.command_channel_header.channel.app_error", + "translation": "Error to retrieve the current channel." + }, + { + "id": "api.command_channel_header.permission.app_error", + "translation": "You do not have the appropriate permissions to edit the channel header." + }, + { + "id": "api.command_channel_header.update_channel.app_error", + "translation": "Error to update the current channel." + }, + { + "id": "api.command_channel_header.update_channel.success", + "translation": "Channel header successfully updated." + }, + { "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 ea08d6671..a154d556d 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -212,6 +212,14 @@ export function showDeletePostModal(post, commentCount = 0) { }); } +export function showChannelHeaderUpdateModal(channel) { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, + value: true, + channel + }); +} + export function showGetPostLinkModal(post) { AppDispatcher.handleViewAction({ type: ActionTypes.TOGGLE_GET_POST_LINK_MODAL, diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx index 52bd61006..2af9a5d96 100644 --- a/webapp/actions/websocket_actions.jsx +++ b/webapp/actions/websocket_actions.jsx @@ -229,6 +229,11 @@ function handleEvent(msg) { } } +function handleChannelUpdatedEvent(msg) { + const channel = JSON.parse(msg.data.channel); + dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel}); +} + function handleNewPostEvent(msg) { const post = JSON.parse(msg.data.post); handleNewPost(post, msg); @@ -341,11 +346,6 @@ function handleUserRemovedEvent(msg) { } } -function handleChannelUpdatedEvent(msg) { - const channel = JSON.parse(msg.data.channel); - dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel}); -} - function handleUserUpdatedEvent(msg) { const user = msg.data.user; if (UserStore.getCurrentId() !== user.id) { diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index d0f2a0afd..8ec671e45 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -76,14 +76,15 @@ export default class CreatePost extends React.Component { PostStore.clearDraftUploads(); - const channelId = ChannelStore.getCurrentId(); - const draft = PostStore.getDraft(channelId); - + const channel = ChannelStore.getCurrent(); + const channelId = channel.id; + const draft = PostStore.getPostDraft(channelId); const stats = ChannelStore.getCurrentStats(); const members = stats.member_count - 1; this.state = { channelId, + channel, message: draft.message, uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos, @@ -213,12 +214,20 @@ export default class CreatePost extends React.Component { handleSubmit(e) { const stats = ChannelStore.getCurrentStats(); const members = stats.member_count - 1; + const updateChannel = ChannelStore.getCurrent(); if ((this.state.message.includes('@all') || this.state.message.includes('@channel')) && members >= Constants.NOTIFY_ALL_MEMBERS) { this.setState({totalMembers: members}); this.showNotifyAllModal(); return; } + + if (this.state.message.endsWith('/header ')) { + GlobalActions.showChannelHeaderUpdateModal(updateChannel); + this.setState({message: ''}); + return; + } + this.doSubmit(e); } diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx index 0f8de01a3..b27e22709 100644 --- a/webapp/components/navbar.jsx +++ b/webapp/components/navbar.jsx @@ -55,6 +55,7 @@ export default class Navbar extends React.Component { this.showSearch = this.showSearch.bind(this); this.showEditChannelHeaderModal = this.showEditChannelHeaderModal.bind(this); + this.hideEditChannelHeaderModal = this.hideEditChannelHeaderModal.bind(this); this.showRenameChannelModal = this.showRenameChannelModal.bind(this); this.hideRenameChannelModal = this.hideRenameChannelModal.bind(this); this.isStateValid = this.isStateValid.bind(this); @@ -110,6 +111,7 @@ export default class Navbar extends React.Component { UserStore.addChangeListener(this.onChange); PreferenceStore.addChangeListener(this.onChange); ModalStore.addModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal); + ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.showEditChannelHeaderModal); $('.inner-wrap').click(this.hideSidebars); document.addEventListener('keydown', this.handleQuickSwitchKeyPress); } @@ -121,6 +123,7 @@ export default class Navbar extends React.Component { UserStore.removeChangeListener(this.onChange); PreferenceStore.removeChangeListener(this.onChange); ModalStore.removeModalListener(ActionTypes.TOGGLE_QUICK_SWITCH_MODAL, this.toggleQuickSwitchModal); + ModalStore.addModalListener(ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL, this.hideEditChannelHeaderModal); document.removeEventListener('keydown', this.handleQuickSwitchKeyPress); } @@ -193,6 +196,12 @@ export default class Navbar extends React.Component { }); } + hideEditChannelHeaderModal() { + this.setState({ + showEditChannelHeaderModal: false + }); + } + showRenameChannelModal(e) { e.preventDefault(); diff --git a/webapp/stores/modal_store.jsx b/webapp/stores/modal_store.jsx index 07842ca59..434efcf90 100644 --- a/webapp/stores/modal_store.jsx +++ b/webapp/stores/modal_store.jsx @@ -41,6 +41,7 @@ class ModalStoreClass extends EventEmitter { case ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL: case ActionTypes.TOGGLE_DM_MODAL: case ActionTypes.TOGGLE_QUICK_SWITCH_MODAL: + case ActionTypes.TOGGLE_CHANNEL_HEADER_UPDATE_MODAL: this.emit(type, value, args); break; } diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index f5e67bcaf..477495423 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -176,6 +176,7 @@ export const ActionTypes = keyMirror({ TOGGLE_GET_PUBLIC_LINK_MODAL: null, TOGGLE_DM_MODAL: null, TOGGLE_QUICK_SWITCH_MODAL: null, + TOGGLE_CHANNEL_HEADER_UPDATE_MODAL: null, SUGGESTION_PRETEXT_CHANGED: null, SUGGESTION_RECEIVED_SUGGESTIONS: null, |