markedでqiitaライクにファイル名を表示する

Qiitaでコードハイライトにファイル名を加える場合、言語の後に`:filename`と入力することで、これが表示できます。

```
```js:index.js
import hoge from 'hoge';
`` `
```

```js:index.js
import hoge from 'hoge';
```

[marked](https://github.com/chjj/marked#readme) と [Highlight.js](https://github.com/isagalaev/highlight.js#readme)でこれを実現する場合、markedのオプションを少し変更する必要があります。

# 環境設定
今回は確認をブラウザ上で行いますので、下記`index.html`を用意します。

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Filename</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.js"></script>

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/monokai_sublime.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js"></script>
</head>
<body>

<script>
var md = "```js:index.js\nimport hoge from 'hoge';\n```";
var highlight = function(code, lang, callback){
  // hljs.highlightの第一引数にはlangを渡すのが適切だが、`js:index.js`という言語は存在しないため、暫定的にハードコーディングで済ます
  return hljs.highlight('js',code).value;
}
var renderer = new marked.Renderer;
var html = marked(md,{
  highlight: highlight,
  renderer: renderer,
});

document.body.innerHTML = html;
</script>
  
</body>
</html>
```

ブラウザ上で`md`内のマークダウン文字列が、整形されて表示されることを確認します。

[DEMO](https://jsfiddle.net/59naga/Lhuj1ape/)

![スクリーンショット 2016-02-25 14.21.46.gif](https://qiita-image-store.s3.amazonaws.com/0/28576/3f5d6e50-5c2b-4266-d636-8ca0b3521a10.gif)

# renderer.codeのオーバーライド

[marked - Overriding renderer methods](https://github.com/chjj/marked/tree/v0.3.5#overriding-renderer-methods)で説明されているように、markedのオプション引数`renderer`のプロパティを定義し直すことで、マークダウン文字列のコンパイル内容を変更することができます。

[marked-v0.3.5のもともとのコードブロックの処理](https://github.com/chjj/marked/blob/v0.3.5/lib/marked.js#L764-L785)は、下記のとおりです。

```js:v0.3.5/lib/marked.js#L764-L785
Renderer.prototype.code = function(code, lang, escaped) {
  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '<pre><code>'
      + (escaped ? code : escape(code, true))
      + '\n</code></pre>';
  }

  return '<pre><code class="'
    + this.options.langPrefix
    + escape(lang, true)
    + '">'
    + (escaped ? code : escape(code, true))
    + '\n</code></pre>\n';
};
```

さきほどの`index.html`は、既に`renderer`インスタンスをオプション引数に渡しているので、`renderer.code`プロパティを定義し直すことで、ファイル名を表示することができます。

[DEMO](https://jsfiddle.net/59naga/Lhuj1ape/5/)

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.js"></script>

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/monokai_sublime.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js"></script>
  
  <style>
    pre{
      background-color: #F7F7F6;
    }
    pre .filename{
      display: inline-block;
      padding: 0 .25em;
      background: rgba(0,0,0,0.07);
    }
    pre .filename+ *{
      display: block;
    }
  </style>
</head>
<body>

<script>
var md = "```js:index.js:fuga\nimport hoge from 'hoge';\n```";
var highlight = function(code, lang, callback){
  return hljs.highlight(lang,code).value;
}
var renderer = new marked.Renderer;
renderer.code = function(code, fileInfo, escaped) {
  var delimiter = ':';
  var info = fileInfo.split(delimiter);
  var lang = info.shift();
  var fileName = info.join(delimiter); // 2つ目以降のdelimiterはファイル名として扱う
  var fileTag = '';
  if (fileName) {
    fileTag = '<code class="filename">'+fileName+'</code>'
  }

  if (this.options.highlight) {
    var out = this.options.highlight(code, lang);
    if (out != null && out !== code) {
      escaped = true;
      code = out;
    }
  }

  if (!lang) {
    return '<pre>'+fileTag+'<code>'
      + (escaped ? code : escape(code, true))
      + '\n</code></pre>';
  }

  return '<pre>'+fileTag+'<code class="'
    + this.options.langPrefix
    + escape(lang, true)
    + '">'
    + (escaped ? code : escape(code, true))
    + '\n</code></pre>\n';
};
var html = marked(md,{
  highlight: highlight,
  renderer: renderer,
});

document.body.innerHTML = html;
</script>

</body>
</html>
```

以上です。