0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Ruby on Rails】ActionTextとMathJax 3.2で数式を表示できる簡易エディターを実装する

Last updated at Posted at 2022-08-01

はじめに

個人開発にて、Action TextとMathJax(バージョン 3.2) で、簡易的ではありますが数式を表示できるエディターを作成しました。

左の画像のように数式を入力してプレビューボタンを押すと、右のように数式が表示されます。

スクリーンショット 2022-08-01 16.32.25.png スクリーンショット 2022-08-01 1.17.04.png

この記事では、実装した際につまづいた点をMathJaxを中心に説明したいと思います。

準備

Action Text

テーブル作成など、サーバー側の準備は Rails ガイドーAction Text を、
エディターの見た目やJSのイベントなど、フロント側の設定は trix の GitHubをご覧ください。
私は、フロント側に関しては以下の2点をデフォルトの設定から変更しました。

  1. ツールバー
    エディター上部のツールバーの見た目は、https://github.com/basecamp/trix/blob/main/assets/trix/stylesheets/toolbar.scss で定義されています。これを参考に以下のようにすることで、ツールバーのいくつかのアイテムを削除しました。
    app/javascript/stylesheets/action_text.scss

    trix-toolbar{
      background-color: white;
    }
    
    .trix-button--icon-heading-1,
    .trix-button--icon-italic,
    .trix-button--icon-quote,
    .trix-button--icon-strike,
    .trix-button--icon-attach,
    .trix-button--icon-code {
      display: none;
    }
    
    trix-toolbar .trix-button-group--file-tools {
      border: none;
    }
    
  2. ファイル添付を無効化
    Action Textはデフォルトでエディターにファイルを添付することができます。今回、この機能は必要ないため、こちらを参考にして無効化しました。

    app/javascript/src/ActionText.js

    require("trix");
    require("@rails/actiontext");
    
    
    (function() {
      addEventListener("trix-initialize", function(e) {
        const file_tools = document.querySelector(".trix-button-group--file-tools");
        file_tools.remove();
      })
      // action text のファイル添付機能を無効化
      addEventListener("trix-file-accept", function(e) {
        e.preventDefault();
      })
    })();
    
    

MathJaxの初期設定

基本的な設定についてはここでは説明しません。MathJaxのドキュメントがとてもわかりやすいので、読めば大丈夫だと思います。

私の初期設定は以下のようになりました。
app/javascript/src/MathJax.js

MathJax = {
  tex: {
    inlineMath: [ ['$','$'], ['\\(','\\)'] ],
    processEscapes: true,
    tags: 'ams',
  },
  startup: {
    elements: [".mathjax-initialize-typeset"]
  }
};

startupセクションには、MathJaxのスタートアップ時の設定を記述します。
参考: MathJax-Performing Actions During Startup

elementsオプションは、画面読み込み時にMathJaxによってタイプセットされるべきDOM要素を指定します(デフォルトの値は document body です)。
参考: MathJax-Startup Options

今回であれば、例えばユーザーが入力するエディターはタイプセットされたくありません。そこで、画面読み込み時にタイプセットしてほしい要素に mathjax-initialize-typeset というクラス名を付与し、これをelementsオプションの値に指定しておきます。

タイプセットすべき要素の設定は、Optionsセクションの skipHtmlTagsなどでも指定できるようです。
参考: Document Options

ビューファイル

CSSに関するものなど、重要でないものは省略しています。
(注)BootStrap5、gem slimsimple_form を利用しています。

/ タブ
ul.nav.nav-tabs role="tablist"
  li.nav-item
    a.nav-link.active data-bs-toggle="tab" href="#code"
      | 入力
  li.nav-item
    a.nav-link.mathjax-typeset-button data-bs-toggle="tab" href="#preview"
      | プレビュー
/ タブの内容
.tab-content
  / 入力フォーム
  .tab-pane.fade.active.show.mathjax-code role="tabpanel" id="code"
    = f.input :document_with_equation, as: :rich_text_area
  / プレビュー表示
  .tab-pane.fade.trix-content.mathjax-typeset-result role="tabpanel" id="preview"

= f.input :document_with_equation, as: :rich_text_areaによって、trix-editor要素が生成し、この中にユーザーの入力した内容のHTMLが作られていきます。

エディターの入力内容をタイプセットして、数式をプレビューに表示するJS

app/javascript/src/MathJax.js

$(function(){
  // タブのプレビューを押したら
  $(document).on("click", ".mathjax-typeset-button", function(e){
    // 数式番号をリセット
    MathJax.texReset([0]);
    // タブを押されたエディター要素を取得
    var tabContent = $(e.target).parents("ul.nav-tabs").next(".tab-content");
    // エディターの入力内容を取得
    var Code = tabContent.find("trix-editor").html();
    // 入力内容をプレビューに表示後、それをタイプセットする
    MathJax.typeset(tabContent.find(".mathjax-typeset-result").html(Code));
  });
});

MathJax.typeset()メソッドを用いて、HTMLをタイプセットします。
参考: ドキュメント-MathJax in Dynamic Content

