summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris <ccbrown112@gmail.com>2017-08-29 16:14:59 -0500
committerGitHub <noreply@github.com>2017-08-29 16:14:59 -0500
commit213a072b38d29d3c3ec8e150584685b1144a7d6a (patch)
tree1da10a494e49914b0f6641db79e7dcf8ad3886f6
parent59798c137584a0b7e008ec713b489929dd83a690 (diff)
downloadchat-213a072b38d29d3c3ec8e150584685b1144a7d6a.tar.gz
chat-213a072b38d29d3c3ec8e150584685b1144a7d6a.tar.bz2
chat-213a072b38d29d3c3ec8e150584685b1144a7d6a.zip
PLT-6403: Interactive messages (#7274)
* wip * finish first pass * requested changes * add DoPostAction to Client4
-rw-r--r--api4/context.go11
-rw-r--r--api4/params.go5
-rw-r--r--api4/post.go20
-rw-r--r--app/command.go2
-rw-r--r--app/notification.go4
-rw-r--r--app/post.go67
-rw-r--r--app/post_test.go72
-rw-r--r--i18n/en.json8
-rw-r--r--model/client4.go10
-rw-r--r--model/post.go85
-rw-r--r--model/post_list.go14
-rw-r--r--model/slack_attachment.go1
-rw-r--r--webapp/actions/post_actions.jsx4
-rw-r--r--webapp/components/post_view/post_attachment.jsx46
-rw-r--r--webapp/components/post_view/post_attachment_list.jsx6
-rw-r--r--webapp/components/post_view/post_body_additional_content.jsx1
-rw-r--r--webapp/sass/layout/_webhooks.scss15
-rw-r--r--webapp/utils/utils.jsx6
18 files changed, 366 insertions, 11 deletions
diff --git a/api4/context.go b/api4/context.go
index 69351a098..e95e29991 100644
--- a/api4/context.go
+++ b/api4/context.go
@@ -586,3 +586,14 @@ func (c *Context) RequireJobType() *Context {
}
return c
}
+
+func (c *Context) RequireActionId() *Context {
+ if c.Err != nil {
+ return c
+ }
+
+ if len(c.Params.ActionId) != 26 {
+ c.SetInvalidUrlParam("action_id")
+ }
+ return c
+}
diff --git a/api4/params.go b/api4/params.go
index b48e5fc1b..8b1d0febe 100644
--- a/api4/params.go
+++ b/api4/params.go
@@ -39,6 +39,7 @@ type ApiParams struct {
Service string
JobId string
JobType string
+ ActionId string
Page int
PerPage int
Permanent bool
@@ -137,6 +138,10 @@ func ApiParamsFromRequest(r *http.Request) *ApiParams {
params.JobType = val
}
+ if val, ok := props["action_id"]; ok {
+ params.ActionId = val
+ }
+
if val, err := strconv.Atoi(r.URL.Query().Get("page")); err != nil || val < 0 {
params.Page = PAGE_DEFAULT
} else {
diff --git a/api4/post.go b/api4/post.go
index deaad1e1c..ea23e098b 100644
--- a/api4/post.go
+++ b/api4/post.go
@@ -27,6 +27,7 @@ func InitPost() {
BaseRoutes.Team.Handle("/posts/search", ApiSessionRequired(searchPosts)).Methods("POST")
BaseRoutes.Post.Handle("", ApiSessionRequired(updatePost)).Methods("PUT")
BaseRoutes.Post.Handle("/patch", ApiSessionRequired(patchPost)).Methods("PUT")
+ BaseRoutes.Post.Handle("/actions/{action_id:[A-Za-z0-9]+}", ApiSessionRequired(doPostAction)).Methods("POST")
BaseRoutes.Post.Handle("/pin", ApiSessionRequired(pinPost)).Methods("POST")
BaseRoutes.Post.Handle("/unpin", ApiSessionRequired(unpinPost)).Methods("POST")
}
@@ -428,3 +429,22 @@ func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.FileInfosToJson(infos)))
}
}
+
+func doPostAction(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequirePostId().RequireActionId()
+ if c.Err != nil {
+ return
+ }
+
+ if !app.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) {
+ c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
+ return
+ }
+
+ if err := app.DoPostAction(c.Params.PostId, c.Params.ActionId, c.Session.UserId); err != nil {
+ c.Err = err
+ return
+ }
+
+ ReturnStatusOK(w)
+}
diff --git a/app/command.go b/app/command.go
index b9d6748fb..a2e63a3d4 100644
--- a/app/command.go
+++ b/app/command.go
@@ -53,7 +53,7 @@ func CreateCommandPost(post *model.Post, teamId string, response *model.CommandR
}
post.ParentId = ""
- SendEphemeralPost(teamId, post.UserId, post)
+ SendEphemeralPost(post.UserId, post)
}
return post, nil
diff --git a/app/notification.go b/app/notification.go
index 258606ad4..af7589934 100644
--- a/app/notification.go
+++ b/app/notification.go
@@ -182,7 +182,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
hereNotification = false
SendEphemeralPost(
- team.Id,
post.UserId,
&model.Post{
ChannelId: post.ChannelId,
@@ -195,7 +194,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
// If the channel has more than 1K users then @channel is disabled
if channelNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
SendEphemeralPost(
- team.Id,
post.UserId,
&model.Post{
ChannelId: post.ChannelId,
@@ -208,7 +206,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
// If the channel has more than 1K users then @all is disabled
if allNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
SendEphemeralPost(
- team.Id,
post.UserId,
&model.Post{
ChannelId: post.ChannelId,
@@ -734,7 +731,6 @@ func sendOutOfChannelMentions(sender *model.User, post *model.Post, teamId strin
}
SendEphemeralPost(
- teamId,
post.UserId,
&model.Post{
ChannelId: post.ChannelId,
diff --git a/app/post.go b/app/post.go
index 5b83ab7a2..c852a90d2 100644
--- a/app/post.go
+++ b/app/post.go
@@ -4,8 +4,11 @@
package app
import (
+ "encoding/json"
+ "fmt"
"net/http"
"regexp"
+ "strings"
l4g "github.com/alecthomas/log4go"
"github.com/dyatlov/go-opengraph/opengraph"
@@ -210,7 +213,7 @@ func parseSlackLinksToMarkdown(text string) string {
return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
}
-func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post {
+func SendEphemeralPost(userId string, post *model.Post) *model.Post {
post.Type = model.POST_EPHEMERAL
// fill in fields which haven't been specified which have sensible defaults
@@ -638,3 +641,65 @@ func GetOpenGraphMetadata(url string) *opengraph.OpenGraph {
return og
}
+
+func DoPostAction(postId string, actionId string, userId string) *model.AppError {
+ pchan := Srv.Store.Post().GetSingle(postId)
+
+ var post *model.Post
+ if result := <-pchan; result.Err != nil {
+ return result.Err
+ } else {
+ post = result.Data.(*model.Post)
+ }
+
+ action := post.GetAction(actionId)
+ if action == nil || action.Integration == nil {
+ return model.NewAppError("DoPostAction", "api.post.do_action.action_id.app_error", nil, fmt.Sprintf("action=%v", action), http.StatusNotFound)
+ }
+
+ request := &model.PostActionIntegrationRequest{
+ UserId: userId,
+ Context: action.Integration.Context,
+ }
+
+ req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson()))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Accept", "application/json")
+ resp, err := utils.HttpClient(false).Do(req)
+ if err != nil {
+ return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, fmt.Sprintf("status=%v", resp.StatusCode), http.StatusBadRequest)
+ }
+
+ var response model.PostActionIntegrationResponse
+ if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
+ return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest)
+ }
+
+ if response.Update != nil {
+ response.Update.Id = postId
+ response.Update.AddProp("from_webhook", "true")
+ if _, err := UpdatePost(response.Update, false); err != nil {
+ return err
+ }
+ }
+
+ if response.EphemeralText != "" {
+ ephemeralPost := &model.Post{}
+ ephemeralPost.Message = parseSlackLinksToMarkdown(response.EphemeralText)
+ ephemeralPost.ChannelId = post.ChannelId
+ ephemeralPost.RootId = post.RootId
+ if ephemeralPost.RootId == "" {
+ ephemeralPost.RootId = post.Id
+ }
+ ephemeralPost.UserId = userId
+ ephemeralPost.AddProp("from_webhook", "true")
+ SendEphemeralPost(userId, ephemeralPost)
+ }
+
+ return nil
+}
diff --git a/app/post_test.go b/app/post_test.go
index 416fbfc9e..ab8e27021 100644
--- a/app/post_test.go
+++ b/app/post_test.go
@@ -4,11 +4,18 @@
package app
import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
"testing"
"time"
- "fmt"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
)
func TestUpdatePostEditAt(t *testing.T) {
@@ -68,3 +75,66 @@ func TestPostReplyToPostWhereRootPosterLeftChannel(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestPostAction(t *testing.T) {
+ th := Setup().InitBasic()
+
+ allowedInternalConnections := *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections
+ defer func() {
+ utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
+ }()
+ *utils.Cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1"
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var request model.PostActionIntegrationRequest
+ err := json.NewDecoder(r.Body).Decode(&request)
+ assert.NoError(t, err)
+ assert.Equal(t, request.UserId, th.BasicUser.Id)
+ assert.Equal(t, "foo", request.Context["s"])
+ assert.EqualValues(t, 3, request.Context["n"])
+ fmt.Fprintf(w, `{"update": {"message": "updated"}, "ephemeral_text": "foo"}`)
+ }))
+ defer ts.Close()
+
+ interactivePost := model.Post{
+ Message: "Interactive post",
+ ChannelId: th.BasicChannel.Id,
+ PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
+ UserId: th.BasicUser.Id,
+ Props: model.StringInterface{
+ "attachments": []*model.SlackAttachment{
+ &model.SlackAttachment{
+ Text: "hello",
+ Actions: []*model.PostAction{
+ &model.PostAction{
+ Integration: &model.PostActionIntegration{
+ Context: model.StringInterface{
+ "s": "foo",
+ "n": 3,
+ },
+ URL: ts.URL,
+ },
+ Name: "action",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ post, err := CreatePostAsUser(&interactivePost)
+ require.Nil(t, err)
+
+ attachments, ok := post.Props["attachments"].([]*model.SlackAttachment)
+ require.True(t, ok)
+
+ require.NotEmpty(t, attachments[0].Actions)
+ require.NotEmpty(t, attachments[0].Actions[0].Id)
+
+ err = DoPostAction(post.Id, "notavalidid", th.BasicUser.Id)
+ require.NotNil(t, err)
+ assert.Equal(t, http.StatusNotFound, err.StatusCode)
+
+ err = DoPostAction(post.Id, attachments[0].Actions[0].Id, th.BasicUser.Id)
+ require.Nil(t, err)
+}
diff --git a/i18n/en.json b/i18n/en.json
index 7624183b3..af93ef775 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1760,6 +1760,14 @@
"translation": "Unable to get post"
},
{
+ "id": "api.post.do_action.action_id.app_error",
+ "translation": "Invalid action id"
+ },
+ {
+ "id": "api.post.do_action.action_integration.app_error",
+ "translation": "Action integration error"
+ },
+ {
"id": "api.preference.delete_preferences.decode.app_error",
"translation": "Unable to decode preferences from request"
},
diff --git a/model/client4.go b/model/client4.go
index 0f7578539..26ea6ee03 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -1803,6 +1803,16 @@ func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*Po
}
}
+// DoPostAction performs a post action.
+func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) {
+ if r, err := c.DoApiPost(c.GetPostRoute(postId)+"/actions/"+actionId, ""); err != nil {
+ return false, BuildErrorResponse(r, err)
+ } else {
+ defer closeBody(r)
+ return CheckStatusOK(r), BuildResponse(r)
+ }
+}
+
// File Section
// UploadFile will upload a file to a channel, to be later attached to a post.
diff --git a/model/post.go b/model/post.go
index 55e6f591d..1c2e9b937 100644
--- a/model/post.go
+++ b/model/post.go
@@ -68,8 +68,31 @@ type PostForIndexing struct {
ParentCreateAt *int64 `json:"parent_create_at"`
}
+type PostAction struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Integration *PostActionIntegration `json:"integration,omitempty"`
+}
+
+type PostActionIntegration struct {
+ URL string `json:"url,omitempty"`
+ Context StringInterface `json:"context,omitempty"`
+}
+
+type PostActionIntegrationRequest struct {
+ UserId string `json:"user_id"`
+ Context StringInterface `json:"context,omitempty"`
+}
+
+type PostActionIntegrationResponse struct {
+ Update *Post `json:"update"`
+ EphemeralText string `json:"ephemeral_text"`
+}
+
func (o *Post) ToJson() string {
- b, err := json.Marshal(o)
+ copy := *o
+ copy.StripActionIntegrations()
+ b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
@@ -179,6 +202,16 @@ func (o *Post) PreSave() {
o.Props = make(map[string]interface{})
}
+ if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
+ for _, attachment := range attachments {
+ for _, action := range attachment.Actions {
+ if action.Id == "" {
+ action.Id = NewId()
+ }
+ }
+ }
+ }
+
if o.Filenames == nil {
o.Filenames = []string{}
}
@@ -246,3 +279,53 @@ func PostPatchFromJson(data io.Reader) *PostPatch {
return &post
}
+
+func (r *PostActionIntegrationRequest) ToJson() string {
+ b, err := json.Marshal(r)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func (o *Post) Attachments() []*SlackAttachment {
+ if attachments, ok := o.Props["attachments"].([]*SlackAttachment); ok {
+ return attachments
+ }
+ var ret []*SlackAttachment
+ if attachments, ok := o.Props["attachments"].([]interface{}); ok {
+ for _, attachment := range attachments {
+ if enc, err := json.Marshal(attachment); err == nil {
+ var decoded SlackAttachment
+ if json.Unmarshal(enc, &decoded) == nil {
+ ret = append(ret, &decoded)
+ }
+ }
+ }
+ }
+ return ret
+}
+
+func (o *Post) StripActionIntegrations() {
+ attachments := o.Attachments()
+ if o.Props["attachments"] != nil {
+ o.Props["attachments"] = attachments
+ }
+ for _, attachment := range attachments {
+ for _, action := range attachment.Actions {
+ action.Integration = nil
+ }
+ }
+}
+
+func (o *Post) GetAction(id string) *PostAction {
+ for _, attachment := range o.Attachments() {
+ for _, action := range attachment.Actions {
+ if action.Id == id {
+ return action
+ }
+ }
+ }
+ return nil
+}
diff --git a/model/post_list.go b/model/post_list.go
index 63f6d6825..b3caadafd 100644
--- a/model/post_list.go
+++ b/model/post_list.go
@@ -20,8 +20,20 @@ func NewPostList() *PostList {
}
}
+func (o *PostList) StripActionIntegrations() {
+ posts := o.Posts
+ o.Posts = make(map[string]*Post)
+ for id, post := range posts {
+ pcopy := *post
+ pcopy.StripActionIntegrations()
+ o.Posts[id] = &pcopy
+ }
+}
+
func (o *PostList) ToJson() string {
- b, err := json.Marshal(o)
+ copy := *o
+ copy.StripActionIntegrations()
+ b, err := json.Marshal(&copy)
if err != nil {
return ""
} else {
diff --git a/model/slack_attachment.go b/model/slack_attachment.go
index 855838214..fe3958316 100644
--- a/model/slack_attachment.go
+++ b/model/slack_attachment.go
@@ -25,6 +25,7 @@ type SlackAttachment struct {
Footer string `json:"footer"`
FooterIcon string `json:"footer_icon"`
Timestamp interface{} `json:"ts"` // This is either a string or an int64
+ Actions []*PostAction `json:"actions,omitempty"`
}
type SlackAttachmentField struct {
diff --git a/webapp/actions/post_actions.jsx b/webapp/actions/post_actions.jsx
index 60913b171..cb111ec39 100644
--- a/webapp/actions/post_actions.jsx
+++ b/webapp/actions/post_actions.jsx
@@ -351,3 +351,7 @@ export function unpinPost(postId) {
});
};
}
+
+export function doPostAction(postId, actionId) {
+ PostActions.doPostAction(postId, actionId)(dispatch, getState);
+}
diff --git a/webapp/components/post_view/post_attachment.jsx b/webapp/components/post_view/post_attachment.jsx
index 36fe3bf9f..cc7aa509c 100644
--- a/webapp/components/post_view/post_attachment.jsx
+++ b/webapp/components/post_view/post_attachment.jsx
@@ -4,6 +4,8 @@
import * as TextFormatting from 'utils/text_formatting.jsx';
import {localizeMessage} from 'utils/utils.jsx';
+import * as PostActions from 'actions/post_actions.jsx';
+
import $ from 'jquery';
import React from 'react';
import PropTypes from 'prop-types';
@@ -12,6 +14,11 @@ export default class PostAttachment extends React.PureComponent {
static propTypes = {
/**
+ * The post id
+ */
+ postId: PropTypes.string.isRequired,
+
+ /**
* The attachment to render
*/
attachment: PropTypes.object.isRequired
@@ -20,6 +27,8 @@ export default class PostAttachment extends React.PureComponent {
constructor(props) {
super(props);
+ this.handleActionButtonClick = this.handleActionButtonClick.bind(this);
+ this.getActionView = this.getActionView.bind(this);
this.getFieldsTable = this.getFieldsTable.bind(this);
this.getInitState = this.getInitState.bind(this);
this.shouldCollapse = this.shouldCollapse.bind(this);
@@ -80,6 +89,41 @@ export default class PostAttachment extends React.PureComponent {
return TextFormatting.formatText(text) + `<div><a class="attachment-link-more" href="#">${localizeMessage('post_attachment.more', 'Show more...')}</a></div>`;
}
+ getActionView() {
+ const actions = this.props.attachment.actions;
+ if (!actions || !actions.length) {
+ return '';
+ }
+
+ const buttons = [];
+
+ actions.forEach((action) => {
+ if (!action.id || !action.name) {
+ return;
+ }
+ buttons.push(
+ <button
+ key={action.id}
+ onClick={() => this.handleActionButtonClick(action.id)}
+ >
+ {action.name}
+ </button>
+ );
+ });
+
+ return (
+ <div
+ className='attachment-actions'
+ >
+ {buttons}
+ </div>
+ );
+ }
+
+ handleActionButtonClick(actionId) {
+ PostActions.doPostAction(this.props.postId, actionId);
+ }
+
getFieldsTable() {
const fields = this.props.attachment.fields;
if (!fields || !fields.length) {
@@ -275,6 +319,7 @@ export default class PostAttachment extends React.PureComponent {
}
const fields = this.getFieldsTable();
+ const actions = this.getActionView();
let useBorderStyle;
if (data.color && data.color[0] === '#') {
@@ -301,6 +346,7 @@ export default class PostAttachment extends React.PureComponent {
{text}
{image}
{fields}
+ {actions}
</div>
{thumb}
<div style={{clear: 'both'}}/>
diff --git a/webapp/components/post_view/post_attachment_list.jsx b/webapp/components/post_view/post_attachment_list.jsx
index cfd2f81f8..ce60a0155 100644
--- a/webapp/components/post_view/post_attachment_list.jsx
+++ b/webapp/components/post_view/post_attachment_list.jsx
@@ -10,6 +10,11 @@ export default class PostAttachmentList extends React.PureComponent {
static propTypes = {
/**
+ * The post id
+ */
+ postId: PropTypes.string.isRequired,
+
+ /**
* Array of attachments to render
*/
attachments: PropTypes.array.isRequired
@@ -21,6 +26,7 @@ export default class PostAttachmentList extends React.PureComponent {
content.push(
<PostAttachment
attachment={attachment}
+ postId={this.props.postId}
key={'att_' + i}
/>
);
diff --git a/webapp/components/post_view/post_body_additional_content.jsx b/webapp/components/post_view/post_body_additional_content.jsx
index ddc73d554..88e8f2ba8 100644
--- a/webapp/components/post_view/post_body_additional_content.jsx
+++ b/webapp/components/post_view/post_body_additional_content.jsx
@@ -90,6 +90,7 @@ export default class PostBodyAdditionalContent extends React.PureComponent {
return (
<PostAttachmentList
attachments={attachments}
+ postId={this.props.post.id}
key={this.props.post.id}
/>
);
diff --git a/webapp/sass/layout/_webhooks.scss b/webapp/sass/layout/_webhooks.scss
index 15572ce85..ed3e2555a 100644
--- a/webapp/sass/layout/_webhooks.scss
+++ b/webapp/sass/layout/_webhooks.scss
@@ -284,5 +284,20 @@
}
}
}
+
+ .attachment-actions {
+ margin-top: 9px;
+
+ button {
+ @include border-radius(3px);
+ outline: 0;
+ margin: 8px 8px 0 0;
+ border-width: 1px;
+ border-style: solid;
+ height: 30px;
+ font-size: 13px;
+ font-weight: 700;
+ }
+ }
}
}
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index 52574e735..93ba39f30 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -602,7 +602,7 @@ export function applyTheme(theme) {
changeCss('.app__body .popover.top>.arrow:after, .app__body .tip-overlay.tip-overlay--chat .arrow', 'border-top-color:' + theme.centerChannelBg);
changeCss('@media(min-width: 768px){.app__body .form-control', 'background:' + theme.centerChannelBg);
changeCss('@media(min-width: 768px){.app__body .sidebar--right.sidebar--right--expanded .sidebar-right-container', 'background:' + theme.centerChannelBg);
- changeCss('.app__body .attachment__content', 'background:' + theme.centerChannelBg);
+ changeCss('.app__body .attachment__content, .app__body .attachment-actions button', 'background:' + theme.centerChannelBg);
changeCss('body.app__body', 'scrollbar-face-color:' + theme.centerChannelBg);
changeCss('body.app__body', 'scrollbar-track-color:' + theme.centerChannelBg);
changeCss('.app__body .shortcut-key, .app__body .post-list__new-messages-below', 'color:' + theme.centerChannelBg);
@@ -653,7 +653,9 @@ export function applyTheme(theme) {
changeCss('.app__body .input-group-addon, .app__body .form-control, .app__body .post-create__container .post-body__actions > span', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.1));
changeCss('@media(min-width: 768px){.app__body .post-list__table .post-list__content .dropdown-menu a:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.1));
changeCss('.app__body .form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));
- changeCss('.app__body .attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .attachment .attachment__content, .app__body .attachment-actions button', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3));
+ changeCss('.app__body .attachment-actions button:focus, .app__body .attachment-actions button:hover', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.5));
+ changeCss('.app__body .attachment-actions button:focus, .app__body .attachment-actions button:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.03));
changeCss('.app__body .input-group-addon, .app__body .channel-intro .channel-intro__content, .app__body .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05));
changeCss('.app__body .date-separator .separator__text', 'color:' + theme.centerChannelColor);
changeCss('.app__body .date-separator .separator__hr, .app__body .modal-footer, .app__body .modal .custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2));