Merge pull request #3157 from SDA-SE/feat/formatted-react-markdown

feat: add MarkdownContent component to @backstage/core
This commit is contained in:
Patrik Oldsberg
2020-11-04 11:14:49 +01:00
committed by GitHub
8 changed files with 312 additions and 2 deletions
+16
View File
@@ -0,0 +1,16 @@
---
'@backstage/core': patch
---
Adds the MarkdownContent component to render and display Markdown content with the default
[GFM](https://github.github.com/gfm/) (Github flavored Markdown) dialect.
```
<MarkdownContent content={markdownGithubFlavored} />
```
To render the Markdown content with plain [CommonMark](https://commonmark.org/), set the dialect to `common-mark`
```
<MarkdownContent content={markdown} dialect='common-mark />
```
+3 -1
View File
@@ -48,11 +48,13 @@
"react-dom": "^16.12.0",
"react-helmet": "6.1.0",
"react-hook-form": "^6.6.0",
"react-markdown": "^5.0.2",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-sparklines": "^1.7.0",
"react-syntax-highlighter": "^13.5.1",
"react-use": "^15.3.3"
"react-use": "^15.3.3",
"remark-gfm": "^1.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.2.0",
@@ -0,0 +1,120 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { MarkdownContent } from './MarkdownContent';
export default {
title: 'Data Display/MarkdownContent',
component: MarkdownContent,
};
const markdownGithubFlavored =
'# GFM\n' +
'\n' +
'## Autolink literals\n' +
'\n' +
'www.example.com, https://example.com, and contact@example.com.\n' +
'\n' +
'## Strikethrough\n' +
'\n' +
'~one~ or ~~two~~ tildes.\n' +
'\n' +
'## Table\n' +
'\n' +
'| foo | bar |\n' +
'| --- | --- |\n' +
'| baz | bim |\n' +
'| buz | bum |\n' +
'| biz | bim |\n' +
'\n' +
'## Tasklist\n' +
'\n' +
'* [ ] to do\n' +
'* [x] done';
const markdown =
'# Choreas Iovis\n' +
'\n' +
'## Incedere retenta\n' +
'\n' +
'Lorem markdownum velamina [nupta amici aequoreis](http://est-quae.org/sic)\n' +
'desertum factum premunt: falcato parvos nihil. Facietque vulnus tum dumque\n' +
'reserato Maeandros insignia solidis, tot longi causa et nimium arcuerat altera\n' +
'unus, in quis.\n' +
'\n' +
'1. Est qui dixere nullus\n' +
'2. Fuit obicit\n' +
'3. Vim patrem portae materiem ulla quod crater\n' +
'4. Rigido est magis raptor quid crepitante aequa\n' +
'5. Imago quis ignis tamen\n' +
'\n' +
'## Vix posse vestem\n' +
'\n' +
'Nec deos robora visa pater toris remittit *crimina* utque, ora ego lacerae quae\n' +
'laboris laturus silvas audax terrae. Qua fuisse patrio inlaesas [sine\n' +
'seque](http://ambitvictore.org/), nondum et tamen annis, nec. Poscimur magnum,\n' +
'Hesperium dedisti, ait ipse et fides terras scalas.\n' +
'\n' +
'- Quas superis satyri adloquitur natura hausimus\n' +
'- Dux suspicere siccare\n' +
'- Cape huc quid videor\n' +
'- Foret vivit concolor\n' +
'- Occupat morte oblectamina minuunt quaeque placidis nate\n' +
'- Non posset' +
'\n' +
'Licet movitque dederat potest in sorores in sola pendere luce pro quod, sit.\n' +
'Inpia ut in opibus flores uno quam quo multifidasque fera anhelitus retorsit.\n' +
'Sustinui premebat puppe somnos. Dicit genu sic qualia excussit facunde parvae in\n' +
'robur, Ianthe Interea. Superis victorque ponat puta cum: est enim.\n' +
'\n' +
'## Tacetve est in nullis Cerberus silvani luminibus\n' +
'\n' +
'Divulsere *summissoque esse manes*; artus ausus conatoque utque: illo\n' +
'Phaestiadas quod pascat et referentem, nec. In seris, iubebat iam nomina:\n' +
'tergoque occidit ingenii.\n' +
'\n' +
' mouseDdl(tablet_definition * phishing_icann_mamp);\n' +
' vector += 20 + key_ram.source_isa(hard_tunneling_zone(w_wireless_page));\n' +
' if (rate_client_direct) {\n' +
' textXDpi += sql_cloud_class.sdk.speakers_wired_warm(pcZettabyteGis(\n' +
' market_bezel, 1), 1);\n' +
' tag_scraping = format_ppi;\n' +
' }\n' +
'\n' +
'Per quem, nec formosior qui cum Peliden me interea **ornatos**! Te facit\n' +
'instimulat sequentia in flumina exilium te vulnere, sola. Coetum nec amnes.\n' +
'Protinus nam Caras cava, *a* vocantem dicta inevitabile, nata nulla.\n' +
'\n' +
'## Piscem Iunoni maius\n' +
'\n' +
'Prece fallere arduus, *ad Athamantis laticem* simillima in ante Temesesque opus!\n' +
'Ausim quoslibet crede Tyria: Medusa [muneris Aeneaden\n' +
'tutaque](http://cragon-aequoribus.io/) genitor fistula cogeris abstrahere nati,\n' +
'relevare videri *non*.\n' +
'\n' +
'> Promissas ulterius senectae Desinet his ait pedum! Libet *sublime* vibrantia\n' +
'> si *dicta quod* pectora cupidine hastam dominoque.\n' +
'\n' +
'Pedis hic, est bis quod, adhaeret et reditum. Fixa sic vel pugnare **forte est**\n' +
'parte in quaerite generisque repugnat; de quod, creatos.';
export const MarkdownContentCommonMark = () => (
<MarkdownContent content={markdown} dialect="common-mark" />
);
export const MarkdownContentGithubFlavoredCommonMark = () => (
<MarkdownContent content={markdownGithubFlavored} dialect="gfm" />
);
@@ -0,0 +1,65 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
import { MarkdownContent } from './MarkdownContent';
describe('<MarkdownContent />', () => {
it('render MarkdownContent component', async () => {
const rendered = await renderWithEffects(
wrapInTestApp(
<MarkdownContent content={'# H1\n' + '## H2\n' + '### H3'} />,
),
);
expect(rendered.getByText('H1', { selector: 'h1' })).toBeInTheDocument();
expect(rendered.getByText('H2', { selector: 'h2' })).toBeInTheDocument();
expect(rendered.getByText('H3', { selector: 'h3' })).toBeInTheDocument();
});
it('render MarkdownContent component with GitHub flavored Markdown dialect', async () => {
const rendered = await renderWithEffects(
wrapInTestApp(<MarkdownContent content="https://example.com" />),
);
expect(
rendered.getByText('https://example.com', { selector: 'a' }),
).toBeInTheDocument();
});
it('Render MarkdownContent component with common mark dialect', async () => {
const rendered = await renderWithEffects(
wrapInTestApp(
<MarkdownContent content="https://example.com" dialect="common-mark" />,
),
);
expect(
rendered.getByText('https://example.com', { selector: 'p' }),
).toBeInTheDocument();
});
it('render MarkdownContent component with CodeSnippet for code blocks', async () => {
const rendered = await renderWithEffects(
wrapInTestApp(<MarkdownContent content=" jest(test: string);" />),
);
const fp1 = rendered.getByText('jest', { selector: 'span' });
expect(fp1).toBeInTheDocument();
expect(fp1.className).toEqual('hljs-function');
const fp2 = rendered.getByText('(test: string)', { selector: 'span' });
expect(fp2).toBeInTheDocument();
expect(fp2.className).toEqual('hljs-function');
expect(rendered.getByText(';', { selector: 'span' })).toBeInTheDocument();
});
});
@@ -0,0 +1,89 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { makeStyles } from '@material-ui/core';
import ReactMarkdown from 'react-markdown';
import gfm from 'remark-gfm';
import React from 'react';
import { BackstageTheme } from '@backstage/theme';
import { CodeSnippet } from '../CodeSnippet';
const useStyles = makeStyles<BackstageTheme>(theme => ({
markdown: {
'& table': {
borderCollapse: 'collapse',
border: `1px solid ${theme.palette.border}`,
},
'& th, & td': {
border: `1px solid ${theme.palette.border}`,
padding: theme.spacing(1),
},
'& td': {
wordBreak: 'break-word',
overflow: 'hidden',
verticalAlign: 'middle',
lineHeight: '1',
margin: 0,
padding: theme.spacing(3, 2, 3, 2.5),
borderBottom: 0,
},
'& th': {
backgroundColor: theme.palette.background.paper,
},
'& tr': {
backgroundColor: theme.palette.background.paper,
},
'& tr:nth-child(odd)': {
backgroundColor: theme.palette.background.default,
},
'& a': {
color: theme.palette.link,
},
'& img': {
maxWidth: '100%',
},
},
}));
type Props = {
content: string;
dialect?: 'gfm' | 'common-mark';
};
const renderers = {
code: ({ language, value }: { language: string; value: string }) => {
return <CodeSnippet language={language} text={value} />;
},
};
/**
* MarkdownContent
* --
* Renders markdown with the default dialect [gfm - GitHub flavored Markdown](https://github.github.com/gfm/) to backstage theme styled HTML.
* If you just want to render to plain [CommonMark](https://commonmark.org/), set the dialect to `'common-mark'`
*/
export const MarkdownContent = ({ content, dialect = 'gfm' }: Props) => {
const classes = useStyles();
return (
<ReactMarkdown
plugins={dialect === 'gfm' ? [gfm] : []}
className={classes.markdown}
children={content}
renderers={renderers}
/>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { MarkdownContent } from './MarkdownContent';
+1
View File
@@ -35,3 +35,4 @@ export * from './Tabs';
export * from './TrendLine';
export * from './WarningPanel';
export * from './EmptyState';
export * from './MarkdownContent';
+1 -1
View File
@@ -19833,7 +19833,7 @@ react-markdown@^4.3.1:
unist-util-visit "^1.3.0"
xtend "^4.0.1"
react-markdown@^5.0.0:
react-markdown@^5.0.0, react-markdown@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-5.0.2.tgz#d15a8beb37b4ec34fc23dd892e7755eb7040b8db"
integrity sha512-kmkB4JbV7LqkDAjvaKRKtodB3n3Id76/DalaDun1U8FuLB0SenPfvH+jAQ5Pcpo54cACRQc1LB1yXmuuuIVecw==