Browse Source

add code highlight

tumao 3 years ago
parent
commit
7b44dcc467

+ 2 - 0
client/package.json

@@ -19,6 +19,7 @@
     "@types/react-dom": "^17.0.0",
     "@types/react-highlight-words": "^0.16.2",
     "@types/react-router-dom": "^5.1.7",
+    "@types/react-syntax-highlighter": "^13.5.2",
     "axios": "^0.21.1",
     "dayjs": "^1.10.5",
     "i18next": "^20.3.1",
@@ -30,6 +31,7 @@
     "react-i18next": "^11.10.0",
     "react-router-dom": "^5.2.0",
     "react-scripts": "4.0.3",
+    "react-syntax-highlighter": "^15.4.4",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
     "web-vitals": "^1.0.1"

+ 6 - 9
client/src/components/advancedSearch/CopyButton.tsx

@@ -3,27 +3,24 @@ import { makeStyles, Theme, createStyles } from '@material-ui/core';
 import { CopyButtonProps } from './Types';
 import icons from '../icons/Icons';
 import CustomIconButton from '../customButton/CustomIconButton';
+import { useTranslation } from 'react-i18next';
 
 const CopyIcon = icons.copyExpression;
 
 const CopyButton: FC<CopyButtonProps> = props => {
-  const {
-    label = 'copy button',
-    icon,
-    className,
-    value = '',
-    ...others
-  } = props;
+  const { label, icon, className, value = '', ...others } = props;
   const classes = useStyles();
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy', { returnObjects: true });
   const [tooltipTitle, setTooltipTitle] = useState('Copy');
 
   const handleClick = (event: React.MouseEvent<HTMLElement>, v: string) => {
     event.stopPropagation();
 
-    setTooltipTitle('Copied!');
+    setTooltipTitle(copyTrans.copied);
     navigator.clipboard.writeText(v);
     setTimeout(() => {
-      setTooltipTitle('Copy');
+      setTooltipTitle(copyTrans.copy);
     }, 1000);
   };
 

+ 52 - 0
client/src/components/code/CodeBlock.tsx

@@ -0,0 +1,52 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import CopyButton from '../advancedSearch/CopyButton';
+import SyntaxHighlighter from 'react-syntax-highlighter';
+import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    position: 'relative',
+    padding: theme.spacing(3),
+    borderRadius: 8,
+    backgroundColor: '#fff',
+    color: '#454545',
+  },
+  block: {
+    margin: 0,
+  },
+  copy: {
+    position: 'absolute',
+    top: theme.spacing(2),
+    right: theme.spacing(2),
+  },
+}));
+
+const code = `
+const a = 2;
+const testFunction = (input) => {
+  console.log(input)
+}
+testFunction(a)`;
+
+const CodeBlock = () => {
+  const classes = getStyles();
+
+  const { t: commonTrans } = useTranslation();
+  const copyTrans = commonTrans('copy', { returnObjects: true });
+
+  return (
+    <div className={classes.wrapper}>
+      <CopyButton
+        className={classes.copy}
+        label={copyTrans.label}
+        value="code block"
+      />
+      <SyntaxHighlighter language="javascript" style={docco}>
+        {code}
+      </SyntaxHighlighter>
+    </div>
+  );
+};
+
+export default CodeBlock;

+ 93 - 0
client/src/components/code/CodeView.tsx

