From 58f80ba2cc2417d14dc83e09b489889da7799e5d Mon Sep 17 00:00:00 2001 From: atanasster Date: Sun, 28 Jun 2020 16:02:22 -0400 Subject: [PATCH] feat: mdx code add parameters and mdx docs --- core/instrument/README.md | 2 +- core/instrument/src/types.ts | 4 +- .../extract-component.test.ts.snap | 1 + .../react-typescript.test.ts.snap | 2 + .../write-documentation/mdx-documentation.mdx | 53 +++++++++- ui/components/package.json | 1 + .../src/Markdown/MarkdownComponents.tsx | 69 ++++++++----- ui/components/src/Source/Source.tsx | 2 +- .../SyntaxHighlighter/SyntaxHighlighter.tsx | 97 ++++++++++++++----- ui/components/src/ThemeContext/theme.ts | 13 +++ ui/components/src/typings.d.ts | 1 + yarn.lock | 5 + 12 files changed, 196 insertions(+), 54 deletions(-) diff --git a/core/instrument/README.md b/core/instrument/README.md index 9b44e8292..5efe20b09 100644 --- a/core/instrument/README.md +++ b/core/instrument/README.md @@ -475,7 +475,7 @@ _Defined in [@types/resolve/index.d.ts](https://github.com/DefinitelyTyped/Defin enable footnotes -• **mdPlugins**? : _any\[]_ +• **remarkPlugins**? : _any\[]_ specify remark plugins diff --git a/core/instrument/src/types.ts b/core/instrument/src/types.ts index 3ed225b7f..5f48ea162 100644 --- a/core/instrument/src/types.ts +++ b/core/instrument/src/types.ts @@ -44,7 +44,7 @@ import { mdx } from '@mdx-js/react'; export const defaultMDXOptions: MDXOptions = { test: /\.(mdx|md)$/i, renderer: DEFAULT_MDX_RENDERER, - mdPlugins: [images, emoji], + remarkPlugins: [images, emoji], }; /** @@ -169,7 +169,7 @@ export interface MDXOptions { /** * specify remark plugins */ - mdPlugins?: any[]; + remarkPlugins?: any[]; /** * specify rehype plugins */ diff --git a/core/instrument/test/__snapshots__/extract-component.test.ts.snap b/core/instrument/test/__snapshots__/extract-component.test.ts.snap index fb7160721..ba92ccc49 100644 --- a/core/instrument/test/__snapshots__/extract-component.test.ts.snap +++ b/core/instrument/test/__snapshots__/extract-component.test.ts.snap @@ -264,6 +264,7 @@ Object { "copy-to-clipboard": "^3.2.1", "fast-memoize": "^2.5.2", "markdown-to-jsx": "^6.11.0", + "mdx-utils": "*", "prism-react-renderer": "^1.0.2", "react": "^16.13.1", "react-animate-height": "^2.0.20", diff --git a/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap b/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap index 7e42a084e..7c281616c 100644 --- a/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap +++ b/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap @@ -198,6 +198,7 @@ export const ActionBar: FC = ({ "copy-to-clipboard": "^3.2.1", "fast-memoize": "^2.5.2", "markdown-to-jsx": "^6.11.0", + "mdx-utils": "*", "prism-react-renderer": "^1.0.2", "react": "^16.13.1", "react-animate-height": "^2.0.20", @@ -242,6 +243,7 @@ export const ActionBar: FC = ({ "copy-to-clipboard": "^3.2.1", "fast-memoize": "^2.5.2", "markdown-to-jsx": "^6.11.0", + "mdx-utils": "*", "prism-react-renderer": "^1.0.2", "react": "^16.13.1", "react-animate-height": "^2.0.20", diff --git a/examples/stories/src/tutorial/write-documentation/mdx-documentation.mdx b/examples/stories/src/tutorial/write-documentation/mdx-documentation.mdx index 7dba7a72a..c47f33f86 100644 --- a/examples/stories/src/tutorial/write-documentation/mdx-documentation.mdx +++ b/examples/stories/src/tutorial/write-documentation/mdx-documentation.mdx @@ -39,7 +39,7 @@ order: 0 In MDX, you can import any react components and use them to create rich documentation pages: -```jsx +```mdx --- title: My Button --- @@ -53,11 +53,58 @@ import { Button } from 'theme-ui'; MDX allows you to import external MDX and MD files statically and display their content: -```jsx +```mdx --- title: Test Transclusion --- import Transclusion from '../sections/transclusion.mdx'; -``` \ No newline at end of file +``` + +# Syntax highlighting + +You can include source code in your MDX and MD documentation and add some useful parameters + +## Language + +The language can be specified as the first parameter +````mdx +```jsx + import { Button } from 'theme-ui'; +``` +```` + +## Title + +A title attribute can be added. Due to some current MDX limitations, the title can not contain spaces. + +````mdx:title=my-title +```jsx:title=my-title +import { Button } from 'theme-ui'; +``` +```` + +## Highlight lines + +You can specify a line to highlight +````markdown {2} +```jsx {2} +import { Button } from 'theme-ui'; + +``` +```` + +Or a range of lines +````markdown {2-3} +```jsx {2-3} +import { Button } from 'theme-ui'; + +``` +```` + +## Emoji + +You can use `:emoji:` in your documents + +`:rocket: :dog: :+1:` = :rocket: :dog: :+1: \ No newline at end of file diff --git a/ui/components/package.json b/ui/components/package.json index 0c4e19643..523c331c8 100644 --- a/ui/components/package.json +++ b/ui/components/package.json @@ -35,6 +35,7 @@ "@theme-ui/presets": "next", "copy-to-clipboard": "^3.2.1", "fast-memoize": "^2.5.2", + "mdx-utils": "*", "markdown-to-jsx": "^6.11.0", "prism-react-renderer": "^1.0.2", "react": "^16.13.1", diff --git a/ui/components/src/Markdown/MarkdownComponents.tsx b/ui/components/src/Markdown/MarkdownComponents.tsx index 4b1e3dceb..7967f1ca4 100644 --- a/ui/components/src/Markdown/MarkdownComponents.tsx +++ b/ui/components/src/Markdown/MarkdownComponents.tsx @@ -1,17 +1,9 @@ /* eslint-disable react/display-name */ /** @jsx jsx */ import { ComponentType } from 'react'; +import { preToCodeBlock } from 'mdx-utils'; import { jsx } from 'theme-ui'; -import { - Label, - Flex, - Box, - Heading, - Button, - Card, - Image, - Avatar, -} from 'theme-ui'; +import { Label, Button, Image } from 'theme-ui'; import { Language } from 'prism-react-renderer'; import { SyntaxHighlighter } from '../SyntaxHighlighter'; import { Source } from '../Source'; @@ -27,6 +19,7 @@ const mdxLanguageMap: MDXLanguageType = { css: 'css', js: 'javascript', jsx: 'jsx', + JSX: 'jsx', 'coffee-script': 'coffeescript', coffeescript: 'coffeescript', coffee: 'coffeescript', @@ -38,6 +31,7 @@ const mdxLanguageMap: MDXLanguageType = { make: 'makefile', Makefile: 'makefile', markdown: 'markdown', + mdx: 'jsx', objectivec: 'objectivec', python: 'python', scss: 'scss', @@ -47,26 +41,55 @@ const mdxLanguageMap: MDXLanguageType = { export interface MarkdownComponentType { [key: string]: ComponentType; } +const paramsFromClassName = (className: string = ``) => { + const [lang = ``, params = ``] = className.split(`:`); + + return [ + // @ts-ignore + lang + .split(`language-`) + .pop() + .split(`{`) + .shift(), + ].concat( + // @ts-ignore + params.split(`&`).reduce((merged, param) => { + const [key, value] = param.split(`=`); + // @ts-ignore + merged[key] = value; + return merged; + }, {}), + ); +}; export const markdownComponents: MarkdownComponentType = { code: props => { return ; }, pre: props => { - const codeProps = props?.children?.props?.children - ? props.children.props - : props; - const { className = '', children } = codeProps || {}; - const arrClass = className.split('-'); - const mdxLanguage = arrClass.length === 2 ? arrClass[1] : 'js'; - const language = mdxLanguageMap[mdxLanguage] || mdxLanguage; - return {children}; + const mdxProps = preToCodeBlock(props); + if (!mdxProps) { + return
;
+    }
+    const { codeString = '', metastring, className } = mdxProps;
+    const [language = 'jsx', ...rest] = paramsFromClassName(className);
+    const otherProps = Array.isArray(rest)
+      ? rest.reduce(
+          (acc, p) =>
+            typeof p === 'object' ? { ...acc, ...(p as object) } : acc,
+          {},
+        )
+      : undefined;
+    return (
+      
+        {codeString.trimRight()}
+      
+    );
   },
