Skip to content

Commit

Permalink
feat(core/presentation): allow markdown in ValidationMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Oct 11, 2019
1 parent aef69cf commit 075bce1
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 20 deletions.
9 changes: 9 additions & 0 deletions app/scripts/modules/core/src/presentation/Markdown.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.Markdown {
p {
margin: 0;
}

p + p {
margin-top: 4px;
}
}
36 changes: 18 additions & 18 deletions app/scripts/modules/core/src/presentation/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import * as React from 'react';
import { HtmlRenderer, Parser } from 'commonmark';
import * as DOMPurify from 'dompurify';
import './Markdown.less';

export interface IMarkdownProps {
[key: string]: any;

/** markdown */
message: string;

/** optional tag */
tag?: string;

/** The className(s) to apply to the tag (.Markdown class is always applied) */
className?: string;
}

/**
* Renders markdown into a div (or some other tag)
* Extra props are passed through to the rendered tag
*/
export class Markdown extends React.Component<IMarkdownProps> {
public static defaultProps: Partial<IMarkdownProps> = {
tag: 'div',
};

private parser: Parser = new Parser();
private renderer: HtmlRenderer = new HtmlRenderer();
export function Markdown(props: IMarkdownProps) {
const { message, tag: tagProp, className: classNameProp, tag = 'div', ...rest } = props;

public render() {
const { message, tag, ...rest } = this.props;
const parser = React.useMemo(() => new Parser(), []);
const renderer = React.useMemo(() => new HtmlRenderer(), []);
const className = `Markdown ${classNameProp || ''}`;

if (message == null) {
return null;
}
if (message == null) {
return null;
}

const restProps = rest as React.DOMAttributes<any>;
const parsed = this.parser.parse(message.toString());
const rendered = this.renderer.render(parsed);
restProps.dangerouslySetInnerHTML = { __html: DOMPurify.sanitize(rendered) };
const restProps = rest as React.DOMAttributes<any>;
const parsed = parser.parse(message.toString());
const rendered = renderer.render(parsed);
restProps.dangerouslySetInnerHTML = { __html: DOMPurify.sanitize(rendered) };

return React.createElement(tag, restProps);
}
return React.createElement(tag, { ...restProps, className });
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as React from 'react';
import { isString } from 'lodash';

import { Markdown } from '../../Markdown';
import { ICategorizedErrors, IValidationCategory } from './categories';
import './ValidationMessage.less';

Expand Down Expand Up @@ -43,7 +46,7 @@ export const ValidationMessage = (props: IValidationMessageProps) => {
return (
<div className={`ValidationMessage ${containerClassName || containerClassNames[type] || ''}`}>
{showIcon && <i className={iconClassName || iconClassNames[type] || ''} />}
<div className="message">{message}</div>
<div className="message">{isString(message) ? <Markdown message={message} /> : message}</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ describe('categorizeErrorMessage', () => {
it('returns the error message without the label prefix', () => {
expect(categorizeValidationMessage('Warning: something sorta bad')[1]).toEqual('something sorta bad');
});

it('supports newlines embedded in the message', () => {
const [status, message] = categorizeValidationMessage('Warning: something sorta bad\n\nhappened');
expect(status).toBe('warning');
expect(message).toBe('something sorta bad\n\nhappened');
});
});

describe('category message builder', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const warningMessage = buildCategoryMessage('warning');
// A regular expression which captures the category label and validation message from a validation message
// I.e., for the string: "Error: There was a fatal error"
// this captures "Error" and "There was a fatal error"
const validationMessageRegexp = new RegExp(`^(${labels.join('|')}): (.*)$`);
const validationMessageRegexp = new RegExp(`^(${labels.join('|')}): (.*)$`, 'sm');

// Takes an errorMessage with embedded category and extracts the category and message
// Example: "Error: there was an error" => ['error', 'there was an error']
Expand Down

0 comments on commit 075bce1

Please sign in to comment.