コードブロックの指定行をハイライトさせる
2020年06月26日
つくったもの
技術ブログを書いているときに、サンプルコードの指定行をハイライトしたいことがありました。
サンプルコードのどこを説明しているのか読みやすくしたいと思ったからです!
たとえば.mdx
ファイルでline={1,4-6}
と行を指定すると、
副作用 (effect) フック により、関数コンポーネント内で副作用を実行することができるようになります。```js line={1,4-6}import React, { useState, useEffect } from 'react';function Example() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;});return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);}```
コードブロック部分は 1 行目と 4~6 行目がハイライトされます。
import React, { useState, useEffect } from 'react';function Example() {const [count, setCount] = useState(0);useEffect(() => {document.title = `You clicked ${count} times`;});return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);}
実装
```で表示するコンポーネントを用意
マークダウン記法の```を使って表示される見た目を、MDXProvider を使って変えたいと思います。
{ code: <表示したいコンポーネント /> }
を
MDXProvider の state である components に渡すことで、
```で表示される見た目を変えることが出来ます。
import { MDXProvider } from '@mdx-js/react';const CodeBlock = props => <pre style={{ background: 'gray' }} {...props} />;const components = {code: CodeBlock,};export const wrapRootElement = ({ element }) => (<MDXProvider components={components}>{element}</MDXProvider>);
MDXProvider について詳細は以下の記事を参照ください。
自分の場合は、
CodeBlock
コンポーネントの中で、Highlight
という色付けされたコード部分を表示するコンポーネントを使っています。
このHighlight
コンポーネントで指定した行をハイライトしています。
指定した行を受け取る
MDXProvider の components の code に指定したCodeBlock
コンポーネントでは、
MDX 内でline={1,3-5}
のように指定した{1,3-5}
という文字列をline
props で受け取れます。
```js line={1,3-5}console.log('macho');console.log('macho!');console.log('macho!!');console.log('macho!!!');console.log('macho!!!!');console.log('macho!!!!!');```
そして、Highlight
コンポーネントに渡しています。
<Highlight title={title} code={code} language={language} line={line} />
受け取った行から、ハイライトすべきか判別する
受け取った行の{1,3-5}
のような文字列を受け取る、
calculateLinesToHighlight
という高階関数を用意します。
const calculateLinesToHighlight = meta => {const RE = /{([\d,-]+)}/;if (RE.test(meta)) {const strLineNumbers = RE.exec(meta)[1];const lineNumbers = rangeParser(strLineNumbers);return index => lineNumbers.includes(index + 1);} else {return () => false;}};
この公開関数が返す関数は、
行番号を渡せばそれが指定行に含まれているかどうか真偽値で返すものです。
const shouldHighlightLine = calculateLinesToHighlight(line);
それを、受け取ったコードを行ごとにループで回している箇所で使い、その行をハイライトすべきか判定しています。
{tokens.map((line, i) => (<div key={i} {...getLineProps({ line, key: i })}><Line shouldHighlight={shouldHighlightLine(i)}>...
ハイライトすべき行の場合、styleを加える
あとは、Line
コンポーネントのshouldHighlight
がtrueの場合にstyleを加えるだけです!
const Line = ({ shouldHighlight, ...props }) => {const highlightProps = shouldHighlight && {d: 'block',bg: 'gray.700',borderLeftWidth: '.5rem',borderLeftColor: 'purple.200',px: '3',};return <Box d='table-cell' px='5' {...props} {...highlightProps} />;};
ちなみに、ホバーした単語のハイライトも実装しているのでこちらも御覧ください!
🥰
以上です!シンプルに少しずつ改善していくのは楽しいので、ぜひ皆さんも自作技術ブログライフを!