@@ -0,0 +1,93 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import CustomTabList from '../customTabList/CustomTabList';
+import CodeBlock from './CodeBlock';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    boxSizing: 'border-box',
+    width: 480,
+
+    padding: theme.spacing(4),
+    backgroundColor: theme.palette.milvusDark.main,
+    borderRadius: 8,
+
+    color: '#fff',
+  },
+  title: {
+    marginBottom: theme.spacing(2),
+  },
+
+  // override tab list style
+  tabs: {
+    minHeight: 0,
+
+    '& .MuiTab-wrapper': {
+      textTransform: 'uppercase',
+      fontWeight: 'bold',
+      color: '#fff',
+    },
+
+    '& .MuiTab-root': {
+      minHeight: 18,
+      marginRight: 0,
+    },
+
+    // disable Ripple Effect
+    '& .MuiTouchRipple-root': {
+      display: 'none',
+    },
+
+    '& .Mui-selected': {
+      '& .MuiTab-wrapper': {
+        color: theme.palette.primary.main,
+      },
+    },
+
+    '& .MuiTabs-indicator': {
+      display: 'flex',
+      justifyContent: 'center',
+
+      top: 32,
+      backgroundColor: 'transparent',
+
+      '& > div': {
+        height: 1,
+        width: '100%',
+        maxWidth: 26,
+        backgroundColor: theme.palette.primary.main,
+      },
+    },
+
+    '& .MuiTabs-flexContainer': {
+      borderBottom: 'none',
+    },
+  },
+}));
+
+const CodeView = () => {
+  const classes = getStyles();
+  const { t: commonTrans } = useTranslation();
+
+  const mockTabs = [
+    {
+      label: 'node.js',
+      component: <CodeBlock />,
+    },
+    {
+      label: 'python',
+      component: <CodeBlock />,
+    },
+  ];
+
+  return (
+    <section className={classes.wrapper}>
+      <Typography variant="h5" className={classes.title}>
+        {commonTrans('code')}
+      </Typography>
+      <CustomTabList tabs={mockTabs} wrapperClass={classes.tabs} />
+    </section>
+  );
+};
+
+export default CodeView;

+ 26 - 6
client/src/components/customButton/CustomIconButton.tsx

@@ -1,20 +1,40 @@
-import { IconButtonProps, Tooltip, IconButton } from '@material-ui/core';
+import {
+  IconButtonProps,
+  Tooltip,
+  IconButton,
+  makeStyles,
+  Theme,
+} from '@material-ui/core';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'inline-block',
+  },
+  iconBtn: {
+    padding: theme.spacing(1),
+  },
+}));
 
 const CustomIconButton = (props: IconButtonProps & { tooltip?: string }) => {
-  const { tooltip, ...otherProps } = props;
+  const { tooltip, className, ...otherProps } = props;
+  const classes = getStyles();
 
   return (
-    <>
+    <div className={`${classes.wrapper} ${className}`}>
       {tooltip ? (
         <Tooltip title={tooltip} arrow>
           <span>
-            <IconButton {...otherProps}>{props.children}</IconButton>
+            <IconButton classes={{ root: classes.iconBtn }} {...otherProps}>
+              {props.children}
+            </IconButton>
           </span>
         </Tooltip>
       ) : (
-        <IconButton {...otherProps}>{props.children}</IconButton>
+        <IconButton classes={{ root: classes.iconBtn }} {...otherProps}>
+          {props.children}
+        </IconButton>
       )}
-    </>
+    </div>
   );
 };
 

+ 4 - 2
client/src/components/customTabList/CustomTabList.tsx

@@ -53,7 +53,7 @@ const a11yProps = (index: number) => {
 };
 
 const CustomTabList: FC<ITabListProps> = props => {
-  const { tabs, activeIndex = 0, handleTabChange } = props;
+  const { tabs, activeIndex = 0, handleTabChange, wrapperClass = '' } = props;
   const classes = useStyles();
   const [value, setValue] = useState<number>(activeIndex);
 
@@ -67,10 +67,12 @@ const CustomTabList: FC<ITabListProps> = props => {
     <>
       <Tabs
         classes={{
-          root: classes.wrapper,
+          root: `${classes.wrapper} ${wrapperClass}`,
           indicator: classes.tab,
           flexContainer: classes.tabContainer,
         }}
+        // if not provide this property, Material will add single span element by default
+        TabIndicatorProps={{ children: <div /> }}
         value={value}
         onChange={handleChange}
         aria-label="tabs"

+ 1 - 0
client/src/components/customTabList/Types.ts

@@ -9,6 +9,7 @@ export interface ITabListProps {
   tabs: ITab[];
   activeIndex?: number;
   handleTabChange?: (index: number) => void;
+  wrapperClass?: string;
 }
 
 export interface ITabPanel {

+ 4 - 0
client/src/pages/overview/collectionCard/CollectionCard.tsx

@@ -64,6 +64,10 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
   btn: {
     marginRight: theme.spacing(1),
+    padding: theme.spacing(0.5, 1),
+
+    lineHeight: '20px',
+    fontSize: 14,
   },
 }));
 

