Merge pull request #3157 from SDA-SE/feat/formatted-react-markdown
feat: add MarkdownContent component to @backstage/core
This commit is contained in:
@@ -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 />
|
||||
```
|
||||
@@ -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';
|
||||
@@ -35,3 +35,4 @@ export * from './Tabs';
|
||||
export * from './TrendLine';
|
||||
export * from './WarningPanel';
|
||||
export * from './EmptyState';
|
||||
export * from './MarkdownContent';
|
||||
|
||||
@@ -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==
|
||||
|
||||
Reference in New Issue
Block a user