Skip to content

Commit

Permalink
Merge pull request #58 from brianphillips/rich-text-to-markdown
Browse files Browse the repository at this point in the history
Add tool for converting rich text to Markdown
  • Loading branch information
drodil committed Oct 17, 2023
2 parents 360f243 + 3d29f18 commit eeccb9c
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
3 changes: 3 additions & 0 deletions plugins/toolbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
"react-use": "^17.2.4",
"sql-formatter": "^13.0.0",
"ts-md5": "^1.3.1",
"turndown": "^7.1.2",
"turndown-plugin-gfm": "^1.0.2",
"xml-js": "^1.6.11",
"yaml": "^2.3.2"
},
Expand All @@ -76,6 +78,7 @@
"@types/json2csv": "^5.0.4",
"@types/luxon": "^3.3.2",
"@types/node": "*",
"@types/turndown": "^5.0.2",
"cross-fetch": "^3.1.5"
},
"files": [
Expand Down
131 changes: 131 additions & 0 deletions plugins/toolbox/src/components/Converters/RichTextToMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import AssignmentReturnedIcon from '@material-ui/icons/AssignmentReturned';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import React, { useEffect } from 'react';
import TurndownService from 'turndown';
import { useStyles } from '../../utils/hooks';

import { MarkdownContent } from '@backstage/core-components';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Button,
ButtonGroup,
Grid,
Paper,
TextField,
Tooltip,
Typography,
} from '@material-ui/core';
import { CopyToClipboardButton } from '../Buttons';

// this library has no types available
const { gfm } = require('turndown-plugin-gfm') as {
gfm: TurndownService.Plugin;
};

export const RichTextToMarkdown = () => {
const styles = useStyles();
const [input, setInput] = React.useState('');
const [error, setError] = React.useState('');
const [output, setOutput] = React.useState('');

const pasteFromClipboard = async () => {
try {
const items = await navigator.clipboard.read();

// we'll take the first applicable item, prefering text/html
const item =
items.find(i => i.types.includes('text/html')) ||
items.find(i => i.types.includes('text/plain'));

if (item) {
const isHtml = item.types.includes('text/html');
const content = await item
.getType(isHtml ? 'text/html' : 'text/plain')
.then(blob => blob.text());
setInput(isHtml ? content : `<pre><code>${content}</code></pre>`);
setError('');
} else {
const contentTypes = items
.map(i => i.types)
.flat()
.join(', ');
setInput('');
setError(
`Clipboard did not contain any <code>text/html</code> or <code>text/plain</code> content (content was <code>${contentTypes})`,
);
}
} catch (err) {
setError(
`Error reading/converting data from clipboard: ${err.message || err}`,
);
}
};

useEffect(() => {
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
});
turndownService.use(gfm);
setOutput(turndownService.turndown(input));
window.addEventListener('paste', pasteFromClipboard);
return () => {
window.removeEventListener('paste', pasteFromClipboard);
};
}, [input]);

return (
<Grid container className={styles.fullWidth}>
<Grid item xs={12} lg={3}>
<Typography>
Copy rich text content to the clipboard and then paste on this page
(or click the button below) to convert to Markdown.
</Typography>
<br />
<Tooltip arrow title="Convert clipboard contents to markdown">
<Button
size="large"
fullWidth
startIcon={<AssignmentReturnedIcon />}
onClick={pasteFromClipboard}
>
Convert to markdown
</Button>
</Tooltip>
</Grid>
<Grid item xs={12} lg={9}>
<Box display="flex" justifyContent="right">
<ButtonGroup size="small">
<CopyToClipboardButton output={output} />
</ButtonGroup>
</Box>
<TextField
id="output"
label="Markdown"
value={output || ''}
className={styles.fullWidth}
multiline
minRows={20}
maxRows={50}
variant="outlined"
/>
<Typography color="error">{error}</Typography>
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h5">Rendered Preview</Typography>
</AccordionSummary>
<AccordionDetails>
<Paper elevation={0} className={styles.previewPaper}>
<MarkdownContent content={output} />
</Paper>
</AccordionDetails>
</Accordion>
</Grid>
</Grid>
);
};