+ 22 - 0
client/src/pages/test.component.tsx

@@ -0,0 +1,22 @@
+import { makeStyles, Theme } from '@material-ui/core';
+import CodeView from '../components/code/CodeView';
+
+const getStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    display: 'flex',
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+}));
+
+const CodePage = () => {
+  const classes = getStyles();
+
+  return (
+    <div className={`page-wrapper ${classes.wrapper}`}>
+      <CodeView />
+    </div>
+  );
+};
+
+export default CodePage;

+ 6 - 0
client/src/router/Config.ts

@@ -3,6 +3,7 @@ import Collections from '../pages/collections/Collections';
 import Connect from '../pages/connect/Connect';
 import Overview from '../pages/overview/Overview';
 import VectorSearch from '../pages/seach/VectorSearch';
+import CodePage from '../pages/test.component';
 import { RouterConfigType } from './Types';
 
 const RouterConfig: RouterConfigType[] = [
@@ -31,6 +32,11 @@ const RouterConfig: RouterConfigType[] = [
     component: VectorSearch,
     auth: true,
   },
+  {
+    path: '/code',
+    component: CodePage,
+    auth: true,
+  },
 ];
 
 export default RouterConfig;

+ 152 - 0
client/yarn.lock

@@ -1869,6 +1869,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/hast@^2.0.0":
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.2.tgz#236201acca9e2695e42f713d7dd4f151dc2982e4"
+  integrity sha512-Op5W7jYgZI7AWKY5wQ0/QNMzQM7dGQPyW1rXKNiymVCy5iTfdPuGu4HhYNOM2sIv8gUfIuIdcYlXmAepwaowow==
+  dependencies:
+    "@types/unist" "*"
+
 "@types/history@*":
   version "4.7.8"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
@@ -2001,6 +2008,13 @@
     "@types/history" "*"
     "@types/react" "*"
 
+"@types/react-syntax-highlighter@^13.5.2":
+  version "13.5.2"
+  resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-13.5.2.tgz#357cc03581dc434c57c3b31f70e0eecdbf7b3ab0"
+  integrity sha512-sRZoKZBGKaE7CzMvTTgz+0x/aVR58ZYUTfB7HN76vC+yQnvo1FWtzNARBt0fGqcLGEVakEzMu/CtPzssmanu8Q==
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-test-renderer@>=16.9.0":
   version "17.0.1"
   resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b"
@@ -2074,6 +2088,11 @@
   dependencies:
     source-map "^0.6.1"
 
+"@types/unist@*":
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
+  integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+
 "@types/webpack-sources@*":
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10"
@@ -3398,6 +3417,21 @@ char-regex@^1.0.2:
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
   integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
 
+character-entities-legacy@^1.0.0:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
+  integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
+
+character-entities@^1.0.0:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
+  integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
+
+character-reference-invalid@^1.0.0:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
+  integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
+
 check-types@^11.1.1:
   version "11.1.2"
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
@@ -3594,6 +3628,11 @@ combined-stream@^1.0.8:
   dependencies:
     delayed-stream "~1.0.0"
 
+comma-separated-tokens@^1.0.0:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
+  integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
+
 commander@^2.20.0:
   version "2.20.3"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -5144,6 +5183,13 @@ fastq@^1.6.0:
   dependencies:
     reusify "^1.0.4"
 
+fault@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
+  integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
+  dependencies:
+    format "^0.2.0"
+
 faye-websocket@^0.11.3:
   version "0.11.4"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@@ -5316,6 +5362,11 @@ form-data@^3.0.0:
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
+format@^0.2.0:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
+  integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=
+
 forwarded@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -5660,6 +5711,22 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
