LaravelでsummernoteにPrism.jsを導入してシンタックスハイライトを付ける方法

LaravelでsummernoteにPrism.jsを導入してシンタックスハイライトを付ける方法

27 回閲覧されました

みなさんこんにちは、jonioです。

summernoteのエディタにソースコードを埋め込んだ時にシンタックスハイライトを付けたくてPrism.jsを導入したのですがメモとしてこの記事を残します。

Prism.jsとは

WordPressでSyntax hilightが(下記参照)ありますがそれを導入できるライブラリです。

最初hilight.jsを使って導入しようとしたのですがコードに関する情報がなさすぎてPrism.jsに変更しました。

Laravelのバージョン

10系ですが他のバージョンで動作するのかが分からないです。

CDN

下記を記述します。

//jQuery
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

//summernoteのツールチップ
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>

//summernote
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.20/src/lang/summernote-ja-JP.js"></script>

//bootstrap
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

//Prism.js
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />

bodyタグの直前に下記の記述をします。

    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
</body>

記事の作成の画面のテンプレート

summernoteで作成した記事の内容だけを投稿するとします。

コードを下記にします。

<div class="container pt-5 pb-5">
        <h2 class="text-center">記事の作成</h2>
        <form method="POST" action="{{ route('post.store') }}">
            @csrf
            <div class="summernote">
                <textarea name="content" id="summernote" cols="30" rows="10"></textarea>
            </div>
            <div>
                <button type="submit" name="status" value="下書き" class="draft-button">保存</button>
            </div>
        </form>
    </div>

11行目〜29行目は自作のツールチップ(下記画像参照)をクリックした時にコードを挿入するモーダルを表示する為の記述です。

jQuery

コードを下記にします。

<script>
    $(document).ready(function() {
        $('[data-toggle="tooltip"]').tooltip();

        $('#summernote').summernote({
            placeholder: 'ここにテキストを書きます。画像も入れる事ができます。',
            height: 500,
            lang: 'ja-JP',
            toolbar: [
                ['style', ['style']],
                ['font', ['bold', 'underline', 'clear']],
                ['fontname', ['fontname']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['insert', ['link', 'picture', 'video']],
                ['view', ['fullscreen', 'codeview', 'help']],
                ['custom', ['codeblock']]  // コードブロックボタンを追加
            ],

            buttons: {
                codeblock: function(context) {
                    let ui = $.summernote.ui;
                    let button = ui.button({
                        contents: '<i class="fa fa-code"/> code',
                        tooltip: 'Insert Code Block',
                        click: function() {
                            let code = prompt("挿入するコードを入力してください:");

                            if (code) {
                                let encodedCode = encodeHTML(code);
                                $('#summernote').summernote('editor.pasteHTML', `<pre><code class="language-javascript">${encodedCode}</code></pre><br>`);

                                applyPrismHighlighting();
                            }
                        }
                    });
                    return button.render();
                }
            },
            callbacks: {
                onImageUpload: function(files) {
                    sendFile(files[0]);
                },
            }
        });


        function encodeHTML(str) {
            return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
        }

        function applyPrismHighlighting() {
            let currentRange = $('#summernote').summernote('createRange');

            let isEditingCodeBlock = false;

            if (currentRange && currentRange.commonAncestor()) {
                let commonAncestor = $(currentRange.commonAncestor());

                if (commonAncestor.closest('pre code').length > 0) {
                    isEditingCodeBlock = true;
                }
            }

            let timeoutDuration = isEditingCodeBlock ? 10000 : 100;

            setTimeout(function() {
                Prism.highlightAll();

                $('#summernote').summernote('restoreRange', currentRange);
            }, timeoutDuration);
        }

        $('#summernote').on('summernote.change', function(we, contents, $editable) {
            applyPrismHighlighting();
        });

        $('form').on('submit', function() {
            let content = $('#summernote').summernote('code');

            $('#summernote').summernote('code', content);
        });
    });
</script>

xssの危険性

ソースコードを挿入するのが自分だけの場合は変なコードを入れる可能性はないはずですが不特定多数の人がソースコードを挿入する場合はxssの危険性があります。

また第三者が何らかの方法を使って別のソースコードを書き込んで掲載してxssをしようとするかもしれません。

そんな場合も考慮してタグをエスケープ(タグを文字扱いにする)します。

「Mews/purifier」をインストールします。

Mews/purifierの適用

下記のコマンドを叩きます。

composer require mews/purifier

コントローラーに下記の記述をします。

use Mews\Purifier\Facades\Purifier;

public function action() {
  $clean_content = Purifier::clean($content);
  
  return view('template', compact('clean_content'));
}

これでサニタイズが適用されますがSyntax hilightが消えるので消えないようにします。

purifier適用時に使用を許可するタグの指定

下記のコマンドを叩きます。

php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

「Laravelのプロジェクト > config > purifier.php」を編集します。

'default' => [
    'HTML.Doctype'             => 'HTML 4.01 Transitional',
    'HTML.Allowed'             => 'div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],pre[class],code[class],blockquote',
    'CSS.AllowedProperties'    => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align',
    'AutoFormat.AutoParagraph' => true,
    'AutoFormat.RemoveEmpty'   => true,
],

3行目に「,pre[class],code[class],blockquote」を追加します。

エディタの内容の表示

summernoteの内容を表示する時と同じです。

{!! $clean_content !!} 

これでsummernoteのエディタにソースコードの埋め込みができるはずです。