Help us understand the problem. What is going on with this article?

markdownをリアルタイムプレビューするWEBエディタを作ってみました(シンタックスハイライト、サニタイズあり)

More than 5 years have passed since last update.

作ったページ

http://kimromi.github.io/frontend/markdown/

使用したライブラリ

markdown→HTML変換: marked.js
シンタックスハイライト: highlight.js

さっそく作ってみる

ダウンロードしたmarked.jshighlight.jsはlibフォルダに入れています。
シンタックスハイライトのCSSファイルはダウンロードしたhighlight.jsの中にstyles/github.cssがあるので
それをlib/styleフォルダに入れました。

HTMLとCSSは以下のような感じで。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>markdown editor</title>
    <link rel="stylesheet" href="lib/style/github.css">
    <link rel="stylesheet" href="css/style.css">
    <script src="js/jquery-1.11.1.js"></script>
    <script src="lib/marked.min.js"></script>
    <script src="lib/highlight.pack.js"></script>
    <script src="js/script.js"></script>
</head>
<body>
    <textarea id="edit" placeholder="input markdown text"></textarea>
    <div id="preview"></div>
</body>
</html>        
style.css
* {
    font-family:'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
    font-size: 12px;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
         -o-box-sizing: border-box;
        -ms-box-sizing: border-box;
            box-sizing: border-box;
}

html, body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
}

textarea#edit {
    float: left;
    overflow: auto;
    width: 50%;
    height: 100%;
    margin: 0;
    padding: 10px;
    border: none;
    resize: none;
}

div#preview {
    float: right;
    overflow: auto;
    width: 50%;
    height: 100%;
    margin: 0;
    padding: 10px;
    background-color: #EEE;
}

markdown→HTML変換

script.js
$(function() {

    $('#edit').keyup(function() {
        var html = marked($(this).val());
        $('#preview').html(html);
    });

});

marked関数にテキストを渡すことでHTML変換してくれます。
なので、textareaのkeyupイベントで入力内容をmarked関数に渡した結果を、プレビュー用のdivにセットすればOK。

シンタックスハイライト

script.js
$(function() {
    /*----- 追加 ----- */
    marked.setOptions({
        langPrefix: ''
    });

    $('#edit').keyup(function() {
        var html = marked($(this).val());
        $('#preview').html(html);

        /*----- 追加 ----- */
        $('#preview pre code').each(function(i, e) {
            hljs.highlightBlock(e, e.className);
        });
    });

});

marked関数で変換したHTMLをプレビュー用divにセットした後、
プレビュー用div内のcodeタグのDOMをhighlight.jsのhighlightBlock関数に渡すことでシンタックスハイライトしてくれます。
第1引数にDOM、第2引数に言語名(クラス名にセットされている)を渡してます。

marked.setOptionでlangPrefix:''としているのは、highlight.js連携のためです。

設定がない場合、

```java
//何かしらのコード
```

と書いた場合、デフォルトの設定ではHTML変換後、

<pre>
   <code class="lang-java">
      //何かしらのコード
   </code>
</pre>

となってしまって"lang-"が邪魔になってしまうのでそれを回避するために
langPrefixの設定を空白にしています。

サニタイジング

上記のままでは

<script>
alert('hoge!');
</script>

なんて書かれてしまうとスクリプトがそのまま実行されてしまうのでサニタイズします。

script.js
$(function() {
    marked.setOptions({
        langPrefix: ''
    });

    $('#edit').keyup(function() {
        /*----- 追加 ----- */
        var md = sanitize($(this).val());
        var html = marked(md);
        $('#preview').html(html);
        $('#preview pre code').each(function(i, e) {
            hljs.highlightBlock(e, e.className);
        });
    });

});

/*----- 追加 ----- */
function sanitize(html) {
    return $('<div />').text(html).html();
}

sanitize関数を用意して、HTML変換前にサニタイズしています。

サニタイズ方法はこの辺りを参考にしました。なるほどーという感じです。
http://nrbm647.blog.fc2.com/blog-entry-40.html

ただこれだとcodeタグ内の文字がpreに入っているのでサニタイズされたままになってしまいます。
なので、シンタックスハイライトする部分のみアンサニタイズ(?)しました。
先ほどの反対です。

最終的にはこんな感じになりました。

script.js
$(function() {
    marked.setOptions({
        langPrefix: ''
    });

    $('#edit').keyup(function() {
        var md = sanitize($(this).val());
        var html = marked(md);
        $('#preview').html(html);
        $('#preview pre code').each(function(i, e) {
            /*----- 追加 ----- */
            $(e).text(unsanitize($(e).text()));
            hljs.highlightBlock(e, e.className);
        });
    });

});

function sanitize(html) {
    return $('<div />').text(html).html();
}
/*----- 追加 ----- */
function unsanitize(html) {
    return $('<div />').html(html).text();
}

ソース

ソースはGitHubにおいてます。
https://github.com/kimromi/frontend/tree/master/markdown

こうしたほうがいいなどありましたらご指摘ください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした