From 357f42b6a105d7ce1ed93b66abe10bb1e8e464f7 Mon Sep 17 00:00:00 2001 From: Jesko Steinberg Date: Thu, 29 Oct 2020 13:14:15 +0100 Subject: [PATCH] feat: add MarkdownContent component --- .changeset/wet-tools-exercise.md | 15 +++ packages/core/package.json | 4 +- .../MarkdownContent.stories.tsx | 120 ++++++++++++++++++ .../MarkdownContent/MarkdownContent.test.tsx | 43 +++++++ .../MarkdownContent/MarkdownContent.tsx | 74 +++++++++++ .../src/components/MarkdownContent/index.ts | 17 +++ packages/core/src/components/index.ts | 1 + 7 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 .changeset/wet-tools-exercise.md create mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.stories.tsx create mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.test.tsx create mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.tsx create mode 100644 packages/core/src/components/MarkdownContent/index.ts diff --git a/.changeset/wet-tools-exercise.md b/.changeset/wet-tools-exercise.md new file mode 100644 index 0000000000..ba6eb8314f --- /dev/null +++ b/.changeset/wet-tools-exercise.md @@ -0,0 +1,15 @@ +--- +'@backstage/core': minor +--- + +Adds the MarkdownContent component to render and display markdown input. + +``` + +``` + +Render and display the github flavored markdown [GFM](https://github.github.com/gfm/) input: + +``` + +``` diff --git a/packages/core/package.json b/packages/core/package.json index ec4411b390..a78fcc0a82 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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.1.1-alpha.26", diff --git a/packages/core/src/components/MarkdownContent/MarkdownContent.stories.tsx b/packages/core/src/components/MarkdownContent/MarkdownContent.stories.tsx new file mode 100644 index 0000000000..b3747971a7 --- /dev/null +++ b/packages/core/src/components/MarkdownContent/MarkdownContent.stories.tsx @@ -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 = () => ( + +); + +export const MarkdownContentGithubFlavoredCommonMark = () => ( + +); diff --git a/packages/core/src/components/MarkdownContent/MarkdownContent.test.tsx b/packages/core/src/components/MarkdownContent/MarkdownContent.test.tsx new file mode 100644 index 0000000000..a96525a3f6 --- /dev/null +++ b/packages/core/src/components/MarkdownContent/MarkdownContent.test.tsx @@ -0,0 +1,43 @@ +/* + * 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('', () => { + it('render MarkdownContent component with common mark', async () => { + const rendered = await renderWithEffects( + wrapInTestApp( + , + ), + ); + 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 common mark github flavored', async () => { + const rendered = await renderWithEffects( + wrapInTestApp( + , + ), + ); + expect( + rendered.getByText('https://example.com', { selector: 'a' }), + ).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core/src/components/MarkdownContent/MarkdownContent.tsx new file mode 100644 index 0000000000..1449c5b1be --- /dev/null +++ b/packages/core/src/components/MarkdownContent/MarkdownContent.tsx @@ -0,0 +1,74 @@ +/* + * 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'; + +const useStyles = makeStyles(theme => ({ + markdown: { + '& table': { + borderCollapse: 'collapse', + border: '1px solid #dfe2e5', + color: 'rgb(36, 41, 46)', + }, + '& th, & td': { + border: '1px solid #dfe2e5', + padding: theme.spacing(1), + }, + '& tr': { + backgroundColor: '#fff', + }, + '& tr:nth-child(2n)': { + backgroundColor: '#f6f8fa', + }, + '& pre': { + padding: '16px', + overflow: 'auto', + fontSize: '85%', + lineHeight: 1.45, + backgroundColor: '#f6f8fa', + borderRadius: '6px', + color: 'rgba(0, 0, 0, 0.87)', + }, + '& a': { + color: '#2E77D0', + }, + '& img': { + maxWidth: '100%', + }, + }, +})); + +/** + * MarkdownContent. Renders markdown (CommonMark, optionally with [GFM](https://github.com/remarkjs/remark-gfm)) to formatted HTML. + */ +type Props = { + content: string; + enableGfm?: boolean; +}; + +export const MarkdownContent = ({ content, enableGfm = false }: Props) => { + const classes = useStyles(); + return ( + + ); +}; diff --git a/packages/core/src/components/MarkdownContent/index.ts b/packages/core/src/components/MarkdownContent/index.ts new file mode 100644 index 0000000000..7267ff191c --- /dev/null +++ b/packages/core/src/components/MarkdownContent/index.ts @@ -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'; diff --git a/packages/core/src/components/index.ts b/packages/core/src/components/index.ts index 4f085ffd6a..40c4ef0c11 100644 --- a/packages/core/src/components/index.ts +++ b/packages/core/src/components/index.ts @@ -35,3 +35,4 @@ export * from './Tabs'; export * from './TrendLine'; export * from './WarningPanel'; export * from './EmptyState'; +export * from './MarkdownContent';