Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Markdown dedent, and remove containerProps #569

Merged
merged 9 commits into from
Jun 14, 2019
58 changes: 54 additions & 4 deletions src/components/Markdown.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class DashMarkdown extends Component {
constructor(props) {
super(props);
this.highlightCode = this.highlightCode.bind(this);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently not needed given our current usage, but makes me nervous to have it there unbound...

this.dedent = this.dedent.bind(this);
}

componentDidMount() {
Expand All @@ -38,6 +39,40 @@ class DashMarkdown extends Component {
}
}

dedent(text) {
const lines = text.split(/\r\n|\r|\n/);
let commonPrefix = null;
for (const line of lines) {
const preMatch = line && line.match(/^\s*(?=\S)/);
if (preMatch) {
const prefix = preMatch[0];
if (commonPrefix !== null) {
for (let i = 0; i < commonPrefix.length; i++) {
// Like Python's textwrap.dedent, we'll remove both
// space and tab characters, but only if they match
if (prefix[i] !== commonPrefix[i]) {
commonPrefix = commonPrefix.substr(0, i);
break;
}
}
} else {
commonPrefix = prefix;
}

if (!commonPrefix) {
break;
}
}
}

const commonLen = commonPrefix ? commonPrefix.length : 0;
return lines
.map(line => {
return line.match(/\S/) ? line.substr(commonLen) : '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

})
.join('\n');
}

render() {
const {
id,
Expand All @@ -46,11 +81,14 @@ class DashMarkdown extends Component {
highlight_config,
loading_state,
dangerously_allow_html,
children,
dedent,
} = this.props;

if (type(this.props.children) === 'Array') {
this.props.children = this.props.children.join('\n');
}
const textProp =
type(children) === 'Array' ? children.join('\n') : children;
const displayText =
dedent && textProp ? this.dedent(textProp) : textProp;

return (
<div
Expand All @@ -75,7 +113,7 @@ class DashMarkdown extends Component {
}
>
<Markdown
source={this.props.children}
source={displayText}
escapeHtml={!dangerously_allow_html}
/>
</div>
Expand Down Expand Up @@ -111,10 +149,21 @@ DashMarkdown.propTypes = {
PropTypes.arrayOf(PropTypes.string),
]),

/**
* Remove matching leading whitespace from all lines.
* Lines that are empty, or contain *only* whitespace, are ignored.
* Both spaces and tab characters are removed, but only if they match;
* we will not convert tabs to spaces or vice versa.
*/
dedent: PropTypes.bool,

/**
* Config options for syntax highlighting.
*/
highlight_config: PropTypes.exact({
/**
* Color scheme; default 'light'
*/
theme: PropTypes.oneOf(['dark', 'light']),
}),

Expand Down Expand Up @@ -145,6 +194,7 @@ DashMarkdown.propTypes = {
DashMarkdown.defaultProps = {
dangerously_allow_html: false,
highlight_config: {},
dedent: true,
};

export default DashMarkdown;
77 changes: 77 additions & 0 deletions test/unit/Markdown.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import Markdown from '../../src/components/Markdown.react.js';
import React from 'react';
import {shallow, render} from 'enzyme';

test('Input renders', () => {
const md = render(<Markdown />);

expect(md.html()).toBeDefined();
});

describe('dedent', () => {
const md = shallow(<Markdown />).instance();

test('leading spaces and tabs are removed from a single line', () => {
[
'test',
' test',
' test',
'\t\t\ttest',
' \t test',
'\t \ttest',
].forEach(s => {
expect(md.dedent(s)).toEqual('test');
});

expect(md.dedent(' test ')).toEqual('test ');
});

test('same chars are removed from multiple lines, ignoring blanks', () => {
['', ' ', '\t', '\t\t', ' ', '\t \t'].forEach(pre => {
expect(
md.dedent(
pre +
'a\n' +
pre +
' b\r' +
pre +
'c\r\n' +
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prettier certainly did not improve clarity here... and its indentation choice is peculiar for this case. Oh well...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot partially disable just for this chunk?

pre +
'\td\n' +
'\t\n' +
'\n' +
pre +
'e\n' +
'\n' +
pre +
'f'
)
).toEqual(
'a\n' +
' b\n' +
'c\n' +
'\td\n' +
'\n' +
'\n' +
'e\n' +
'\n' +
'f'
);
});
});

test('mismatched chars are not removed', () => {
expect(md.dedent(' \ta\n\t b')).toEqual(' \ta\n\t b');
});

test('the dedent prop controls behavior', () => {
const text = ' a\n b';
const mdDedented = render(<Markdown children={text} />);
expect(mdDedented.find('code').length).toEqual(0);
expect(mdDedented.find('p').length).toEqual(1);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dedented: makes everything into a <p> element.


const mdRaw = render(<Markdown children={text} dedent={false} />);
expect(mdRaw.find('code').length).toEqual(1);
expect(mdRaw.find('p').length).toEqual(0);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dedent turned off: the leading spaced mean it gets turned into a code block.

});
});