はてなブログのシンタックスハイライトを jsx に対応させる

やたら重いので消した (2018/10/18)

はてなブログシンタックスハイライトを jsx に対応させる

はてなブログシンタックスハイライトは、2018/08 現在jsxには対応していない。 http://help.hatenablog.com/entry/markup/syntaxhighlight

なので、PrismJSを使って対応させてみる。

PrismJS を読み込む

<head>内に以下を追加。好みのテーマとライブラリを読み込む。CDN で読み込む場合は、ハイライトしたい言語の依存関係にあるものをすべて含める必要がある。依存関係はこちらで確認できる。jsxをハイライトしたいならば、javascriptmarkup、そしてjsxが必要なので、そのとおりに読み込む。

->|html|
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', () => {
            const cssSources = [
                "npm/prismjs@1.15.0/themes/prism-dark.min.css"
            ];
            const link = document.createElement('link');
            link.rel = "stylesheet";
            link.href = `https://cdn.jsdelivr.net/combine/${cssSources.join(',')}`;
            document.head.appendChild(link);

            const jsSources = [
                "npm/prismjs@1.15.0",
                "npm/prismjs@1.15.0/components/prism-javascript.min.js",
                "npm/prismjs@1.15.0/components/prism-jsx.min.js",
                "npm/prismjs@1.15.0/components/prism-markup.min.js"
            ];
        });
    </script>

PrismJS では、パースされたトークン(構文の最小構成単位)にkeywordクラスが与えられることがあるが、はてなブログのデフォルトの CSS!importantでそれに対して適用されてしまうので、打ち消す必要がある:

->|html|
    <style>
        .token.atrule,
        .token.attr-value,
        .token.keyword {
            color: #d1939e !important; /* テーマによって値は異なる */
        }
    </style>

ハイライトさせる

PrismJS は、次のようなcodeブロックの中身をすべてパースし、クラス名に沿ってハイライトしてくれる。

->|html|
<pre><code class="language-css">p { color: red }</code></pre>

が、マークダウン記法に慣れているといちいちタグで囲むのも、クラス名を割り当てるのも少々めんどくさいので、次のスクリプトを追加する。ついでに<>エスケープする:

->|js|
        document.addEventListener('DOMContentLoaded', () => {

            /* 略 */

            var pres = document.querySelectorAll('pre');
            pres.forEach((pre) => {
                const langRegExp = /^-&gt;\|(.*)\|\n/
                const lang = pre.innerHTML.match(langRegExp);
                if (!lang) return;
                const content = pre.innerHTML
                    .replace(langRegExp, '')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;')
                pre.innerHTML = `<code class="language-${lang[1]}">${content}</code>`;
                pre.setAttribute('class', `language-${lang[1]}`);
            });
        });

これによって、次のようにブロックのはじめに->|jsx|を書くと:

->|none|
->|jsx| 
class Todo component Component {
  render () {
    return <div />
  }
}

PrismJS がパース可能な HTML が作り出されるようになる:

->|none|
<pre class="language-jsx">
  <code class="language-jsx">
    class Todo component Component {
      render () {
         return <div />
      }
    }
  </code>
</pre>

プラグイン

PrismJS には、プラグインがいくつか用意されており、拡張が可能。ただ、CSSはてなのデフォルト CSS とバッティングする可能性があるので慎重に。

Line Numbers

行番号を追加するプラグインを入れてみる。 まずは、プラグイン用のスクリプトと、CSS を追加する:

->|js|
        document.addEventListener('DOMContentLoaded', () => {
            const cssSources = [
                                 /* 略 */
                "npm/prismjs@1.15.0/plugins/line-numbers/prism-line-numbers.min.css"
            ];
            const link = document.createElement('link');
            link.rel = "stylesheet";
            link.href = `https://cdn.jsdelivr.net/combine/${cssSources.join(',')}`;
            document.head.appendChild(link);

            const jsSources = [
                                 /* 略 */
                "npm/prismjs@1.15.0/plugins/line-numbers/prism-line-numbers.min.js",
            ];
            const script = document.createElement('script');
            script.src = `https://cdn.jsdelivr.net/combine/${jsSources.join(',')}`
            document.body.appendChild(script);

            /* 略 */
        });

preタグのクラスにline-numbersを加える:

->|js|
        document.addEventListener('DOMContentLoaded', () => {
            /* 略 */
            pres.forEach((pre) => {
                /* 略 */
                pre.setAttribute('class', `line-numbers language-${lang[1]}`);
            });
        });

これだけだと、プラグインが追加した行番号のフォントサイズとはてなのデフォルトのフォントサイズが違うためか、ずれてしまうことがあるので、行番号の方を修正する:

->|html|
<style>
/* 略 */
code[class*=language-], pre[class*=language-] {
  font-size: inherit;
}
</style>

Copy to Clipboard

ツールバーにコピーボタンを加える。 まずは同じように、JS と CSS ファイルを読み込む:

->|js|
        document.addEventListener('DOMContentLoaded', () => {
            const cssSources = [
                /* 略 */
                "npm/prismjs@1.15.0/plugins/toolbar/prism-toolbar.min.css"
            ];
            const link = document.createElement('link');
            link.rel = "stylesheet";
            link.href = `https://cdn.jsdelivr.net/combine/${cssSources.join(',')}`;
            document.head.appendChild(link);

            const jsSources = [
                /* 略 */
                "npm/prismjs@1.15.0/plugins/toolbar/prism-toolbar.min.js",
                "npm/prismjs@1.15.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"
            ];
            const script = document.createElement('script');
            script.src = `https://cdn.jsdelivr.net/combine/${jsSources.join(',')}`
            document.body.appendChild(script);

            /* 略 */
        });

これで、ツールバーにコピーボタンが追加される。

しかし、デザインが好みじゃないので、カスタマイズしてみる。PrismJS の読み込み後に ClipboardJS を読み込み、そのコールバック内でコピーボタンを追加:

->|js|
        document.addEventListener('DOMContentLoaded', () => {
            /* 略 */
            const jsSources = [
                /* 略 */
                "npm/prismjs@1.15.0/plugins/toolbar/prism-toolbar.min.js"
            ];
            const script = document.createElement('script');
            script.src = `https://cdn.jsdelivr.net/combine/${jsSources.join(',')}`
            script.onload = () => {
                const clipboardJSSrc = document.createElement("script");
                clipboardJSSrc.src = "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js";
                clipboardJSSrc.onload = () => {
                    addCopyBotton()
                }
                document.querySelector("head").appendChild(clipboardJSSrc);
            }
            document.head.appendChild(script);
            /* 略 */
        })

        function addCopyBotton() {
            Prism.plugins.toolbar.registerButton('hello-world', (env) => {
                var linkCopy = document.createElement('a');
                linkCopy.innerHTML = '<i class="copy-icon material-icons">file_copy</i>'
                registerClipboard();
                return linkCopy;

                function registerClipboard() {
                    var clip = new ClipboardJS(linkCopy, {
                        'text': function () {
                            return env.code;
                        }
                    });
                }
            });
        }

linkCopyのアイコンはお好みのものをどうぞ。

正直カスタマイズがかなりめんどくさかったので、次なんかやるとしたらブログ移行してからかな…