Merge pull request #624 from GlancingMind/upstream-fix-and-improve-rendering-of-markdown-elements

Michael Muré created

WebUI: Fix and improve rendering of markdown elements

Change summary

webui/package-lock.json                            | 26 +++++++++-
webui/package.json                                 |  1 
webui/src/components/CommentInput/CommentInput.tsx |  2 
webui/src/components/Content/AnchorTag.tsx         | 38 ++++++++++++++++
webui/src/components/Content/BlockQuoteTag.tsx     | 21 ++++++++
webui/src/components/Content/ImageTag.tsx          |  9 ++-
webui/src/components/Content/index.tsx             | 14 ++++-
webui/src/pages/bug/CommentForm.tsx                |  8 ---
webui/src/pages/bug/Message.tsx                    |  3 
webui/src/pages/bug/MessageHistoryDialog.tsx       |  8 ++
webui/types/remark-gemoji/index.d.ts               |  6 ++
11 files changed, 115 insertions(+), 21 deletions(-)

Detailed changes

webui/package-lock.json 🔗

@@ -12325,6 +12325,11 @@
       "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
       "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
     },
+    "gemoji": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/gemoji/-/gemoji-6.1.0.tgz",
+      "integrity": "sha512-MOlX3doQ1fsfzxQX8Y+u6bC5Ssc1pBUBIPVyrS69EzKt+5LIZAOm0G5XGVNhwXFgkBF3r+Yk88ONyrFHo8iNFA=="
+    },
     "generic-names": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz",
@@ -12572,7 +12577,8 @@
     "growly": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
-      "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
+      "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
+      "optional": true
     },
     "gzip-size": {
       "version": "5.1.1",
@@ -16118,6 +16124,7 @@
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz",
       "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==",
+      "optional": true,
       "requires": {
         "growly": "^1.3.0",
         "is-wsl": "^2.2.0",
@@ -16131,6 +16138,7 @@
           "version": "7.3.4",
           "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
           "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+          "optional": true,
           "requires": {
             "lru-cache": "^6.0.0"
           }
@@ -16138,12 +16146,14 @@
         "uuid": {
           "version": "8.3.2",
           "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+          "optional": true
         },
         "which": {
           "version": "2.0.2",
           "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
           "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+          "optional": true,
           "requires": {
             "isexe": "^2.0.0"
           }
@@ -19227,6 +19237,15 @@
         "fbjs": "^1.0.0"
       }
     },
+    "remark-gemoji": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/remark-gemoji/-/remark-gemoji-6.0.0.tgz",
+      "integrity": "sha512-LDW2h6QqNzAbAcOjscgfkJW9/8TGBasBe/ji+3mCxHlJdhF2IEXFSmm/3tdEPP1JJDZ4y+Ea+xlFQ4tOIU9WvA==",
+      "requires": {
+        "gemoji": "^6.0.0",
+        "unist-util-visit": "^2.0.0"
+      }
+    },
     "remark-html": {
       "version": "12.0.0",
       "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-12.0.0.tgz",
@@ -20219,7 +20238,8 @@
     "shellwords": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
-      "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww=="
+      "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
+      "optional": true
     },
     "side-channel": {
       "version": "1.0.3",

webui/package.json 🔗

@@ -22,6 +22,7 @@
     "react-router": "^5.2.0",
     "react-router-dom": "^5.2.0",
     "react-scripts": "^4.0.0-next.98",
+    "remark-gemoji": "^6.0.0",
     "remark-html": "^12.0.0",
     "remark-parse": "^8.0.3",
     "remark-react": "^7.0.1",

webui/src/components/CommentInput/CommentInput.tsx 🔗

@@ -5,7 +5,7 @@ import Tabs from '@material-ui/core/Tabs';
 import TextField from '@material-ui/core/TextField';
 import { makeStyles } from '@material-ui/core/styles';
 
-import Content from 'src/components/Content';
+import Content from '../Content';
 
 /**
  * Styles

webui/src/components/Content/AnchorTag.tsx 🔗

@@ -0,0 +1,38 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles((theme) => ({
+  tag: {
+    color: theme.palette.text.secondary,
+  },
+}));
+
+const AnchorTag = ({ children, href }: React.HTMLProps<HTMLAnchorElement>) => {
+  const classes = useStyles();
+  const origin = window.location.origin;
+  const destination = href === undefined ? '' : href;
+  const isInternalLink =
+    destination.startsWith('/') || destination.startsWith(origin);
+  const internalDestination = destination.replace(origin, '');
+  const internalLink = (
+    <Link className={classes.tag} to={internalDestination}>
+      {children}
+    </Link>
+  );
+  const externalLink = (
+    <a
+      className={classes.tag}
+      href={destination}
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      {children}
+    </a>
+  );
+
+  return isInternalLink ? internalLink : externalLink;
+};
+
+export default AnchorTag;

webui/src/components/Content/BlockQuoteTag.tsx 🔗

@@ -0,0 +1,21 @@
+import React from 'react';
+
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles((theme) => ({
+  tag: {
+    color: theme.palette.text.secondary,
+    borderLeftWidth: '0.5ch',
+    borderLeftStyle: 'solid',
+    borderLeftColor: theme.palette.text.secondary,
+    marginLeft: 0,
+    paddingLeft: '0.5rem',
+  },
+}));
+
+const BlockQuoteTag = (props: React.HTMLProps<HTMLPreElement>) => {
+  const classes = useStyles();
+  return <blockquote className={classes.tag} {...props} />;
+};
+
+export default BlockQuoteTag;

webui/src/components/Content/ImageTag.tsx 🔗

@@ -14,9 +14,12 @@ const ImageTag = ({
 }: React.ImgHTMLAttributes<HTMLImageElement>) => {
   const classes = useStyles();
   return (
-    <a href={props.src} target="_blank" rel="noopener noreferrer nofollow">
-      <img className={classes.tag} alt={alt} {...props} />
-    </a>
+    <>
+      <a href={props.src} target="_blank" rel="noopener noreferrer nofollow">
+        <img className={classes.tag} alt={alt} {...props} />
+      </a>
+      <br />
+    </>
   );
 };
 

webui/src/components/Content/index.tsx 🔗

@@ -1,26 +1,32 @@
 import React from 'react';
+import gemoji from 'remark-gemoji';
 import html from 'remark-html';
 import parse from 'remark-parse';
 import remark2react from 'remark-react';
 import unified from 'unified';
 
+import AnchorTag from './AnchorTag';
+import BlockQuoteTag from './BlockQuoteTag';
 import ImageTag from './ImageTag';
 import PreTag from './PreTag';
 
 type Props = { markdown: string };
 const Content: React.FC<Props> = ({ markdown }: Props) => {
-  const processor = unified()
+  const content = unified()
     .use(parse)
+    .use(gemoji)
     .use(html)
     .use(remark2react, {
       remarkReactComponents: {
         img: ImageTag,
         pre: PreTag,
+        a: AnchorTag,
+        blockquote: BlockQuoteTag,
       },
-    });
+    })
+    .processSync(markdown).result;
 
-  const contents: React.ReactNode = processor.processSync(markdown).contents;
-  return <>{contents}</>;
+  return <>{content}</>;
 };
 
 export default Content;

webui/src/pages/bug/CommentForm.tsx 🔗

@@ -17,14 +17,6 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
   container: {
     padding: theme.spacing(0, 2, 2, 2),
   },
-  textarea: {},
-  tabContent: {
-    margin: theme.spacing(2, 0),
-  },
-  preview: {
-    borderBottom: `solid 3px ${theme.palette.grey['200']}`,
-    minHeight: '5rem',
-  },
   actions: {
     display: 'flex',
     gap: '1em',

webui/src/pages/bug/Message.tsx 🔗

@@ -57,7 +57,8 @@ const useStyles = makeStyles((theme) => ({
   },
   body: {
     ...theme.typography.body2,
-    padding: '0.5rem',
+    paddingLeft: theme.spacing(1),
+    paddingRight: theme.spacing(1),
   },
   headerActions: {
     color: theme.palette.info.contrastText,

webui/src/pages/bug/MessageHistoryDialog.tsx 🔗

@@ -22,6 +22,8 @@ import {
 import CloseIcon from '@material-ui/icons/Close';
 import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
 
+import Content from '../../components/Content';
+
 import { AddCommentFragment } from './MessageCommentFragment.generated';
 import { CreateFragment } from './MessageCreateFragment.generated';
 import { useMessageHistoryQuery } from './MessageHistory.generated';
@@ -108,6 +110,7 @@ const AccordionSummary = withStyles((theme) => ({
 
 const AccordionDetails = withStyles((theme) => ({
   root: {
+    display: 'block',
     padding: theme.spacing(2),
   },
 }))(MuiAccordionDetails);
@@ -214,6 +217,7 @@ function MessageHistoryDialog({ bugId, commentId, open, onClose }: Props) {
         {history?.map((edit, index) => (
           <Accordion
             square
+            key={index}
             expanded={expanded === 'panel' + index}
             onChange={handleChange('panel' + index)}
           >
@@ -224,7 +228,9 @@ function MessageHistoryDialog({ bugId, commentId, open, onClose }: Props) {
             >
               <Typography>{getSummary(index, edit.date)}</Typography>
             </AccordionSummary>
-            <AccordionDetails>{edit.message}</AccordionDetails>
+            <AccordionDetails>
+              <Content markdown={edit.message} />
+            </AccordionDetails>
           </Accordion>
         ))}
       </DialogContent>