decorateCooked のテーマと HighlightJS の比較

サイトのテーマで decorateCooked を使用して、特定の言語の HighlightJS コードブロックをポストプロセッシングしています。投稿内で ``` を使ってコードを指定した場合は動作しますが、インデントを使ってコードを含めた場合は失敗しています。

動作するケースでは、decorateCooked コードが呼び出される前に HighlightJS のクラスが既に付与されており、JQuery で言語クラス(‘applescript’)を持つコードブロックを検出できます。一方、失敗するケースでは、HighlightJS がプラグインコードの実行後に処理を行っているように見え、HighlightJS のクラス(hljs applescript)が存在しません。ただし、ページが読み込まれた後に確認すると、これらのクラスは存在しています。

以下は、テーマの <HEAD> 部分で定義されている私のコードです:

<script type="text/discourse-plugin" version="0.1">
    if (navigator.appVersion.indexOf('Mac') >= 0) { // Mac のみ実行
        api.decorateCooked(
            $elem => {
                console.log($elem.find('code'));
                console.log($elem.find('code').attr('class'));
                $elem.find('.lang-applescript, .applescript').filter(function () {
		            return $(this).parents('.d-editor-preview').length < 1; // エディタのプレビューでは実行しない
            	}).each(function(index) {
   		            var src = encodeURIComponent(this.innerText);

        		    $( '<a class="widget-button btn btn-default" href="sdapplescript://com.apple.scripteditor?action=new&script=' + src + '">Open in Script Debugger</a>' ).insertAfter($(this).parent());
        	    });
            },
            { id: 'applescript-decorator' }
        );
    }
</script>

コンソールログを見ると、投稿内の <code> ブロックは検出されていますが、class 属性は undefined として報告されています。

HighlightJS の処理が完了した後に decorateCooked コードを実行する方法はありますか?

この問題に対する回避策を MutationObserver を使用して見つけましたが、これが最適なアプローチかどうかはわかりません:

<script type="text/discourse-plugin" version="0.1">
    if (navigator.appVersion.indexOf('Mac') >= 0) { // Mac の場合のみ実行
        if (typeof myObserver == 'undefined') {
            var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
            var myObserver = new MutationObserver(function (mutations) {
                mutations.forEach(function (mutation) {
                    var target = $(mutation.target);
                    
                    if (target.hasClass('applescript')) {
                        var src = encodeURIComponent(mutation.target.innerText);

                        $('<a class="widget-button btn btn-default" href="sdapplescript://com.apple.scripteditor?action=new&script=' + src + '">Open in Script Debugger</a>').insertAfter(target.parent());
                    }
                });
            });
            var obsConfig = { childList: false, characterData: false, attributes: true, subtree: false };
        }

        api.decorateCooked(
            $elem => {
                $elem.find('.lang-applescript, .applescript').filter(function () {
                    return $(this).parents('.d-editor-preview').length < 1; // エディタのプレビューでは実行しない
                }).each(function(index) {
                    var src = encodeURIComponent(this.innerText);

                    $('<a class="widget-button btn btn-default" href="sdapplescript://com.apple.scripteditor?action=new&script=' + src + '">Open in Script Debugger</a>').insertAfter($(this).parent());
                });
                
                // decorateCooked が呼び出された後に HighlightJS が適用されるケースに対応します。
                // これは ``` ではなくインデントで導入されたコードブロックで発生します。
                $elem.find('code:not(.applescript,.lang-applescript)').filter(function () {
                    return $(this).parents('.d-editor-preview').length < 1; // エディタのプレビューでは実行しない
                }).each(function () {
                    myObserver.observe(this, obsConfig);
                });
            },
            { id: 'applescript-decorator', onlyStream: true }
        );
    }
</script>

私も同じ課題に直面していたので、このユースケースに対応するために新しいAPI機能 api.registerHighlightJSPlugin を追加しました。

これに highlightjs の関数ベースのプラグインを渡すことができます。これは Plugin API — highlight.js 11.9.0 documentation で文書化されています。