MM-15219 Added support for introduction text in interactive dialogs (#3222)

* MM-15219 Added support for optional introduction text in interactive dialogs

* Fixed margins in introduction text for interactive dialogs

* Created DialogIntroductionText component to render the introduction text.  Changed text color

* Changed DialogIntroductionText component to only render if a value is supplied
This commit is contained in:
Theo Gkourasas 2019-09-16 12:22:59 -04:00 committed by Miguel Alatzar
parent 19e6b2a00e
commit 30f91a3814
8 changed files with 321 additions and 2 deletions

View file

@ -55,7 +55,7 @@ describe('InteractiveDialogController', () => {
});
});
function getBaseProps(triggerId, elements) {
function getBaseProps(triggerId, elements, introductionText) {
const dialogData = {
dialog: {
callback_id: 'somecallbackid',
@ -65,6 +65,7 @@ function getBaseProps(triggerId, elements) {
state: 'somestate',
submit_label: 'Submit Test',
title: 'Dialog Test',
introductionText,
},
trigger_id: triggerId,
url: 'https://localhost:8065/dialog_submit',

View file

@ -0,0 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DialogIntroductionText should not render the component with an empty value 1`] = `null`;
exports[`DialogIntroductionText should render the introduction text correctly 1`] = `
<View
style={
Object {
"marginHorizontal": 15,
}
}
>
<Connect(Markdown)
baseTextStyle={
Object {
"color": "#3d3c40",
}
}
blockStyles={
Object {
"adjacentParagraph": Object {
"marginTop": 6,
},
"horizontalRule": Object {
"backgroundColor": "#3d3c40",
"height": 0.5,
"marginVertical": 10,
},
"quoteBlockIcon": Object {
"color": "rgba(61,60,64,0.5)",
"padding": 5,
},
}
}
disableAtMentions={true}
disableChannelLink={true}
disableHashtags={true}
textStyles={
Object {
"code": Object {
"alignSelf": "center",
"backgroundColor": "rgba(61,60,64,0.07)",
"fontFamily": "Menlo",
},
"codeBlock": Object {
"fontFamily": "Menlo",
},
"del": Object {
"textDecorationLine": "line-through",
},
"emph": Object {
"fontStyle": "italic",
},
"error": Object {
"color": "#fd5960",
},
"heading1": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading1Text": Object {
"paddingBottom": 8,
},
"heading2": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading2Text": Object {
"paddingBottom": 8,
},
"heading3": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading3Text": Object {
"paddingBottom": 8,
},
"heading4": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading4Text": Object {
"paddingBottom": 8,
},
"heading5": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading5Text": Object {
"paddingBottom": 8,
},
"heading6": Object {
"fontSize": 17,
"fontWeight": "700",
"lineHeight": 25,
},
"heading6Text": Object {
"paddingBottom": 8,
},
"link": Object {
"color": "#2389d7",
},
"mention": Object {
"color": "#2389d7",
},
"mention_highlight": Object {
"backgroundColor": "#ffe577",
},
"strong": Object {
"fontWeight": "bold",
},
"table_header_row": Object {
"fontWeight": "700",
},
}
}
value="**bold** *italic* [link](https://mattermost.com/) <br/> [link target blank](!https://mattermost.com/)"
/>
</View>
`;

View file

@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`InteractiveDialog should display introduction text if present 1`] = `
<View
style={
Object {
"backgroundColor": "rgba(61,60,64,0.03)",
}
}
>
<ScrollViewMock
style={
Object {
"marginBottom": 20,
"marginTop": 10,
}
}
>
<Connect(StatusBar) />
<DialogIntroductionText
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
value="**Some** _introduction_ text"
/>
</ScrollViewMock>
</View>
`;

View file

@ -0,0 +1,58 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import Markdown from 'app/components/markdown';
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class DialogIntroductionText extends PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
};
render() {
const {
value,
theme,
} = this.props;
if (value) {
const style = getStyleFromTheme(theme);
const blockStyles = getMarkdownBlockStyles(theme);
const textStyles = getMarkdownTextStyles(theme);
return (
<View style={style.introductionTextView}>
<Markdown
baseTextStyle={style.introductionText}
textStyles={textStyles}
blockStyles={blockStyles}
value={value}
disableHashtags={true}
disableAtMentions={true}
disableChannelLink={true}
/>
</View>
);
}
return null;
}
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
return {
introductionTextView: {
marginHorizontal: 15,
},
introductionText: {
color: theme.centerChannelColor,
},
};
});

View file

@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import Preferences from 'mattermost-redux/constants/preferences';
import DialogIntroductionText from './dialog_introduction_text.js';
describe('DialogIntroductionText', () => {
const baseProps = {
theme: Preferences.THEMES.default,
value: '**bold** *italic* [link](https://mattermost.com/) <br/> [link target blank](!https://mattermost.com/)',
};
test('should render the introduction text correctly', () => {
const wrapper = shallow(
<DialogIntroductionText
{...baseProps}
/>
);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should not render the component with an empty value', () => {
baseProps.value = '';
const wrapper = shallow(
<DialogIntroductionText
{...baseProps}
/>
);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View file

@ -19,6 +19,7 @@ function mapStateToProps(state) {
callbackId: data.dialog.callback_id,
elements: data.dialog.elements,
title: data.dialog.title,
introductionText: data.dialog.introduction_text,
iconUrl: data.dialog.icon_url,
submitLabel: data.dialog.submit_label,
notifyOnCancel: data.dialog.notify_on_cancel,

View file

@ -14,11 +14,13 @@ import StatusBar from 'app/components/status_bar';
import FormattedText from 'app/components/formatted_text';
import DialogElement from './dialog_element.js';
import DialogIntroductionText from './dialog_introduction_text.js';
export default class InteractiveDialog extends PureComponent {
static propTypes = {
url: PropTypes.string.isRequired,
callbackId: PropTypes.string,
introductionText: PropTypes.string,
elements: PropTypes.arrayOf(PropTypes.object),
notifyOnCancel: PropTypes.bool,
state: PropTypes.string,
@ -150,13 +152,19 @@ export default class InteractiveDialog extends PureComponent {
}
render() {
const {elements, theme} = this.props;
const {introductionText, elements, theme} = this.props;
const style = getStyleFromTheme(theme);
return (
<View style={style.container}>
<ScrollView style={style.scrollView}>
<StatusBar/>
{Boolean(introductionText) &&
<DialogIntroductionText
value={introductionText}
theme={theme}
/>
}
{elements && elements.map((e) => {
return (
<DialogElement

View file

@ -106,4 +106,39 @@ describe('InteractiveDialog', () => {
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
});
test('should display introduction text if present', async () => {
baseProps.introductionText = '**Some** _introduction_ text';
const wrapper = shallow(
<InteractiveDialog
{...baseProps}
notifyOnCancel={false}
/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('introduction text should not affect submission', async () => {
baseProps.introductionText = '**Some** _introduction_ text';
const wrapper = shallow(
<InteractiveDialog
{...baseProps}
notifyOnCancel={false}
/>,
);
const dialog = {
url: baseProps.url,
callback_id: baseProps.callbackId,
state: baseProps.state,
submission: {},
};
wrapper.instance().handleSubmit();
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledTimes(1);
expect(baseProps.actions.submitInteractiveDialog).toHaveBeenCalledWith(dialog);
});
});