MM-10012 Added Markdown rendering to expanded announcement banner (#1861)
* MM-10012 Added Markdown rendering to expanded announcement banner * Updated snapshots * Re-added dismiss button to expanded announcement banner * Updated 'Don't show again' button style'
This commit is contained in:
parent
76a82aa627
commit
da8b5e92cc
12 changed files with 297 additions and 80 deletions
|
|
@ -5,12 +5,6 @@ ShallowWrapper {
|
|||
"length": 1,
|
||||
Symbol(enzyme.__root__): [Circular],
|
||||
Symbol(enzyme.__unrendered__): <AnnouncementBanner
|
||||
actions={
|
||||
Object {
|
||||
"dismissBanner": [MockFunction],
|
||||
}
|
||||
}
|
||||
allowDismissal={true}
|
||||
bannerColor="#ddd"
|
||||
bannerDismissed={false}
|
||||
bannerEnabled={true}
|
||||
|
|
@ -58,7 +52,9 @@ ShallowWrapper {
|
|||
]
|
||||
}
|
||||
>
|
||||
Banner Text
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
|
|
@ -107,7 +103,9 @@ ShallowWrapper {
|
|||
]
|
||||
}
|
||||
>
|
||||
Banner Text
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>,
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
|
|
@ -132,7 +130,9 @@ ShallowWrapper {
|
|||
"props": Object {
|
||||
"accessible": true,
|
||||
"allowFontScaling": true,
|
||||
"children": "Banner Text",
|
||||
"children": <RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>,
|
||||
"ellipsizeMode": "tail",
|
||||
"numberOfLines": 1,
|
||||
"style": Array [
|
||||
|
|
@ -147,7 +147,17 @@ ShallowWrapper {
|
|||
],
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": "Banner Text",
|
||||
"rendered": Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"value": "Banner Text",
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
"type": [Function],
|
||||
},
|
||||
Object {
|
||||
|
|
@ -204,7 +214,9 @@ ShallowWrapper {
|
|||
]
|
||||
}
|
||||
>
|
||||
Banner Text
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
|
|
@ -253,7 +265,9 @@ ShallowWrapper {
|
|||
]
|
||||
}
|
||||
>
|
||||
Banner Text
|
||||
<RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>,
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
|
|
@ -278,7 +292,9 @@ ShallowWrapper {
|
|||
"props": Object {
|
||||
"accessible": true,
|
||||
"allowFontScaling": true,
|
||||
"children": "Banner Text",
|
||||
"children": <RemoveMarkdown
|
||||
value="Banner Text"
|
||||
/>,
|
||||
"ellipsizeMode": "tail",
|
||||
"numberOfLines": 1,
|
||||
"style": Array [
|
||||
|
|
@ -293,7 +309,17 @@ ShallowWrapper {
|
|||
],
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": "Banner Text",
|
||||
"rendered": Object {
|
||||
"instance": null,
|
||||
"key": undefined,
|
||||
"nodeType": "class",
|
||||
"props": Object {
|
||||
"value": "Banner Text",
|
||||
},
|
||||
"ref": null,
|
||||
"rendered": null,
|
||||
"type": [Function],
|
||||
},
|
||||
"type": [Function],
|
||||
},
|
||||
Object {
|
||||
|
|
@ -331,12 +357,6 @@ ShallowWrapper {
|
|||
"length": 1,
|
||||
Symbol(enzyme.__root__): [Circular],
|
||||
Symbol(enzyme.__unrendered__): <AnnouncementBanner
|
||||
actions={
|
||||
Object {
|
||||
"dismissBanner": [MockFunction],
|
||||
}
|
||||
}
|
||||
allowDismissal={true}
|
||||
bannerColor="#ddd"
|
||||
bannerDismissed={false}
|
||||
bannerEnabled={false}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Alert,
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Text,
|
||||
|
|
@ -13,19 +12,19 @@ import {
|
|||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import RemoveMarkdown from 'app/components/remove_markdown';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class AnnouncementBanner extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissBanner: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
allowDismissal: PropTypes.bool,
|
||||
bannerColor: PropTypes.string,
|
||||
bannerDismissed: PropTypes.bool,
|
||||
bannerEnabled: PropTypes.bool,
|
||||
bannerText: PropTypes.string,
|
||||
bannerTextColor: PropTypes.string,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -52,30 +51,24 @@ export default class AnnouncementBanner extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleDismiss = () => {
|
||||
const {actions, bannerText} = this.props;
|
||||
actions.dismissBanner(bannerText);
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const options = [{
|
||||
text: formatMessage({id: 'mobile.announcement_banner.ok', defaultMessage: 'OK'}),
|
||||
}];
|
||||
const {navigator, theme} = this.props;
|
||||
|
||||
if (this.props.allowDismissal) {
|
||||
options.push({
|
||||
text: formatMessage({id: 'mobile.announcement_banner.dismiss', defaultMessage: 'Dismiss'}),
|
||||
onPress: this.handleDismiss,
|
||||
});
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
formatMessage({id: 'mobile.announcement_banner.title', defaultMessage: 'Announcement'}),
|
||||
this.props.bannerText,
|
||||
options,
|
||||
{cancelable: false}
|
||||
);
|
||||
navigator.push({
|
||||
screen: 'ExpandedAnnouncementBanner',
|
||||
title: this.context.intl.formatMessage({
|
||||
id: 'mobile.announcement_banner.title',
|
||||
defaultMessage: 'Announcement',
|
||||
}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
toggleBanner = (show = true) => {
|
||||
|
|
@ -120,7 +113,7 @@ export default class AnnouncementBanner extends PureComponent {
|
|||
numberOfLines={1}
|
||||
style={[style.bannerText, bannerTextStyle]}
|
||||
>
|
||||
{bannerText}
|
||||
<RemoveMarkdown value={bannerText}/>
|
||||
</Text>
|
||||
<MaterialIcons
|
||||
color={bannerTextColor}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ jest.useFakeTimers();
|
|||
|
||||
describe('AnnouncementBanner', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
dismissBanner: jest.fn(),
|
||||
},
|
||||
allowDismissal: true,
|
||||
bannerColor: '#ddd',
|
||||
bannerDismissed: false,
|
||||
bannerEnabled: true,
|
||||
|
|
@ -33,16 +29,4 @@ describe('AnnouncementBanner', () => {
|
|||
wrapper.setProps({bannerEnabled: false});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call actions.dismissBanner on handleDismiss', () => {
|
||||
const actions = {dismissBanner: jest.fn()};
|
||||
const props = {...baseProps, actions};
|
||||
const wrapper = shallow(
|
||||
<AnnouncementBanner {...props}/>
|
||||
);
|
||||
|
||||
wrapper.instance().handleDismiss();
|
||||
expect(actions.dismissBanner).toHaveBeenCalledTimes(1);
|
||||
expect(actions.dismissBanner).toHaveBeenCalledWith(props.bannerText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {dismissBanner} from 'app/actions/views/announcement';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import AnnouncementBanner from './announcement_banner';
|
||||
|
||||
|
|
@ -16,21 +14,13 @@ function mapStateToProps(state) {
|
|||
const {announcement} = state.views;
|
||||
|
||||
return {
|
||||
allowDismissal: config.AllowBannerDismissal === 'true',
|
||||
bannerColor: config.BannerColor,
|
||||
bannerDismissed: config.BannerText === announcement,
|
||||
bannerEnabled: config.EnableBanner === 'true' && license.IsLicensed === 'true',
|
||||
bannerText: config.BannerText,
|
||||
bannerTextColor: config.BannerTextColor || '#000',
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissBanner,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);
|
||||
export default connect(mapStateToProps)(AnnouncementBanner);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default class ChannelLink extends React.PureComponent {
|
|||
static propTypes = {
|
||||
channelName: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style,
|
||||
onChannelLinkPress: PropTypes.func,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
channelsByName: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
|
|
@ -57,6 +58,10 @@ export default class ChannelLink extends React.PureComponent {
|
|||
handlePress = () => {
|
||||
this.props.actions.setChannelDisplayName(this.state.channel.display_name);
|
||||
this.props.actions.handleSelectChannel(this.state.channel.id);
|
||||
|
||||
if (this.props.onChannelLinkPress) {
|
||||
this.props.onChannelLinkPress(this.state.channel);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export default class Markdown extends PureComponent {
|
|||
isEdited: PropTypes.bool,
|
||||
isSearchResult: PropTypes.bool,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
onChannelLinkPress: PropTypes.func,
|
||||
onLongPress: PropTypes.func,
|
||||
onPermalinkPress: PropTypes.func,
|
||||
onPostPress: PropTypes.func,
|
||||
|
|
@ -194,6 +195,7 @@ export default class Markdown extends PureComponent {
|
|||
<ChannelLink
|
||||
linkStyle={this.props.textStyles.link}
|
||||
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
|
||||
onChannelLinkPress={this.props.onChannelLinkPress}
|
||||
channelName={channelName}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
75
app/components/remove_markdown.js
Normal file
75
app/components/remove_markdown.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Parser} from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
export default class RemoveMarkdown extends React.PureComponent {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.parser = this.createParser();
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
|
||||
createParser = () => {
|
||||
return new Parser();
|
||||
};
|
||||
|
||||
createRenderer = () => {
|
||||
return new Renderer({
|
||||
renderers: {
|
||||
text: this.renderText,
|
||||
|
||||
emph: Renderer.forwardChildren,
|
||||
strong: Renderer.forwardChildren,
|
||||
del: Renderer.forwardChildren,
|
||||
code: Renderer.forwardChildren,
|
||||
link: Renderer.forwardChildren,
|
||||
atMention: Renderer.forwardChildren,
|
||||
channelLink: Renderer.forwardChildren,
|
||||
emoji: this.renderNull,
|
||||
|
||||
paragraph: Renderer.forwardChildren,
|
||||
heading: Renderer.forwardChildren,
|
||||
codeBlock: this.renderNull,
|
||||
blockQuote: this.renderNull,
|
||||
|
||||
list: this.renderNull,
|
||||
item: this.renderNull,
|
||||
|
||||
hardBreak: this.renderNull,
|
||||
thematicBreak: this.renderNull,
|
||||
softBreak: this.renderNull,
|
||||
|
||||
htmlBlock: this.renderNull,
|
||||
htmlInline: this.renderNull,
|
||||
|
||||
table: this.renderNull,
|
||||
table_row: this.renderNull,
|
||||
table_cell: this.renderNull,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
renderText = ({literal}) => {
|
||||
return <Text>{literal}</Text>;
|
||||
};
|
||||
|
||||
renderNull = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const ast = this.parser.parse(this.props.value);
|
||||
|
||||
return <Text>{this.renderer.render(ast)}</Text>;
|
||||
}
|
||||
}
|
||||
|
|
@ -228,7 +228,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
return (
|
||||
<View style={style.container}>
|
||||
{component}
|
||||
<AnnouncementBanner/>
|
||||
<AnnouncementBanner navigator={navigator}/>
|
||||
<RetryBarIndicator/>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
// 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 {ScrollView, View} from 'react-native';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class ExpandedAnnouncementBanner extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissBanner: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
allowDismissal: PropTypes.bool.isRequired,
|
||||
bannerText: PropTypes.string.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.navigator.pop();
|
||||
};
|
||||
|
||||
dismissBanner = () => {
|
||||
this.props.actions.dismissBanner(this.props.bannerText);
|
||||
|
||||
this.close();
|
||||
};
|
||||
|
||||
handleChannelLinkPress = () => {
|
||||
this.close();
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
let dismissButton = null;
|
||||
if (this.props.allowDismissal) {
|
||||
dismissButton = (
|
||||
<View style={style.dismissContainer}>
|
||||
<Button
|
||||
containerStyle={style.dismissButton}
|
||||
onPress={this.dismissBanner}
|
||||
>
|
||||
<FormattedText
|
||||
id='asdf'
|
||||
defaultMessage={'Don\'t show again'}
|
||||
style={style.dismissButtonText}
|
||||
/>
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<ScrollView
|
||||
style={style.scrollContainer}
|
||||
contentContainerStyle={style.textContainer}
|
||||
>
|
||||
<Markdown
|
||||
baseTextStyle={style.baseTextStyle}
|
||||
blockStyles={getMarkdownBlockStyles(this.props.theme)}
|
||||
navigator={this.props.navigator}
|
||||
onChannelLinkPress={this.handleChannelLinkPress}
|
||||
textStyles={getMarkdownTextStyles(this.props.theme)}
|
||||
value={this.props.bannerText}
|
||||
/>
|
||||
</ScrollView>
|
||||
{dismissButton}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
textContainer: {
|
||||
padding: 15,
|
||||
},
|
||||
baseTextStyle: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 15,
|
||||
lineHeight: 20,
|
||||
},
|
||||
dismissContainer: {
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderTopWidth: 1,
|
||||
padding: 10,
|
||||
},
|
||||
dismissButton: {
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
borderRadius: 3,
|
||||
padding: 15,
|
||||
},
|
||||
dismissButtonText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
},
|
||||
};
|
||||
});
|
||||
32
app/screens/expanded_announcement_banner/index.js
Normal file
32
app/screens/expanded_announcement_banner/index.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {dismissBanner} from 'app/actions/views/announcement';
|
||||
|
||||
import ExpandedAnnouncementBanner from './expanded_announcement_banner';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
|
||||
return {
|
||||
allowDismissal: config.AllowBannerDismissal === 'true',
|
||||
bannerText: config.BannerText,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissBanner,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ExpandedAnnouncementBanner);
|
||||
|
|
@ -26,6 +26,7 @@ export function registerScreens(store, Provider) {
|
|||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider);
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider);
|
||||
Navigation.registerComponent('Entry', () => Entry, store, Provider);
|
||||
Navigation.registerComponent('ExpandedAnnouncementBanner', () => wrapWithContextProvider(require('app/screens/expanded_announcement_banner').default), store, Provider);
|
||||
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider);
|
||||
|
|
|
|||
|
|
@ -2307,7 +2307,6 @@
|
|||
"mobile.android.videos_permission_denied_description": "To upload videos from your library, please change your permission settings.",
|
||||
"mobile.android.videos_permission_denied_title": "Video library access is required",
|
||||
"mobile.announcement_banner.dismiss": "Dismiss",
|
||||
"mobile.announcement_banner.ok": "OK",
|
||||
"mobile.announcement_banner.title": "Announcement",
|
||||
"mobile.channel.markAsRead": "Mark As Read",
|
||||
"mobile.channel_drawer.search": "Jump to...",
|
||||
|
|
|
|||
Loading…
Reference in a new issue