export default RichTextToMarkdown;
9 changes: 9 additions & 0 deletions plugins/toolbox/src/components/Root/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const TimeConverter = lazy(() => import('../Converters/TimeConverter'));
const XmlToJson = lazy(() => import('../Converters/XmlToJson'));
const SLACalculator = lazy(() => import('../Converters/SLACalculator'));
const ColorConverter = lazy(() => import('../Converters/ColorConverter'));
const RichTextToMarkdown = lazy(
() => import('../Converters/RichTextToMarkdown'),
);

const EntityValidator = lazy(() => import('../Validators/EntityValidator'));
const EntityDescriber = lazy(() => import('../Misc/EntityDescriber'));
Expand Down Expand Up @@ -128,6 +131,12 @@ export const defaultTools: Tool[] = [
component: <ColorConverter />,
category: 'Convert',
},
{
id: 'rich-text-to-markdown-convert',
name: 'Rich text to markdown',
component: <RichTextToMarkdown />,
category: 'Convert',
},
{
id: 'sla-calculator',
name: 'SLA calculator',
Expand Down
33 changes: 33 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,7 @@ __metadata:
"@types/json2csv": ^5.0.4
"@types/luxon": ^3.3.2
"@types/node": "*"
"@types/turndown": ^5.0.2
color-convert: ^2.0.1
cross-fetch: ^3.1.5
crypto-hash: ^2.0.1
Expand All @@ -2554,6 +2555,8 @@ __metadata:
react-use: ^17.2.4
sql-formatter: ^13.0.0
ts-md5: ^1.3.1
turndown: ^7.1.2
turndown-plugin-gfm: ^1.0.2
xml-js: ^1.6.11
yaml: ^2.3.2
peerDependencies:
Expand Down Expand Up @@ -5997,6 +6000,13 @@ __metadata:
languageName: node
linkType: hard

"@types/turndown@npm:^5.0.2":
version: 5.0.2
resolution: "@types/turndown@npm:5.0.2"
checksum: 70f40f2f048413640c5570a58d8bb76f1f8bc0c69a58aefe57b60b21138335762ec3eb11b0d25a114681aef28364c889bef0b74c03f452f9148c6c926264018b
languageName: node
linkType: hard

"@types/unist@npm:*, @types/unist@npm:^2.0.0":
version: 2.0.6
resolution: "@types/unist@npm:2.0.6"
Expand Down Expand Up @@ -9197,6 +9207,13 @@ __metadata:
languageName: node
linkType: hard

"domino@npm:^2.1.6":
version: 2.1.6
resolution: "domino@npm:2.1.6"
checksum: 9b1b6d2661efd8bf942b70d5e11ac0de6a63f17e49b7eb227d9a612fa7b7c12b7775520d64f498988a8ee334ea9c59a463c84ea510b0af17dd3e13fdce120410
languageName: node
linkType: hard

"domutils@npm:^2.5.2, domutils@npm:^2.8.0":
version: 2.8.0
resolution: "domutils@npm:2.8.0"
Expand Down Expand Up @@ -19149,6 +19166,22 @@ __metadata:
languageName: node
linkType: hard

"turndown-plugin-gfm@npm:^1.0.2":
version: 1.0.2
resolution: "turndown-plugin-gfm@npm:1.0.2"
checksum: 18191dc18d731ec16bb1f1a477af6471ce2acf152b882a7eccef8e0101e09628fec0f8499f352f0a291ce798f1f30d799b5a72bd5d2cde30a2a28070de24495c
languageName: node
linkType: hard

"turndown@npm:^7.1.2":
version: 7.1.2
resolution: "turndown@npm:7.1.2"
dependencies:
domino: ^2.1.6
checksum: 4779580c3439d0385e7dd71144bf0f72884cf7fb492bd2f5600ff256fa7c9ae9663ef284507021de90d54d62885fc027d740d578a3e11a1ae83e84a107eedd38
languageName: node
linkType: hard

"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
version: 0.4.0
resolution: "type-check@npm:0.4.0"
Expand Down

0 comments on commit eeccb9c

Please sign in to comment.