+hast-util-parse-selector@^2.0.0:
+  version "2.2.5"
+  resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
+  integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
+
+hastscript@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
+  integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
+  dependencies:
+    "@types/hast" "^2.0.0"
+    comma-separated-tokens "^1.0.0"
+    hast-util-parse-selector "^2.0.0"
+    property-information "^5.0.0"
+    space-separated-tokens "^1.0.0"
+
 he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -5675,6 +5742,11 @@ highlight-words-core@^1.2.0:
   resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa"
   integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==
 
+highlight.js@^10.4.1, highlight.js@~10.7.0:
+  version "10.7.3"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
+  integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
+
 history@^4.9.0:
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
@@ -6100,6 +6172,19 @@ is-accessor-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
+is-alphabetical@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
+  integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
+
+is-alphanumerical@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
+  integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
+  dependencies:
+    is-alphabetical "^1.0.0"
+    is-decimal "^1.0.0"
+
 is-arguments@^1.0.4:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
@@ -6198,6 +6283,11 @@ is-date-object@^1.0.1:
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
   integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
 
+is-decimal@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
+  integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
+
 is-descriptor@^0.1.0:
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -6272,6 +6362,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-hexadecimal@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
+  integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
+
 is-in-browser@^1.0.2, is-in-browser@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
@@ -7339,6 +7434,14 @@ lower-case@^2.0.2:
   dependencies:
     tslib "^2.0.3"
 
+lowlight@^1.17.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
+  integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
+  dependencies:
+    fault "^1.0.0"
+    highlight.js "~10.7.0"
+
 lru-cache@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -8203,6 +8306,18 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
 
+parse-entities@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
+  integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
+  dependencies:
+    character-entities "^1.0.0"
+    character-entities-legacy "^1.0.0"
+    character-reference-invalid "^1.0.0"
+    is-alphanumerical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-hexadecimal "^1.0.0"
+
 parse-json@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -9149,6 +9264,11 @@ pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2:
     ansi-styles "^4.0.0"
     react-is "^17.0.1"
 
+prismjs@^1.22.0, prismjs@~1.24.0:
+  version "1.24.1"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.1.tgz#c4d7895c4d6500289482fa8936d9cdd192684036"
+  integrity sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==
+
 process-nextick-args@~2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -9201,6 +9321,13 @@ prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
     object-assign "^4.1.1"
     react-is "^16.8.1"
 
+property-information@^5.0.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
+  integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
+  dependencies:
+    xtend "^4.0.0"
+
 proxy-addr@~2.0.5:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -9548,6 +9675,17 @@ react-scripts@4.0.3:
   optionalDependencies:
     fsevents "^2.1.3"
 
+react-syntax-highlighter@^15.4.4:
+  version "15.4.4"
+  resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.4.4.tgz#dc9043f19e7bd063ff3ea78986d22a6eaa943b2a"
+  integrity sha512-PsOFHNTzkb3OroXdoR897eKN5EZ6grht1iM+f1lJSq7/L0YVnkJaNVwC3wEUYPOAmeyl5xyer1DjL6MrumO6Zw==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    highlight.js "^10.4.1"
+    lowlight "^1.17.0"
+    prismjs "^1.22.0"
+    refractor "^3.2.0"
+
 react-transition-group@^4.4.0:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
@@ -9655,6 +9793,15 @@ redent@^3.0.0:
     indent-string "^4.0.0"
     strip-indent "^3.0.0"
 
+refractor@^3.2.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.4.0.tgz#62bd274b06c942041f390c371b676eb67cb0a678"
+  integrity sha512-dBeD02lC5eytm9Gld2Mx0cMcnR+zhSnsTfPpWqFaMgUMJfC9A6bcN3Br/NaXrnBJcuxnLFR90k1jrkaSyV8umg==
+  dependencies:
+    hastscript "^6.0.0"
+    parse-entities "^2.0.0"
+    prismjs "~1.24.0"
+
 regenerate-unicode-properties@^8.2.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@@ -10427,6 +10574,11 @@ sourcemap-codec@^1.4.4:
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 
+space-separated-tokens@^1.0.0:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
+  integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
+
 spdx-correct@^3.0.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"