[Chrome拡張機能] Markdownビューアー

目次

機能

投稿のプレビューを確認するための補助ツールとして、Markdown (.mdファイル)を当サイトのスタイルで表示するChrome拡張機能を自分用に制作した。

.mdファイルをブラウザーで開くと、次の3つの処理を自動的に行って表示する。

  • Markdown形式で書かれた文書をHTMLに変換 (Marked)
  • TeXで記述された数式の表示 (KaTeX)
  • コード要素の修飾 (Google Code Prettify)

実行例

改行

改行に関してはより簡単に記述できるように、GFM (GitHub Flavored Markdown)と同じ仕様を採用している。
本来のMarkdownの仕様では、改行は「行末の2つのスペース」によって行われるが、GFMの仕様では改行コードによって改行されるようになり、よりわかりやすくMarkdownを記述できる。

数式

インライン数式は「\\( \\)」で囲むことで変換される。
本来は「\」が1つだけで変換したいところだが、Markedの処理が先に入る影響で「\」自体のエスケープが必要になる。

文中の数式は、\\(a_k = \int_0^\infty \frac{1}{1+x^k}dx\\)のように表示される。

文中の数式は、\(a_k = \int_0^\infty \frac{1}{1+x^k}dx\)のように表示される。

ディスプレイ形式の数式は次の文字列で囲むことで変換される。

<pre data-math>
\begin{align}

\end{align}
</pre>

本来はpreタグがなくても変換したいところだがこれもまたMarkedとの競合を避けるためであり、preで囲んだ部分がMarkedの影響を受けないことを利用した苦肉の策。

<pre data-math>
\begin{align}
a_k = \int_0^\infty \frac{1}{1+x^k}dx
\end{align}
</pre>

\begin{align} a_k = \int_0^\infty \frac{1}{1+x^k}dx \end{align}

インストール方法・ソースコード

1. ファイル構成

次のようにファイルを配置する。

src/
├── lib/
│   ├── marked.min.js
│   ├── katex.min.js
│   ├── katex.min.css
│   ├── auto-render.min.js
│   ├── fonts/
│   ├── prettify.min.js
│   └── desert.css
├── manifest.json
├── content.js
└── base.css

必要なライブラリーはそれぞれ次のリンクからダウンロードできる。

  • Marked - Markdown変換
  • KaTeX - 数式表示
    • katex.min.js
    • katex.min.css
    • auto-render.min.js - ページ全体をレンダリングするKaTeXの公式拡張機能。contribフォルダー内にある。
    • fonts - フォントファイル群。フォルダーごとコピーして配置する。
  • Google Code Prettify - コード装飾
  • henatips - 当サイト

manifest.jsonとcontent.jsは次の内容で保存する。

manifest.json

{
  "manifest_version": 3,
  "name": "henamd",
  "version": "1.0",
  "content_scripts": [{
    "matches": ["file:///*.md"],
    "css": [
      "lib/desert.css",
      "base.css"
    ],
    "js": [
      "lib/marked.min.js",
      "lib/katex.min.js",
      "lib/auto-render.min.js",
      "lib/prettify.min.js",
      "content.js"
    ]
  }],
  "web_accessible_resources": [{
    "matches": ["<all_urls>"],
    "resources": ["lib/katex.min.css", "lib/fonts/*"]
  }]
}

katex.min.cssでは相対パスでフォントファイルを参照しているが、content_scriptsでcssを設定すると参照に問題が発生する。この問題はJavaScriptからchrome.runtime.getURLを使うことによって解決することができるので、JSからkatex.min.cssを参照できるように予めweb_accessible_resourcesに登録している。

content.js

function render(md, container, style=null) {
    const renderer = new marked.Renderer()
    renderer.code = (code, infostring, escaped) => {
        // prettify
        code = escaped ? code.text : code.text.replace(/[&<>"']/g, c => ({
            "&": "&amp;",
            "<": "&lt;",
            ">": "&gt;",
            '"': "&quot;",
            "'": "&#39;"
        })[c])
        return `<pre class="prettyprint linenums">${code}</pre>\n`
    }
    renderer.image = (() => {
        const image = renderer.image
        return (href, title, text) => `<div class="image">${image(href, title, text)}</div>`
    })()

    marked.setOptions({
        breaks: true,
        renderer: renderer,
    })
    let html = marked.parse(md)

    // h2 split
    function h2split(html) {
        const sections = []
        html.split(/(?=<h2>)/).filter(v => v.trim() !== '').forEach((sec, i) => {
            sections.push(`<section>\n${sec}\n</section>`)
        })
        return sections.join('\n')
    }

    // h1 split
    const sections = []
    html.split(/(<h1[^>]*>.*?<\/h1>)/g).filter(v => v.trim() !== '').forEach(s => {
        if (s.startsWith('<h1>')) {
            sections.push(`<header>${s}</header>`)
        } else {
            sections.push(h2split(s))
        }
    })
    html = sections.join('\n')

    style ??= 'body{grid-template-rows:0 1fr;padding:0 16px;}h1{padding:24px 24px 16px !important;}main{padding:16px 0;width:672px;color:#333;}'
    container.innerHTML = `<style>${style}</style><main>${html}</main>`

    // prettify
    PR.prettyPrint(undefined, container)

    // KaTeX
    renderMathInElement(container)
    container.querySelectorAll('pre[data-math]').forEach(e => {
        const p = document.createElement('p')
        p.innerHTML = katex.renderToString(e.innerText, {displayMode:true})
        e.replaceWith(p)
    })
    const link = document.createElement("link")
    link.rel = "stylesheet"
    link.href = chrome.runtime.getURL("lib/katex.min.css")
    document.head.appendChild(link)
}

const md = document.body.innerText
render(md, document.body)

renderer.codeの設定では、markdownの変換でコード部分がpreタグとcodeタグで囲まれるのをpreタグでのみ囲まれるように変更し、Code Prettifyのためのクラスを与えている。
また、renderer.imageの設定では、img要素が左右中央に表示されるように動作を変更している。

h2タグやh1タグで分割している部分は当サイト特有の見た目に合わせるための処理。
この辺りは必要に応じてソースコードを書き変えることで、利用者の好みに沿う見た目にカスタマイズすることもできる。

Markdown変換後に、ページ全体に対してCode PrettifyとKaTeXを実行する。
KaTeXの処理では、上記の理由で仕方なく入れたpreタグをpタグに変換し、CSSが適用されることを防いでいる。
また、katex.min.cssを読み込んで適用している。

2. 拡張機能の読み込み

Google Chromeの場合を説明する。

  1. Chromeの設定メニューから「拡張機能を管理」のページを開く。
    (アドレス欄に「chrome://extensions/」と入力することでも開ける)
  2. ページ右上の「デベロッパー モード」が有効化されていない場合は有効化する。
  3. ページ左上の「パッケージ化されていない拡張機能を読み込む」を押す。
  4. 前節で構築したsrcフォルダーを選択。

これで拡張機能が利用可能となり、Chromeで拡張子が「.md」のファイルを開くとHTMLに変換されて表示されるようになる。

参考

Markdown

記法

KaTeX