gatsby-theme-mdx-blog icon indicating copy to clipboard operation
gatsby-theme-mdx-blog copied to clipboard

Add Prism code highlighting

Open jletey opened this issue 5 years ago • 6 comments

Kind of like Kent C. Dodds does in his website, I'd love for Prism code highlighting (and specific code lines highlighted too)

Reference: Kent C. Dodds' src/components/mdx/code.js

jletey avatar Apr 02 '19 17:04 jletey

Ah! Think I missed this in the readme, but you can shadow src/components.js to add a custom code component for syntax highlighting etc – can add that to the docs, but maybe that helps you move forward for now

jxnblk avatar Apr 02 '19 20:04 jxnblk

but you can shadow src/components.js to add a custom code component for syntax highlighting etc

@jxnblk Not seeing how I can do this ... could you maybe give an example?

can add that to the docs

For sure ... let me just understand what is going on first and how to use it properly

jletey avatar Apr 03 '19 06:04 jletey

I have the following code in src/@jxnblk/gatsby-theme-mdx-blog/components.js:

import components from "@jxnblk/gatsby-theme-mdx-blog/src/components.js";
import Code from "../../components/mdx/code";

export default {
  ...components,
  code: Code
};

Where ../../components/mdx/code is Kent C. Dodds' src/components/mdx/code.js

But I get this error:

TypeError: undefined is not an object (evaluating 'token.type')
normalizeTokens
node_modules/prism-react-renderer/es/utils/normalizeTokens.js:42
  39 |   types = stackIndex > 0 ? types : ["plain"];
  40 |   content = token;
  41 | } else {
> 42 |   types = types[0] === token.type ? types : types.concat(token.type);
  43 |   content = token.content;
  44 | } // If token.content is an array, increase the stack depth and repeat this while-loop
  45 | 

jletey avatar Apr 03 '19 10:04 jletey

Looks like you have the part in src/@jxnblk/gatsby-theme-mdx-blog/components.js correct, but that error seems to be coming from somewhere else... maybe the Code component?

jxnblk avatar Apr 03 '19 15:04 jxnblk

maybe the Code component?

@jxnblk I'll take a look ... thanks for the help. I'll keep you posted

jletey avatar Apr 03 '19 15:04 jletey

@jxnblk Still not working ... I'm not getting any error (like before), it just doesn't colour the code.

src/@jxnblk/gatsby-theme-mdx-blog/components.js:

import components from "@jxnblk/gatsby-theme-mdx-blog/src/components.js";
import mdxComponents from "../../components/mdx";

export default {
  ...components,
  ...mdxComponents
};

Note that the following code was taken from Kent C Dodds website

src/components/mdx/index.js:

import React from "react";

import Code from "./code";

export default {
  pre: preProps => {
    const props = preToCodeBlock(preProps);
    // if there's a codeString and some props, we passed the test
    if (props) {
      return <Code {...props} />;
    } else {
      // it's possible to have a pre without a code in it
      return <pre {...preProps} />;
    }
  }
};

function preToCodeBlock(preProps) {
  if (
    // children is MDXTag
    preProps.children &&
    // MDXTag props
    preProps.children.props &&
    // if MDXTag is going to render a <code>
    preProps.children.props.name === "code"
  ) {
    // we have a <pre><code> situation
    const {
      children: codeString,
      props: { className, ...props }
    } = preProps.children.props;

    return {
      codeString: codeString.trim(),
      language: className && className.split("-")[1],
      ...props
    };
  }
}

src/components/mdx/code.js:

import React from "react";
import { css } from "@emotion/core";
import theme from "prism-react-renderer/themes/oceanicNext";
import Highlight, { defaultProps } from "prism-react-renderer";

const RE = /{([\d,-]+)}/;

const wrapperStyles = css`
  overflow: auto;
`;

const preStyles = css`
  float: left;
  min-width: 100%;
  overflow: initial;
`;

function calculateLinesToHighlight(meta) {
  if (RE.test(meta)) {
    const lineNumbers = RE.exec(meta)[1]
      .split(",")
      .map(v => v.split("-").map(y => parseInt(y, 10)));
    return index => {
      const lineNumber = index + 1;
      const inRange = lineNumbers.some(([start, end]) =>
        end ? lineNumber >= start && lineNumber <= end : lineNumber === start
      );
      return inRange;
    };
  } else {
    return () => false;
  }
}

function Code({ codeString, language, metastring }) {
  const shouldHighlightLine = calculateLinesToHighlight(metastring);
  return (
    <Highlight
      {...defaultProps}
      code={codeString}
      language={language}
      theme={theme}
    >
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <div css={wrapperStyles}>
          <pre className={className} style={style} css={preStyles}>
            {tokens.map((line, i) => (
              <div
                key={i}
                {...getLineProps({
                  line,
                  key: i,
                  className: shouldHighlightLine(i) ? "highlight-line" : ""
                })}
              >
                <span
                  css={css`
                    display: inline-block;
                    width: 2em;
                    user-select: none;
                    opacity: 0.3;
                  `}
                >
                  {i + 1}
                </span>
                {line.map((token, key) => (
                  <span key={key} {...getTokenProps({ token, key })} />
                ))}
              </div>
            ))}
          </pre>
        </div>
      )}
    </Highlight>
  );
}

export default Code;

jletey avatar Apr 05 '19 15:04 jletey