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のエディタにソースコードの埋め込みができるはずです。