diff --git a/actions/post_actions.jsx b/actions/post_actions.jsx index c3f04016e585..a618fd84b1cc 100644 --- a/actions/post_actions.jsx +++ b/actions/post_actions.jsx @@ -14,7 +14,7 @@ import * as StorageActions from 'actions/storage'; import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx'; import * as RhsActions from 'actions/views/rhs'; import {isEmbedVisible} from 'selectors/posts'; -import {getSelectedPostId, getRhsState} from 'selectors/rhs'; +import {getSelectedPostId, getSelectedPostCardId, getRhsState} from 'selectors/rhs'; import { ActionTypes, Constants, @@ -246,6 +246,14 @@ export function deleteAndRemovePost(post) { }); } + if (post.id === getSelectedPostCardId(getState())) { + dispatch({ + type: ActionTypes.SELECT_POST_CARD, + postId: '', + channelId: '', + }); + } + dispatch(PostActions.removePost(post)); return {data: true}; diff --git a/actions/views/rhs.js b/actions/views/rhs.js index 49983e93244b..e9b5960f6753 100644 --- a/actions/views/rhs.js +++ b/actions/views/rhs.js @@ -54,6 +54,17 @@ export function selectPostFromRightHandSideSearch(post) { }; } +export function selectPostCardFromRightHandSideSearch(post) { + return async (dispatch, getState) => { + dispatch({ + type: ActionTypes.SELECT_POST_CARD, + postId: post.id, + channelId: post.channel_id, + previousRhsState: getRhsState(getState()), + }); + }; +} + export function selectPostFromRightHandSideSearchByPostId(postId) { return async (dispatch, getState) => { const post = getPost(getState(), postId); @@ -231,3 +242,7 @@ export function toggleRhsExpanded() { export function selectPost(post) { return {type: ActionTypes.SELECT_POST, postId: post.root_id || post.id, channelId: post.channel_id}; } + +export function selectPostCard(post) { + return {type: ActionTypes.SELECT_POST_CARD, postId: post.id, channelId: post.channel_id}; +} diff --git a/components/__snapshots__/profile_picture.test.jsx.snap b/components/__snapshots__/profile_picture.test.jsx.snap index 77f29340f92b..2d8c5f2600cb 100644 --- a/components/__snapshots__/profile_picture.test.jsx.snap +++ b/components/__snapshots__/profile_picture.test.jsx.snap @@ -6,7 +6,7 @@ exports[`components/ProfilePicture should match snapshot, no user specified, def > { + if (!post) { + return; + } + + this.props.actions.selectPostCard(post); + } + handleDropdownOpened = (opened) => { if (this.props.togglePostMenu) { this.props.togglePostMenu(opened); @@ -258,6 +267,7 @@ export default class Post extends React.PureComponent { + + + } + > + + + ); + } + let options; if (isEphemeral) { options = ( @@ -274,6 +319,7 @@ export default class PostInfo extends React.PureComponent {
{postTime} {pinnedBadge} + {postInfoIcon} {postFlagIcon} {visibleMessage}
diff --git a/components/post_view/post_info/post_info.test.jsx b/components/post_view/post_info/post_info.test.jsx index 78db5d288f06..f9c17d7eba82 100644 --- a/components/post_view/post_info/post_info.test.jsx +++ b/components/post_view/post_info/post_info.test.jsx @@ -31,6 +31,7 @@ describe('components/post_view/PostInfo', () => { const requiredProps = { post, handleCommentClick: jest.fn(), + handleCardClick: jest.fn(), handleDropdownOpened: jest.fn(), compactDisplay: false, replyCount: 0, diff --git a/components/profile_picture.jsx b/components/profile_picture.jsx index 677fada6eb0d..c46e6fb8325b 100644 --- a/components/profile_picture.jsx +++ b/components/profile_picture.jsx @@ -53,7 +53,7 @@ export default class ProfilePicture extends React.PureComponent { > {`${this.props.username {''} + + +
+
+ +
+
+
+ , + "user": , + } + } + /> +
+
+ + + +
+
+
+
+ +`; + +exports[`comoponents/rhs_card/RhsCard should match on post when plugin defining card types don't match with the post type 1`] = ` +
+ + +
+
+ +
+
+
+ , + "user": , + } + } + /> +
+
+ + + +
+
+
+
+
+`; + +exports[`comoponents/rhs_card/RhsCard should match on post when plugin defining card types match with the post type 1`] = ` +
+ + +
+ +
+
+ , + "user": , + } + } + /> +
+
+ + + +
+
+
+
+
+`; + +exports[`comoponents/rhs_card/RhsCard should match when no post is selected 1`] = `
`; diff --git a/components/rhs_card/index.js b/components/rhs_card/index.js new file mode 100644 index 000000000000..eb699d5ff86b --- /dev/null +++ b/components/rhs_card/index.js @@ -0,0 +1,25 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {connect} from 'react-redux'; +import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams'; +import {getConfig} from 'mattermost-redux/selectors/entities/general'; + +import {getSelectedPostCard} from 'selectors/rhs.jsx'; + +import RhsCard from './rhs_card.jsx'; + +function mapStateToProps(state) { + const selected = getSelectedPostCard(state); + const config = getConfig(state); + const enablePostUsernameOverride = config.EnablePostUsernameOverride === 'true'; + + return { + enablePostUsernameOverride, + selected, + pluginPostCardTypes: state.plugins.postCardTypes, + teamUrl: getCurrentRelativeTeamUrl(state), + }; +} + +export default connect(mapStateToProps)(RhsCard); diff --git a/components/rhs_card/rhs_card.jsx b/components/rhs_card/rhs_card.jsx new file mode 100644 index 000000000000..a3b2ccae4d1c --- /dev/null +++ b/components/rhs_card/rhs_card.jsx @@ -0,0 +1,192 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import PropTypes from 'prop-types'; +import React from 'react'; +import Scrollbars from 'react-custom-scrollbars'; +import {FormattedMessage} from 'react-intl'; +import {Link} from 'react-router-dom'; + +import DelayedAction from 'utils/delayed_action.jsx'; +import Constants, {RHSStates} from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; +import RhsCardHeader from 'components/rhs_card_header'; +import Markdown from 'components/markdown'; +import UserProfile from 'components/user_profile'; +import PostProfilePicture from 'components/post_profile_picture'; +import * as GlobalActions from 'actions/global_actions.jsx'; + +export function renderView(props) { + return ( +
); +} + +export function renderThumbHorizontal(props) { + return ( +
); +} + +export function renderThumbVertical(props) { + return ( +
); +} + +export default class RhsCard extends React.Component { + static propTypes = { + selected: PropTypes.object, + pluginPostCardTypes: PropTypes.object, + previousRhsState: PropTypes.oneOf(Object.values(RHSStates)), + enablePostUsernameOverride: PropTypes.bool, + teamUrl: PropTypes.string, + } + + static defaultProps = { + pluginPostCardTypes: {}, + } + + constructor(props) { + super(props); + + this.scrollStopAction = new DelayedAction(this.handleScrollStop); + + this.state = { + isScrolling: false, + topRhsPostCreateAt: 0, + }; + } + + shouldComponentUpdate(nextProps, nextState) { + if (!Utils.areObjectsEqual(nextState.selected, this.props.selected)) { + return true; + } + if (nextState.isScrolling !== this.state.isScrolling) { + return true; + } + return false; + } + + handleScroll = () => { + if (!this.state.isScrolling) { + this.setState({ + isScrolling: true, + }); + } + + this.scrollStopAction.fireAfter(Constants.SCROLL_DELAY); + } + + handleScrollStop = () => { + this.setState({ + isScrolling: false, + }); + } + + getSidebarBody = () => { + return this.refs.sidebarbody; + } + + handleClick = () => { + if (Utils.isMobile()) { + GlobalActions.emitCloseRightHandSide(); + } + }; + + render() { + if (this.props.selected == null) { + return (
); + } + + const {selected, pluginPostCardTypes, teamUrl} = this.props; + const postType = selected.type; + let content = null; + if (pluginPostCardTypes.hasOwnProperty(postType)) { + const PluginComponent = pluginPostCardTypes[postType].component; + content = ; + } + + if (!content) { + content = ( +
+ +
+ ); + } + + let user = ( + + ); + if (selected.props.override_username && this.props.enablePostUsernameOverride) { + user = ( + + ); + } + const avatar = ( + + ); + + return ( +
+ + +
+ {content} +
+
+ +
+
+ + + +
+
+
+
+
+ ); + } +} diff --git a/components/rhs_card/rhs_card.test.jsx b/components/rhs_card/rhs_card.test.jsx new file mode 100644 index 000000000000..a60aff49c503 --- /dev/null +++ b/components/rhs_card/rhs_card.test.jsx @@ -0,0 +1,68 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React from 'react'; +import {shallow} from 'enzyme'; + +import RhsCard from './rhs_card.jsx'; + +describe('comoponents/rhs_card/RhsCard', () => { + const post = { + id: '123', + message: 'test', + type: 'test', + create_at: 1542994995740, + props: {}, + }; + + const currentChannel = { + id: '111', + name: 'town-square', + display_name: 'Town Square', + }; + + it('should match when no post is selected', () => { + const wrapper = shallow( + , + ); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should match on post when no plugin defining card types', () => { + const wrapper = shallow( + , + ); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should match on post when plugin defining card types don\'t match with the post type', () => { + const wrapper = shallow( + }}} + channel={currentChannel} + />, + ); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should match on post when plugin defining card types match with the post type', () => { + const wrapper = shallow( + }}} + channel={currentChannel} + />, + ); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/components/rhs_card_header/index.jsx b/components/rhs_card_header/index.jsx new file mode 100644 index 000000000000..7b28dfea2288 --- /dev/null +++ b/components/rhs_card_header/index.jsx @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; + +import { + showMentions, + showSearchResults, + showFlaggedPosts, + showPinnedPosts, + closeRightHandSide, + toggleRhsExpanded, +} from 'actions/views/rhs'; + +import RshCardHeader from './rhs_card_header.jsx'; + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + showMentions, + showSearchResults, + showFlaggedPosts, + showPinnedPosts, + closeRightHandSide, + toggleRhsExpanded, + }, dispatch), + }; +} + +export default connect(null, mapDispatchToProps)(RshCardHeader); diff --git a/components/rhs_card_header/rhs_card_header.jsx b/components/rhs_card_header/rhs_card_header.jsx new file mode 100644 index 000000000000..6138b6f3dc38 --- /dev/null +++ b/components/rhs_card_header/rhs_card_header.jsx @@ -0,0 +1,221 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import PropTypes from 'prop-types'; +import React from 'react'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; + +import Constants, {RHSStates} from 'utils/constants.jsx'; + +export default class RhsCardHeader extends React.Component { + static propTypes = { + previousRhsState: PropTypes.oneOf(Object.values(RHSStates)), + actions: PropTypes.shape({ + showMentions: PropTypes.func, + showSearchResults: PropTypes.func, + showFlaggedPosts: PropTypes.func, + showPinnedPosts: PropTypes.func, + closeRightHandSide: PropTypes.func, + toggleRhsExpanded: PropTypes.func.isRequired, + }), + }; + + handleBack = (e) => { + e.preventDefault(); + + switch (this.props.previousRhsState) { + case RHSStates.SEARCH: + this.props.actions.showSearchResults(); + break; + case RHSStates.MENTION: + this.props.actions.showMentions(); + break; + case RHSStates.FLAG: + this.props.actions.showFlaggedPosts(); + break; + case RHSStates.PIN: + this.props.actions.showPinnedPosts(); + break; + default: + break; + } + } + + render() { + let back; + let backToResultsTooltip; + + switch (this.props.previousRhsState) { + case RHSStates.SEARCH: + case RHSStates.MENTION: + backToResultsTooltip = ( + + + + ); + break; + case RHSStates.FLAG: + backToResultsTooltip = ( + + + + ); + break; + case RHSStates.PIN: + backToResultsTooltip = ( + + + + ); + break; + } + + const closeSidebarTooltip = ( + + + + ); + + const expandSidebarTooltip = ( + + + + ); + + const shrinkSidebarTooltip = ( + + + + ); + + if (backToResultsTooltip) { + back = ( + + + + {(ariaLabel) => ( + + )} + + + + ); + } + + return ( +
+ + {back} + + +
+ + +
+
+ ); + } +} diff --git a/components/rhs_comment/rhs_comment.jsx b/components/rhs_comment/rhs_comment.jsx index 82304fd382ad..41f319df6560 100644 --- a/components/rhs_comment/rhs_comment.jsx +++ b/components/rhs_comment/rhs_comment.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {FormattedMessage} from 'react-intl'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {Posts} from 'mattermost-redux/constants/index'; import { isPostEphemeral, @@ -23,6 +24,7 @@ import ReactionList from 'components/post_view/reaction_list'; import MessageWithAdditionalContent from 'components/message_with_additional_content'; import BotBadge from 'components/widgets/badges/bot_badge.jsx'; import Badge from 'components/widgets/badges/badge.jsx'; +import InfoSmallIcon from 'components/svg/info_small_icon'; import UserProfile from 'components/user_profile'; @@ -44,6 +46,7 @@ export default class RhsComment extends React.PureComponent { pluginPostTypes: PropTypes.object, channelIsArchived: PropTypes.bool.isRequired, isConsecutivePost: PropTypes.bool, + handleCardClick: PropTypes.func, }; constructor(props) { @@ -338,6 +341,38 @@ export default class RhsComment extends React.PureComponent { postTime = this.renderPostTime(isEphemeral); } + let postInfoIcon; + if (post.props && post.props.card) { + postInfoIcon = ( + + + + } + > + + + ); + } + return (
{postTime} {pinnedBadge} + {postInfoIcon} {flagIcon} {visibleMessage}
diff --git a/components/rhs_root_post/rhs_root_post.jsx b/components/rhs_root_post/rhs_root_post.jsx index 62e7c966e358..2249275ddb7a 100644 --- a/components/rhs_root_post/rhs_root_post.jsx +++ b/components/rhs_root_post/rhs_root_post.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {FormattedMessage} from 'react-intl'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {Posts} from 'mattermost-redux/constants'; import * as ReduxPostUtils from 'mattermost-redux/utils/post_utils'; @@ -19,6 +20,7 @@ import PostTime from 'components/post_view/post_time'; import PostReaction from 'components/post_view/post_reaction'; import MessageWithAdditionalContent from 'components/message_with_additional_content'; import BotBadge from 'components/widgets/badges/bot_badge.jsx'; +import InfoSmallIcon from 'components/svg/info_small_icon'; import UserProfile from 'components/user_profile'; @@ -41,6 +43,7 @@ export default class RhsRootPost extends React.PureComponent { channelIsArchived: PropTypes.bool.isRequired, channelType: PropTypes.string, channelDisplayName: PropTypes.string, + handleCardClick: PropTypes.func.isRequired, }; static defaultProps = { @@ -268,6 +271,38 @@ export default class RhsRootPost extends React.PureComponent { ); } + let postInfoIcon; + if (this.props.post.props && this.props.post.props.card) { + postInfoIcon = ( + + + + } + > + + + ); + } + return (
{this.renderPostTime(isEphemeral)} {pinnedBadge} + {postInfoIcon} {postFlagIcon}
{dotMenuContainer} diff --git a/components/rhs_thread/index.js b/components/rhs_thread/index.js index f4a7c7213836..9873a378fe47 100644 --- a/components/rhs_thread/index.js +++ b/components/rhs_thread/index.js @@ -10,6 +10,7 @@ import {removePost} from 'mattermost-redux/actions/posts'; import {Preferences} from 'utils/constants.jsx'; import {getSelectedPost} from 'selectors/rhs.jsx'; +import {selectPostCard} from 'actions/views/rhs'; import RhsThread from './rhs_thread.jsx'; @@ -42,6 +43,7 @@ function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ removePost, + selectPostCard, }, dispatch), }; } diff --git a/components/rhs_thread/rhs_thread.jsx b/components/rhs_thread/rhs_thread.jsx index ff8e35c142a5..5bdd8d189b06 100644 --- a/components/rhs_thread/rhs_thread.jsx +++ b/components/rhs_thread/rhs_thread.jsx @@ -55,6 +55,7 @@ export default class RhsThread extends React.Component { previewEnabled: PropTypes.bool.isRequired, actions: PropTypes.shape({ removePost: PropTypes.func.isRequired, + selectPostCard: PropTypes.func.isRequired, }).isRequired, } @@ -149,6 +150,22 @@ export default class RhsThread extends React.Component { } } + handleCardClick = (post) => { + if (!post) { + return; + } + + this.props.actions.selectPostCard(post); + } + + handleCardClickPost = (post) => { + if (!post) { + return; + } + + this.props.actions.selectPostCard(post); + } + onBusy = (isBusy) => { this.setState({isBusy}); } @@ -273,6 +290,7 @@ export default class RhsThread extends React.Component { removePost={this.props.actions.removePost} previewCollapsed={this.props.previewCollapsed} previewEnabled={this.props.previewEnabled} + handleCardClick={this.handleCardClickPost} /> ); } @@ -356,6 +374,7 @@ export default class RhsThread extends React.Component { previewCollapsed={this.props.previewCollapsed} previewEnabled={this.props.previewEnabled} isBusy={this.state.isBusy} + handleCardClick={this.handleCardClick} /> {isFakeDeletedPost && rootPostDay && }
); + } else if (this.props.isCard) { + title = ( + + ); } return ( diff --git a/components/search_results_item/index.js b/components/search_results_item/index.js index d1e224d3291a..20d4feffd8a3 100644 --- a/components/search_results_item/index.js +++ b/components/search_results_item/index.js @@ -14,6 +14,7 @@ import {isPostFlagged} from 'mattermost-redux/utils/post_utils'; import { closeRightHandSide, selectPostFromRightHandSideSearch, + selectPostCardFromRightHandSideSearch, setRhsExpanded, } from 'actions/views/rhs'; @@ -45,6 +46,7 @@ function mapDispatchToProps(dispatch) { actions: bindActionCreators({ closeRightHandSide, selectPost: selectPostFromRightHandSideSearch, + selectPostCard: selectPostCardFromRightHandSideSearch, setRhsExpanded, }, dispatch), }; diff --git a/components/search_results_item/search_results_item.jsx b/components/search_results_item/search_results_item.jsx index 6599fe9c2c9a..ebfbccf539cf 100644 --- a/components/search_results_item/search_results_item.jsx +++ b/components/search_results_item/search_results_item.jsx @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {FormattedMessage} from 'react-intl'; import {Posts} from 'mattermost-redux/constants/index'; import * as ReduxPostUtils from 'mattermost-redux/utils/post_utils'; +import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import PostMessageContainer from 'components/post_view/post_message_view'; import FileAttachmentListContainer from 'components/file_attachment_list'; @@ -20,6 +21,7 @@ import ArchiveIcon from 'components/svg/archive_icon'; import PostTime from 'components/post_view/post_time'; import {browserHistory} from 'utils/browser_history'; import BotBadge from 'components/widgets/badges/bot_badge.jsx'; +import InfoSmallIcon from 'components/svg/info_small_icon'; import Constants, {Locations} from 'utils/constants.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; @@ -89,6 +91,7 @@ export default class SearchResultsItem extends React.PureComponent { actions: PropTypes.shape({ closeRightHandSide: PropTypes.func.isRequired, selectPost: PropTypes.func.isRequired, + selectPostCard: PropTypes.func.isRequired, setRhsExpanded: PropTypes.func.isRequired, }).isRequired, }; @@ -119,6 +122,14 @@ export default class SearchResultsItem extends React.PureComponent { browserHistory.push(`/${this.props.currentTeamName}/pl/${this.props.post.id}`); }; + handleCardClick = (post) => { + if (!post) { + return; + } + + this.props.actions.selectPostCard(post); + } + handleDropdownOpened = (isOpened) => { this.setState({ dropdownOpened: isOpened, @@ -213,6 +224,7 @@ export default class SearchResultsItem extends React.PureComponent { let message; let flagContent; + let postInfoIcon; let rhsControls; if (post.state === Constants.POST_DELETED) { message = ( @@ -232,6 +244,37 @@ export default class SearchResultsItem extends React.PureComponent { /> ); + if (post.props && post.props.card) { + postInfoIcon = ( + + + + } + > + + + ); + } + rhsControls = (
{this.renderPostTime()} {pinnedBadge} + {postInfoIcon} {flagContent}
{rhsControls} diff --git a/components/search_results_item/search_results_item.test.jsx b/components/search_results_item/search_results_item.test.jsx index c5a486ca52bb..6cbb1243e839 100644 --- a/components/search_results_item/search_results_item.test.jsx +++ b/components/search_results_item/search_results_item.test.jsx @@ -82,6 +82,7 @@ describe('components/SearchResultsItem', () => { actions: { closeRightHandSide: mockFunc, selectPost: mockFunc, + selectPostCard: mockFunc, setRhsExpanded: mockFunc, }, }; diff --git a/components/sidebar_right/index.js b/components/sidebar_right/index.js index f72d01373bfc..0e0d132952a8 100644 --- a/components/sidebar_right/index.js +++ b/components/sidebar_right/index.js @@ -14,6 +14,7 @@ import { getIsRhsOpen, getRhsState, getSelectedPostId, + getSelectedPostCardId, getSelectedChannelId, getPreviousRhsState, } from 'selectors/rhs'; @@ -47,6 +48,7 @@ function mapStateToProps(state) { channel, currentUserId: getCurrentUserId(state), postRightVisible: Boolean(getSelectedPostId(state)), + postCardVisible: Boolean(getSelectedPostCardId(state)), searchVisible: Boolean(rhsState), previousRhsState: getPreviousRhsState(state), isMentionSearch: rhsState === RHSStates.MENTION, diff --git a/components/sidebar_right/sidebar_right.jsx b/components/sidebar_right/sidebar_right.jsx index eac957513482..774a14bf2cd5 100644 --- a/components/sidebar_right/sidebar_right.jsx +++ b/components/sidebar_right/sidebar_right.jsx @@ -11,6 +11,7 @@ import * as Utils from 'utils/utils.jsx'; import FileUploadOverlay from 'components/file_upload_overlay.jsx'; import RhsThread from 'components/rhs_thread'; +import RhsCard from 'components/rhs_card'; import SearchBar from 'components/search_bar'; import SearchResults from 'components/search_results'; @@ -21,6 +22,7 @@ export default class SidebarRight extends React.PureComponent { currentUserId: PropTypes.string.isRequired, channel: PropTypes.object, postRightVisible: PropTypes.bool, + postCardVisible: PropTypes.bool, searchVisible: PropTypes.bool, isMentionSearch: PropTypes.bool, isFlaggedPosts: PropTypes.bool, @@ -62,6 +64,7 @@ export default class SidebarRight extends React.PureComponent { isMentionSearch, isPinnedPosts, postRightVisible, + postCardVisible, previousRhsState, searchVisible, } = this.props; @@ -114,6 +117,13 @@ export default class SidebarRight extends React.PureComponent { />
); + } else if (postCardVisible) { + content = ( +
+
{searchForm}
+ +
+ ); } if (!content) { diff --git a/components/svg/info_small_icon.jsx b/components/svg/info_small_icon.jsx new file mode 100644 index 000000000000..e857d1bc64f6 --- /dev/null +++ b/components/svg/info_small_icon.jsx @@ -0,0 +1,43 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +export default class InfoSmallIcon extends React.PureComponent { + render() { + return ( + + + {(ariaLabel) => ( + + + + + + + + )} + + + ); + } +} diff --git a/i18n/en.json b/i18n/en.json index edefee846e46..38059b78e705 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2598,6 +2598,7 @@ "post_info.del": "Delete", "post_info.dot_menu.tooltip.more_actions": "More Actions", "post_info.edit": "Edit", + "post_info.info.view_additional_info": "View additional info", "post_info.menuAriaLabel": "Post extra options", "post_info.message.show_less": "Show Less", "post_info.message.show_more": "Show More", @@ -2659,6 +2660,8 @@ "revoke_user_sessions_modal.desc": "This action revokes all sessions for {username}. They will be logged out from all devices. Are you sure you want to revoke all sessions for {username}?", "revoke_user_sessions_modal.revoke": "Revoke", "revoke_user_sessions_modal.title": "Revoke Sessions for {username}", + "rhs_card.jump": "Jump", + "rhs_card.message_by": "Message by {avatar} {user}", "rhs_comment.comment": "Comment", "rhs_header.backToFlaggedTooltip": "Back to Flagged Posts", "rhs_header.backToPinnedTooltip": "Back to Pinned Posts", @@ -2686,6 +2689,7 @@ "search_header.title2": "Recent Mentions", "search_header.title3": "Flagged Posts", "search_header.title4": "Pinned posts in {channelDisplayName}", + "search_header.title5": "Extra information", "search_item.channelArchived": "Archived", "search_item.direct": "Direct Message (with {username})", "search_item.jump": "Jump", diff --git a/package-lock.json b/package-lock.json index 1a2e78ccd335..78900882cca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6788,8 +6788,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -6810,14 +6809,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6832,20 +6829,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -6962,8 +6956,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -6975,7 +6968,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6990,7 +6982,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6998,14 +6989,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7024,7 +7013,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -7105,8 +7093,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -7118,7 +7105,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7204,8 +7190,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -7241,7 +7226,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7261,7 +7245,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7305,14 +7288,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/plugins/registry.js b/plugins/registry.js index 29adf736d8a1..e556fe6ca921 100644 --- a/plugins/registry.js +++ b/plugins/registry.js @@ -144,6 +144,26 @@ export default class PluginRegistry { return id; } + // Register a component to render a custom body for post cards with a specific type. + // Custom post types must be prefixed with 'custom_'. + // Accepts a string type and a component. + // Returns a unique identifier. + registerPostCardTypeComponent(type, component) { + const id = generateId(); + + store.dispatch({ + type: ActionTypes.RECEIVED_PLUGIN_POST_CARD_COMPONENT, + data: { + id, + pluginId: this.id, + type, + component, + }, + }); + + return id; + } + // Register a main menu list item by providing some text and an action function. // Accepts the following: // - text - A string or React element to display in the menu diff --git a/reducers/plugins/index.js b/reducers/plugins/index.js index c73b874b44c9..84584e3eff89 100644 --- a/reducers/plugins/index.js +++ b/reducers/plugins/index.js @@ -181,6 +181,33 @@ function postTypes(state = {}, action) { } } +function postCardTypes(state = {}, action) { + switch (action.type) { + case ActionTypes.RECEIVED_PLUGIN_POST_CARD_COMPONENT: { + if (action.data) { + // Skip saving the component if one already exists and the new plugin id + // is lower alphabetically + const currentPost = state[action.data.type]; + if (currentPost && action.data.pluginId > currentPost.pluginId) { + return state; + } + + const nextState = {...state}; + nextState[action.data.type] = action.data; + return nextState; + } + return state; + } + case ActionTypes.REMOVED_PLUGIN_POST_CARD_COMPONENT: + return removePostPluginComponent(state, action); + case ActionTypes.RECEIVED_WEBAPP_PLUGIN: + case ActionTypes.REMOVED_WEBAPP_PLUGIN: + return removePostPluginComponents(state, action); + default: + return state; + } +} + export default combineReducers({ // object where every key is a plugin id and values are webapp plugin manifests @@ -193,4 +220,8 @@ export default combineReducers({ // object where every key is a post type and the values are components wrapped in an // an object that contains a plugin id postTypes, + + // object where every key is a post type and the values are components wrapped in an + // an object that contains a plugin id + postCardTypes, }); diff --git a/reducers/views/rhs.js b/reducers/views/rhs.js index 9aa08e363f85..54986d528e19 100644 --- a/reducers/views/rhs.js +++ b/reducers/views/rhs.js @@ -14,6 +14,26 @@ function selectedPostId(state = '', action) { switch (action.type) { case ActionTypes.SELECT_POST: return action.postId; + case ActionTypes.SELECT_POST_CARD: + return ''; + case PostTypes.REMOVE_POST: + if (action.data && action.data.id === state) { + return ''; + } + return state; + case ActionTypes.UPDATE_RHS_STATE: + return ''; + default: + return state; + } +} + +function selectedPostCardId(state = '', action) { + switch (action.type) { + case ActionTypes.SELECT_POST_CARD: + return action.postId; + case ActionTypes.SELECT_POST: + return ''; case PostTypes.POST_REMOVED: if (action.data && action.data.id === state) { return ''; @@ -30,6 +50,8 @@ function selectedChannelId(state = '', action) { switch (action.type) { case ActionTypes.SELECT_POST: return action.channelId; + case ActionTypes.SELECT_POST_CARD: + return action.channelId; case ActionTypes.UPDATE_RHS_STATE: if (action.state === RHSStates.PIN) { return action.channelId; @@ -47,6 +69,11 @@ function previousRhsState(state = null, action) { return action.previousRhsState; } return null; + case ActionTypes.SELECT_POST_CARD: + if (action.previousRhsState) { + return action.previousRhsState; + } + return null; default: return state; } @@ -58,6 +85,8 @@ function rhsState(state = null, action) { return action.state; case ActionTypes.SELECT_POST: return null; + case ActionTypes.SELECT_POST_CARD: + return null; default: return state; } @@ -111,6 +140,8 @@ function isSidebarOpen(state = false, action) { return Boolean(action.state); case ActionTypes.SELECT_POST: return Boolean(action.postId); + case ActionTypes.SELECT_POST_CARD: + return Boolean(action.postId); case ActionTypes.TOGGLE_RHS_MENU: return false; case ActionTypes.OPEN_RHS_MENU: @@ -136,6 +167,8 @@ function isSidebarExpanded(state = false, action) { return action.state ? state : false; case ActionTypes.SELECT_POST: return action.postId ? state : false; + case ActionTypes.SELECT_POST_CARD: + return action.postId ? state : false; case ActionTypes.TOGGLE_RHS_MENU: return false; case ActionTypes.OPEN_RHS_MENU: @@ -172,6 +205,7 @@ function isMenuOpen(state = false, action) { export default combineReducers({ selectedPostId, + selectedPostCardId, selectedChannelId, previousRhsState, rhsState, diff --git a/reducers/views/rhs.test.js b/reducers/views/rhs.test.js index 7f3306e0f3a0..fae9ae5256f6 100644 --- a/reducers/views/rhs.test.js +++ b/reducers/views/rhs.test.js @@ -9,6 +9,7 @@ import {ActionTypes, RHSStates} from 'utils/constants.jsx'; describe('Reducers.RHS', () => { const initialState = { selectedPostId: '', + selectedPostCardId: '', selectedChannelId: '', previousRhsState: null, rhsState: null, @@ -67,6 +68,25 @@ describe('Reducers.RHS', () => { }); }); + test(`should wipe selectedPostCardId on ${ActionTypes.UPDATE_RHS_STATE}`, () => { + const nextState = rhsReducer( + { + selectedPostCardId: '123', + }, + { + type: ActionTypes.UPDATE_RHS_STATE, + state: RHSStates.SEARCH, + } + ); + + expect(nextState).toEqual({ + ...initialState, + selectedPostCardId: '', + rhsState: RHSStates.SEARCH, + isSidebarOpen: true, + }); + }); + test('should match isSearchingFlaggedPost state to true', () => { const nextState = rhsReducer( {}, @@ -167,6 +187,63 @@ describe('Reducers.RHS', () => { }); }); + test('should match select_post_card state', () => { + const nextState1 = rhsReducer( + {}, + { + type: ActionTypes.SELECT_POST_CARD, + postId: '123', + channelId: '321', + } + ); + + expect(nextState1).toEqual({ + ...initialState, + selectedPostCardId: '123', + selectedChannelId: '321', + isSidebarOpen: true, + }); + + const nextState2 = rhsReducer( + { + }, + { + type: ActionTypes.SELECT_POST_CARD, + postId: '123', + channelId: '321', + previousRhsState: RHSStates.SEARCH, + } + ); + + expect(nextState2).toEqual({ + ...initialState, + selectedPostCardId: '123', + selectedChannelId: '321', + previousRhsState: RHSStates.SEARCH, + isSidebarOpen: true, + }); + + const nextState3 = rhsReducer( + { + previousRhsState: RHSStates.SEARCH, + }, + { + type: ActionTypes.SELECT_POST_CARD, + postId: '123', + channelId: '321', + previousRhsState: RHSStates.FLAG, + } + ); + + expect(nextState3).toEqual({ + ...initialState, + selectedPostCardId: '123', + selectedChannelId: '321', + previousRhsState: RHSStates.FLAG, + isSidebarOpen: true, + }); + }); + test(`should wipe rhsState on ${ActionTypes.SELECT_POST}`, () => { const nextState = rhsReducer( { @@ -190,6 +267,29 @@ describe('Reducers.RHS', () => { }); }); + test(`should wipe rhsState on ${ActionTypes.SELECT_POST_CARD}`, () => { + const nextState = rhsReducer( + { + rhsState: RHSStates.PIN, + }, + { + type: ActionTypes.SELECT_POST_CARD, + postId: '123', + channelId: '321', + previousRhsState: RHSStates.PIN, + } + ); + + expect(nextState).toEqual({ + ...initialState, + rhsState: null, + selectedPostCardId: '123', + selectedChannelId: '321', + previousRhsState: RHSStates.PIN, + isSidebarOpen: true, + }); + }); + test(`should open menu, closing sidebar on ${ActionTypes.TOGGLE_RHS_MENU}`, () => { const nextState = rhsReducer( { diff --git a/sass/components/_buttons.scss b/sass/components/_buttons.scss index da505be02e56..9e35104e58b0 100644 --- a/sass/components/_buttons.scss +++ b/sass/components/_buttons.scss @@ -92,3 +92,7 @@ button { } } } + +%btn-transition { + @include single-transition(all, .15s, ease); +} \ No newline at end of file diff --git a/sass/components/_module.scss b/sass/components/_module.scss index dc2b36ee36aa..c19259d07466 100644 --- a/sass/components/_module.scss +++ b/sass/components/_module.scss @@ -33,5 +33,6 @@ @import 'groups'; @import 'select'; @import 'admin-sidebar-header'; +@import 'sidebar-card'; @import 'sidebar-header-dropdown-button'; @import 'sidebar-header'; diff --git a/sass/components/_sidebar-card.scss b/sass/components/_sidebar-card.scss new file mode 100644 index 000000000000..f85e4ba6ae6c --- /dev/null +++ b/sass/components/_sidebar-card.scss @@ -0,0 +1,30 @@ +.post-card--info { + border-top: $border-gray; + position: fixed; + bottom: 0; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px; + white-space: nowrap; + min-width: 0; + + .post-card--post-by { + padding-right: 10px; + } + + .status-wrapper { + height: auto; + } + + .more-modal__image { + height: 20px; + width: 20px; + margin: 0px 2px 0px 4px + } + + .user-popover { + font-weight: 600; + } +} \ No newline at end of file diff --git a/sass/layout/_post-right.scss b/sass/layout/_post-right.scss index 5ce2c54259e4..53b7fee7091d 100644 --- a/sass/layout/_post-right.scss +++ b/sass/layout/_post-right.scss @@ -59,7 +59,7 @@ } } - .flag-icon__container { + .flag-icon__container, .card-icon__container { left: auto; position: relative; top: 2px; @@ -159,6 +159,11 @@ margin: 0 0 15px; } +.card-info-channel__name { + font-weight: 600; + margin: 0 0 15px 14px; +} + .post-right-root-container li { display: inline; list-style-type: none; diff --git a/sass/layout/_post.scss b/sass/layout/_post.scss index 8faa0bba0213..b8a2a98caa14 100644 --- a/sass/layout/_post.scss +++ b/sass/layout/_post.scss @@ -774,6 +774,7 @@ &:hover { .dropdown, .comment-icon__container, + .card-icon__container, .reacticon, .reacticon__container, .flag-icon__container, @@ -799,6 +800,7 @@ &.post--hovered { .dropdown, .comment-icon__container, + .card-icon__container, .post__reply, .post__remove, .post__dropdown, @@ -854,7 +856,7 @@ } - .flag-icon__container { + .flag-icon__container, .card-icon__container { left: 32px; margin-left: 7px; position: absolute; @@ -890,6 +892,14 @@ } } + .Badge { + margin: 0; + } + + .card-icon__container { + margin: 0 7px 0 -4px; + } + .post-message { overflow: inherit; } @@ -1173,7 +1183,7 @@ } } - .flag-icon__container { + .flag-icon__container, .card-icon__container { left: 32px; margin-left: 7px; position: absolute; @@ -1837,6 +1847,29 @@ } } + .card-icon__container { + @extend %btn-transition; + @include opacity(.24); + margin-left: 7px; + vertical-align: top; + top: 2px; + position: relative; + z-index: 1; + + &:hover { + @include opacity(.4); + } + + &.active { + @include opacity(1); + } + + svg { + height: 14px; + width: 14px; + } + } + .flag-icon__container { display: inline-block; height: 15px; @@ -2012,6 +2045,12 @@ } } +.sidebar-right__card { + .post-message--collapsed { + max-height: 150px; + } +} + .post-message--collapsed { .post-message__text-container { // If this max-height is changed, the MAX_POST_HEIGHT constant in diff --git a/sass/layout/_sidebar-right.scss b/sass/layout/_sidebar-right.scss index c6bdf1eff3ea..131fd788e8fa 100644 --- a/sass/layout/_sidebar-right.scss +++ b/sass/layout/_sidebar-right.scss @@ -61,6 +61,10 @@ .post { &.post--compact { + .card-icon__container { + margin: 0px 0 0 6px; + } + .post__pinned-badge { margin: 0 0 0 5px; } @@ -91,7 +95,7 @@ } } - .flag-icon__container { + .flag-icon__container, .card-icon__container { z-index: 5; } } @@ -169,10 +173,15 @@ position: relative; height: 40px; padding: 0px; + .loading__content { height: 40px; } } + + .info-card { + padding: 5px 15px 60px 15px; + } } .sidebar__overlay { diff --git a/sass/responsive/_tablet.scss b/sass/responsive/_tablet.scss index d3f6a0d2d403..a0a05fb4a6c6 100644 --- a/sass/responsive/_tablet.scss +++ b/sass/responsive/_tablet.scss @@ -396,13 +396,13 @@ } } - .flag-icon__container { + .flag-icon__container, .card-icon-container { left: -21px; position: absolute; top: 4px; } - .sidebar--right & .flag-icon__container { + .sidebar--right & .flag-icon__container, .sidebar--right & .card-icon__container { left: auto; position: relative; top: 1px; @@ -413,7 +413,7 @@ padding-left: 77px; padding-top: 0; - .flag-icon__container { + .flag-icon__container, .card-icon__container { left: -21px; position: absolute; top: 4px; @@ -496,7 +496,7 @@ } } - .flag-icon__container { + .flag-icon__container, .card-icon__container { .sidebar--right & { left: 30px; } diff --git a/selectors/rhs.jsx b/selectors/rhs.jsx index 05e4703d0108..83270de56e0c 100644 --- a/selectors/rhs.jsx +++ b/selectors/rhs.jsx @@ -12,6 +12,14 @@ export function getSelectedPostId(state) { return state.views.rhs.selectedPostId; } +export function getSelectedPostCardId(state) { + return state.views.rhs.selectedPostCardId; +} + +export function getSelectedPostCard(state) { + return state.entities.posts.posts[getSelectedPostCardId(state)]; +} + export function getSelectedChannelId(state) { return state.views.rhs.selectedChannelId; } diff --git a/utils/constants.jsx b/utils/constants.jsx index f92a8f139497..50feb8e2916c 100644 --- a/utils/constants.jsx +++ b/utils/constants.jsx @@ -96,6 +96,7 @@ export const Preferences = { export const ActionTypes = keyMirror({ RECEIVED_FOCUSED_POST: null, SELECT_POST: null, + SELECT_POST_CARD: null, INCREASE_POST_VISIBILITY: null, LOADING_POSTS: null, @@ -131,7 +132,9 @@ export const ActionTypes = keyMirror({ RECEIVED_PLUGIN_COMPONENT: null, REMOVED_PLUGIN_COMPONENT: null, RECEIVED_PLUGIN_POST_COMPONENT: null, + RECEIVED_PLUGIN_POST_CARD_COMPONENT: null, REMOVED_PLUGIN_POST_COMPONENT: null, + REMOVED_PLUGIN_POST_CARD_COMPONENT: null, RECEIVED_WEBAPP_PLUGINS: null, RECEIVED_WEBAPP_PLUGIN: null, REMOVED_WEBAPP_PLUGIN: null, diff --git a/utils/utils.jsx b/utils/utils.jsx index 42fffde351e2..923506e022c4 100644 --- a/utils/utils.jsx +++ b/utils/utils.jsx @@ -606,7 +606,7 @@ export function applyTheme(theme) { changeCss('@media(max-width: 768px){.app__body .post .MenuWrapper .dropdown-menu button', 'background:' + theme.centerChannelBg); changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'background:' + theme.centerChannelBg); changeCss('@media(max-width: 320px){.tutorial-steps__container', 'background:' + theme.centerChannelBg); - changeCss('.app__body .bg--white, .app__body .system-notice, .app__body .channel-header__info .channel-header__description:before, .app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span, .app__body .status-wrapper .status, .app__body .alert.alert-transparent, .app__body .post-image__column', 'background:' + theme.centerChannelBg); + changeCss('.app__body .post-card--info, .app__body .bg--white, .app__body .system-notice, .app__body .channel-header__info .channel-header__description:before, .app__body .app__content, .app__body .markdown__table, .app__body .markdown__table tbody tr, .app__body .suggestion-list__content, .app__body .modal .modal-footer, .app__body .post.post--compact .post-image__column, .app__body .suggestion-list__divider > span, .app__body .status-wrapper .status, .app__body .alert.alert-transparent, .app__body .post-image__column', 'background:' + theme.centerChannelBg); changeCss('#post-list .post-list-holder-by-time, .app__body .post .dropdown-menu a, .app__body .post .Menu .MenuItem', 'background:' + theme.centerChannelBg); changeCss('#post-create, .app__body .emoji-picker__preview', 'background:' + theme.centerChannelBg); changeCss('.app__body .modal-content, .app__body .date-separator .separator__text, .app__body .new-separator .separator__text', 'background:' + theme.centerChannelBg); @@ -666,6 +666,7 @@ export function applyTheme(theme) { changeCss('.app__body .mentions__name .status.status--group, .app__body .multi-select__note', 'background:' + changeOpacity(theme.centerChannelColor, 0.12)); changeCss('.app__body .modal-tabs .nav-tabs > li, .app__body .form-control, .app__body .system-notice, .app__body .file-view--single .file__image .image-loaded, .app__body .post .MenuWrapper .dropdown-menu button, .app__body .member-list__popover .more-modal__body, .app__body .alert.alert-transparent, .app__body .channel-header .channel-header__icon, .app__body .search-bar__container .search__form, .app__body .table > thead > tr > th, .app__body .table > tbody > tr > td', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.12)); changeCss('.app__body .post-list__arrows, .app__body .post .flag-icon__container', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3)); + changeCss('.app__body .post .card-icon__container', 'color:' + changeOpacity(theme.centerChannelColor, 0.3)); changeCss('@media(min-width: 768px){.app__body .search__icon svg', 'stroke:' + changeOpacity(theme.centerChannelColor, 0.4)); changeCss('.app__body .post-image__details .post-image__download svg', 'stroke:' + changeOpacity(theme.centerChannelColor, 0.4)); changeCss('.app__body .post-image__details .post-image__download svg', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.35)); @@ -674,7 +675,7 @@ export function applyTheme(theme) { changeCss('.app__body .channel-header__icon .icon--stroke.icon__search svg', 'stroke:' + changeOpacity(theme.centerChannelColor, 0.55)); changeCss('.app__body .modal .status .offline--icon, .app__body .channel-header__links .icon, .app__body .sidebar--right .sidebar--right__subheader .usage__icon, .app__body .more-modal__header svg, .app__body .icon--body', 'fill:' + theme.centerChannelColor); changeCss('@media(min-width: 768px){.app__body .post:hover .post__header .col__reply, .app__body .post.post--hovered .post__header .col__reply', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); - changeCss('.app__body .MenuWrapper .MenuItem__divider, .app__body .modal .about-modal .modal-content .modal-header, .post .attachment .attachment__image.attachment__image--opengraph, .app__body .DayPicker .DayPicker-Caption, .app__body .modal .settings-modal .team-img-preview div, .app__body .modal .settings-modal .team-img__container div, .app__body .system-notice__footer, .app__body .system-notice__footer .btn:last-child, .app__body .modal .shortcuts-modal .subsection, .app__body .sidebar--right .sidebar--right__header, .app__body .channel-header, .app__body .nav-tabs > li > a:hover, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:focus, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:hover, .app__body .post .dropdown-menu a, .sidebar--left, .app__body .suggestion-list__content .command, .app__body .channel-archived__message', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); + changeCss('.app__body .MenuWrapper .MenuItem__divider, .app__body .modal .about-modal .modal-content .modal-header, .post .attachment .attachment__image.attachment__image--opengraph, .app__body .DayPicker .DayPicker-Caption, .app__body .modal .settings-modal .team-img-preview div, .app__body .modal .settings-modal .team-img__container div, .app__body .system-notice__footer, .app__body .system-notice__footer .btn:last-child, .app__body .modal .shortcuts-modal .subsection, .app__body .sidebar--right .sidebar--right__header, .app__body .channel-header, .app__body .nav-tabs > li > a:hover, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:focus, .app__body .nav-tabs, .app__body .nav-tabs > li.active > a:hover, .app__body .post .dropdown-menu a, .sidebar--left, .app__body .suggestion-list__content .command, .app__body .channel-archived__message, .app__body .post-card--info', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2)); changeCss('.app__body .help-text, .app__body .post .post-waiting, .app__body .post.post--system .post__body, .app__body .modal .channel-switch-modal .modal-header .close', 'color:' + changeOpacity(theme.centerChannelColor, 0.6)); changeCss('.app__body .nav-tabs, .app__body .nav-tabs > li.active > a, pp__body .input-group-addon, .app__body .app__content, .app__body .post-create__container .post-create-body .btn-file, .app__body .post-create__container .post-create-footer .msg-typing, .app__body .suggestion-list__content .command, .app__body .modal .modal-content, .app__body .dropdown-menu, .app__body .popover, .app__body .mentions__name, .app__body .tip-overlay, .app__body .form-control[disabled], .app__body .form-control[readonly], .app__body fieldset[disabled] .form-control', 'color:' + theme.centerChannelColor); changeCss('.app__body .post .post__link', 'color:' + changeOpacity(theme.centerChannelColor, 0.65)); @@ -908,7 +909,7 @@ export function applyTheme(theme) { changeCss('.app__body .channel-header .channel-header_plugin-dropdown a:hover, .app__body .member-list__popover .more-modal__list .more-modal__row:hover', 'background:' + changeOpacity(theme.linkColor, 0.08)); changeCss('.app__body .channel-header__links .icon:hover, .app__body .channel-header__links > a.active .icon, .app__body .post .flag-icon__container.visible, .app__body .post .reacticon__container, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor); changeCss('@media(min-width: 768px){.app__body .search__form.focused .search__icon svg, .app__body .search__form:hover .search__icon svg', 'stroke:' + theme.linkColor); - changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor); + changeCss('.app__body .channel-header__links .icon:hover, .app__body .post .flag-icon__container.visible, .app__body .post .card-icon__container.active svg, .app__body .post .comment-icon__container, .app__body .post .post__reply', 'fill:' + theme.linkColor); changeCss('.app__body .channel-header .channel-header__icon:hover #member_popover, .app__body .channel-header .channel-header__icon.active #member_popover', 'color:' + theme.linkColor); changeCss('.app__body .channel-header .pinned-posts-button:hover svg', 'fill:' + changeOpacity(theme.linkColor, 0.6)); changeCss('.app__body .member-list__popover .more-modal__actions svg, .app__body .channel-header .channel-header__icon:hover svg, .app__body .channel-header .channel-header__icon.active svg', 'fill:' + theme.linkColor);