ただし、タイプセットが実行されるたび自動的に数式番号が増えていくので、MathJax.texReset([n])で数式番号をリセットします。
参考: ドキュメント-Resetting Automatic Equation Numbering

問題点

以上が基本的な構造ですが、このままでは以下の問題が発生します。

問題点1

ディスプレー数式の直後に改行が入ってしまい、見栄えが悪い。

スクリーンショット 2022-08-01 20.37.38.png スクリーンショット 2022-08-01 20.50.16.png

プレビューのHTMLは概略次のようになります。

<mjx-container display="true">
(ディスプレー数式)
<mjx-container>
<br>
"が成り立つ。"

このように、ディスプレー数式の直後に br タグができてしまうことが原因です。

問題点2

複数のユーザーが作成した文書を同一画面に表示すると、数式番号が続き番号になってしまいます。
ざっくりとした例ですが、ビューファイルを

== user_b.document_with_equation
== user_a.document_with_equation

とすると、以下のようになります。
スクリーンショット 2022-08-01 20.57.09.png

問題点を解決する

問題点1について

<mjx-container display="true"> に続く br 要素を削除するJS(JQuery)のremoveBrNextToDisplayMathメソッドを作成します。
app/javascript/src/MathJax.js

// MathJaxによる数式表示時、ディスプレー数式の後のbrは削除する
const removeBrNextToDisplayMath = function(){
  $('mjx-container[display="true"]').next().each(function(){
    if($(this).is("br")){
      $(this).remove();
    }
  });
}

そして、MathJaxのタイプセット後にこのメソッドを実行します。

プレビューボタンをクリックした際の実行は簡単で、次のようにイベントリスナーの最後にメソッドを追加するだけです。
app/javascript/src/MathJax.js

$(function(){
  // タブのプレビューを押したら
  $(document).on("click", ".mathjax-typeset-button", function(e){
    // 数式番号をリセット
    MathJax.texReset([0]);
    // タブを押されたエディター要素を取得
    var tabContent = $(e.target).parents("ul.nav-tabs").next(".tab-content");
    // エディターの入力内容を取得
    var Code = tabContent.find("trix-editor").html();
    // 入力内容をプレビューに表示後、それをタイプセットする
    MathJax.typeset(tabContent.find(".mathjax-typeset-result").html(Code));
    // ディスプレー数式直後のbrタグを削除
    removeBrNextToDisplayMath(); // これを追加
  });
});

画面読み込み時の初期タイプセット後に removeBrNextToDisplayMath() を実行するには、MathJaxの startup セクションで MathJax.startup.promise を利用してready() 関数を上書きします。
参考: ドキュメント-Performing Actions After Typesetting
app/javascript/src/MathJax.js

MathJax = {
  tex: {
    inlineMath: [ ['$','$'], ['\\(','\\)'] ],
    processEscapes: true,
    tags: 'ams',
  },
  startup: {
    elements: [".mathjax-initialize-typeset"]
    // 以下を追加
    ready() {
      MathJax.startup.promise.then(() => {
        // 初期タイプセットが終了したら、ディスプレー数式直後のbrタグを削除
        removeBrNextToDisplayMath();
      });
    }
  }
};

問題点2について

デフォルトでは、MathJaxが同一画面内の数式をタイプセットすると、それらの数式番号はひと続きになります。そのため、文章ごとに数式番号をリセットしたい場合は設定を追加する必要があります。今回、ドキュメント-Example: Section Numberingの内容を利用することにしました。
これは、数式番号をセクションごとに付与する example で、例えばセクション2の3つ目の数式には「(2.3)」のように数式番号を付与することができます。
こちらのissueにわかりやすい例があります。

私の場合、セクション番号は必要なく、セクションごとに数式番号をリセットしたいだけなので、以下のような設定を追加するだけで十分でした。
app/javascript/src/MathJax.js

MathJax = {
  loader: {load: ['[tex]/tagformat']}, 
  tex: {
    packages: {'[+]': ['tagformat', 'sections']},
  },
  startup: {
    ready() {
      const Configuration = MathJax._.input.tex.Configuration.Configuration;
      const CommandMap = MathJax._.input.tex.SymbolMap.CommandMap;
      new CommandMap('sections', {
        nextSection: 'NextSection'
      }, {
        NextSection(parser, name) {
          parser.tags.counter = parser.tags.allCounter = 0;
        }
      });
      Configuration.create(
        'sections', {handler: {macro: ['sections']}}
      );
      MathJax.startup.defaultReady();

まず、数式番号のフォーマットオプションを利用するために、loadertex.packagesの設定を追加します。
参考: ドキュメント-tagformat

さらに、startupセクションのready()関数に、上記exampleで紹介されているコードのうち\nextSectionでセクションを増加させるマクロの部分を追加しました。

これにより、ビューファイルの

span style="display: none;"
  | \(\nextSection\)

を記述したところでセクションが切り替わり、数式番号がリセットされます。

先ほどの例でいうと、ビューファイルを

== user_b.document_with_equation

span style="display: none;"
  | \(\nextSection\)

== user_a.document_with_equation

とすれば、以下のようになります!!
スクリーンショット 2022-08-01 23.56.28.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?