-  avatar: Avatar,
   image: Image,
-  box: Box,
   button: Button,
-  card: Card,
-  flex: Flex,
-  heading: Heading,
   label: Label,
 };
diff --git a/ui/components/src/Source/Source.tsx b/ui/components/src/Source/Source.tsx
index 366783abe..7d5800486 100644
--- a/ui/components/src/Source/Source.tsx
+++ b/ui/components/src/Source/Source.tsx
@@ -51,7 +51,7 @@ export const Source: FC = ({
           display: 'block',
         }}
       >
-        {children.trimRight()}
+        {children}
       
     
   );
diff --git a/ui/components/src/SyntaxHighlighter/SyntaxHighlighter.tsx b/ui/components/src/SyntaxHighlighter/SyntaxHighlighter.tsx
index 3cec38989..456075541 100644
--- a/ui/components/src/SyntaxHighlighter/SyntaxHighlighter.tsx
+++ b/ui/components/src/SyntaxHighlighter/SyntaxHighlighter.tsx
@@ -1,7 +1,7 @@
 /** @jsx jsx */
 /* eslint react/jsx-key: 0 */
-import { jsx } from 'theme-ui';
-import React, { FC } from 'react';
+import { jsx, Heading } from 'theme-ui';
+import { FC, Fragment } from 'react';
 import { Styled, Box, useColorMode } from 'theme-ui';
 import Highlight, {
   defaultProps,
@@ -13,6 +13,26 @@ import duotoneLight from 'prism-react-renderer/themes/duotoneLight';
 
 type RenderProps = Parameters[0];
 
+// from lekoarts gatsby themes
+// https://github.com/LekoArts/gatsby-themes/blob/master/themes/gatsby-theme-minimal-blog/src/components/code.tsx#L34
+const RE = /{([\d,-]+)}/;
+
+const calculateLinesToHighlight = (meta: string) => {
+  if (!RE.test(meta)) {
+    return () => false;
+  }
+  const lineNumbers = RE.exec(meta)![1]
+    .split(`,`)
+    .map(v => v.split(`-`).map(x => parseInt(x, 10)));
+  return (index: number) => {
+    const lineNumber = index + 1;
+    const inRange = lineNumbers.some(([start, end]) =>
+      end ? lineNumber >= start && lineNumber <= end : lineNumber === start,
+    );
+    return inRange;
+  };
+};
+
 export interface SyntaxHighlighterProps {
   /**
    * source code to be displayed.
@@ -22,6 +42,12 @@ export interface SyntaxHighlighterProps {
    * optional `PrismTheme` theme provided to the component. Themes can be imported from `prism-react-renderer/themes`.
    */
   theme?: PrismTheme;
+
+  /**
+   * optional title to display for the code block. Usually used from MDX
+   */
+  title?: string;
+
   /**
    * source lnguage used, by default "jsx".
    */
@@ -49,6 +75,11 @@ export interface SyntaxHighlighterProps {
    * syntax container as element. Can be used as `div` or `span`.
    */
   as?: React.ElementType;
+
+  /**
+   * code configuration string passed from MDX
+   */
+  metastring?: string;
 }
 
 /**
@@ -61,37 +92,55 @@ export const SyntaxHighlighter: FC = ({
   renderFn,
   dark = false,
   style: propStyle,
+  title,
+  metastring = ``,
   as = 'span',
 }) => {
   const [colorMode] = useColorMode();
   const isDark = dark === true || colorMode === `dark`;
   const theme = customTheme ? customTheme : isDark ? duotoneDark : duotoneLight;
-
+  const shouldHighlightLine = calculateLinesToHighlight(metastring);
   const renderProps =
     typeof renderFn === 'function'
       ? (props: RenderProps) => renderFn(props, { theme })
       : ({ className, style, tokens, getLineProps, getTokenProps }: any) => (
-          
-            {tokens.map((line: string[], i: number) => (
-              
-                {line.map((token, key) => (
-                  
-                ))}
-              
-            ))}
-          
+          
+            {title && (
+              
+                {title}
+              
+            )}
+            
+              {tokens.map((line: string[], i: number) => {
+                const highlight = shouldHighlightLine(i);
+                return (
+                  
+                    {line.map((token, key) => (
+                      
+                    ))}
+                  
+                );
+              })}
+            
+          
         );
   const props = { ...defaultProps, theme };
 
diff --git a/ui/components/src/ThemeContext/theme.ts b/ui/components/src/ThemeContext/theme.ts
index d4014e9e7..83e591b3a 100644
--- a/ui/components/src/ThemeContext/theme.ts
+++ b/ui/components/src/ThemeContext/theme.ts
@@ -32,6 +32,7 @@ export type ControlsTheme = {
   searchinput: Record;
   subtitle: ThemeUIStyleObject;
   subheading: ThemeUIStyleObject;
+  syntaxhighlight: Record;
   tabs: Record;
   tag: Record;
   title: ThemeUIStyleObject;
@@ -376,6 +377,18 @@ export const theme: ControlsTheme = {
     fontWeight: 'body',
     pb: 1,
   },
+  syntaxhighlight: {
+    highlight: {
+      pl: 1,
+      backgroundColor: 'highlight',
+      borderLeft: (t: Theme) => `4px solid ${t.colors?.primary}`,
+    },
+    normal: {},
+    title: {
+      fontWeight: 'bold',
+      pl: 2,
+    },
+  },
   tabs: {
     '.react-tabs': {
       WebkitTapHighlightColor: 'transparent',
diff --git a/ui/components/src/typings.d.ts b/ui/components/src/typings.d.ts
index d5930aeec..359e36480 100644
--- a/ui/components/src/typings.d.ts
+++ b/ui/components/src/typings.d.ts
@@ -1,4 +1,5 @@
 declare module '@mdx-js/runtime';
+declare module 'mdx-utils';
 declare module '@theme-ui/match-media';
 declare module '@mdx-js/react' {
   import * as React from 'react';
diff --git a/yarn.lock b/yarn.lock
index dad9d92d2..81cb7c016 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14744,6 +14744,11 @@ mdurl@^1.0.0:
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
   integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
 
+mdx-utils@*:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/mdx-utils/-/mdx-utils-0.2.0.tgz#b759c5d06545c4f5c49ac3adba3b93942039ac52"
+  integrity sha512-kRhSIrvJ/++jz+ppDNqG3vjJSqSipjqdh2BqYXxUTJBo1cO+hRtQwuudM0ljAWKf5WmePwT4OBRjoCrhmiK+RA==
+
 meant@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.1.tgz#66044fea2f23230ec806fb515efea29c44d2115d"