株式会社HAMWORKS
🤖

MTAppjQuery + Vue.js + Markdown-itで使ってMarkdown投稿を実装(コンテンツタイプ版)

2020/01/28に公開

長くMovable TypeのMarkdownのエディタを使って投稿してましたが、世間一般的なMarkdownとは違いコードのハイライトだったりはエスケープして展開する必要があり使いにくいエディタだったので、MTAppjQuery + Vue.js + Markdown-itを組み合わせて世間一般的なMarkdownで入力できるようにカスタマイズしてみました。
今回は、Markdown対応をコンテンツタイプと記事の両方対応してみました。
※この記事もMarkdownで書くように作ってみました。

コンテンツタイプの場合

まずはコンテンツタイプの実装方法になります。
フィールドは3つ必要になります。

  • MarkdownEditor:Markdown入力用のフィールド
  • プレビューHTML:入力したMarkdownをHTMLへ変換し出力用フィールド
  • 保存用:状態保存用のフィールド

コンテンツフィールド

MarkdownEditor

MarkdownEditorは、Markdownを入力するフィールドです。
実際のフィールド自体は使わず Markdown入力用のフィールドを別途 jQueryでappendするためのid取得用になります。

プレビューHTML

プレビューHTMLは、MarkdownEditorで入力した値をHTMLで変換して代入するフィールドになります。
表示側はこちらのプレビューHTMLを展開することで出力させます。

保存用

状態保存用のフィールドになります。MarkdownEditorで入力した値はJavaScriptになるため永続的にデータを保存しておけません。
そのためプレビューHTMLとは別にマークダウンで入力した値をそのまま保存して、ページ保存やページ遷移しても値を保持し続けるために必要なフィールドになります。
※MarkdownEditorで作ったフィールドに代入はできません。MarkdownEditorはVueの管理下になるため、valを代入することができないため保存用のフィールドが必要です。

設定

Mardown-itを読み込む必要があるため、システムからMTAppjQueryの設定画面に入ります。
user.jsの読み込む前に以下のコードをいれておきます。

<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.0/build/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/10.0.0/markdown-it.js"></script>

コードをハイライトしてくれる highlight.jsmarkdown-it.js をCDN経由で読み込ませます。もちろんサーバにインストールパスでも問題ありません。

システムのプラグイン画面

この設定でuser.jsからmarkdown-itが使えるようになります。

コード

次に作成したフィールドを元に user.jsとuser.cssを作っていきます。

user.jsでは、windowからmarkdownitをグローバル変数に格納してメソッドを使えるように設定します。また公式のガイドラインの通りhighlightを使うための設定も施します。

const md = window.markdownit({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(lang, str).value;
      } catch (__) {}
    }
    return '';
  }
});

次にコンテンツタイプのフィールドのIDをセットさせます。
コンテンツタイプは標準ではIDが付きませんがMTAppjQueryならIDを振ってくれます。
そのIDを元に変数オブジェクトとして使いませるようにしておきましょう。
textareaやvalなども使用するため、こちらも合わせて変数に代入しておきます。

  const elementTags = {
    saveName: '#contentField7',
    targetName: '#contentField6',
    previewName: '#contentField5'
  }
  const elementSaveData = $(elementTags.saveName + ' textarea').val();
  const elementSaveTarget = $(elementTags.saveName + ' textarea')
  const elementTarget = $(elementTags.targetName);
  const elementPreviewTarget = $(elementTags.previewName + ' textarea');

次にMarkdownEditorのフィールド内にVue.jsで使用するフィールドをappendしておきます。
このときv-modelでないと反映しなかったので、v-modelにします。
:value@change などが利用できなかったため

elementTarget.append('<textarea class="form-control text high html5-form content-field markdown" v-model="text" @change="update"></textarea>');

つぎにVue.js部分の実装になります。
コード的には難しいことはなく、dataに格納した値( text: elementSaveData )を常に保存したフィールドを代入するようにしています。
methodsupdate で入力があるたびにプレビューHTMLと保存用のフィールドを更新する形になります。

  const markdownEditor = new Vue({
    el: elementTags.targetName,
    data: {
      text: elementSaveData
    },
    methods: {
      update: function () {
        this.$nextTick(function () {
          // 保存用のフィールドに入力データ格納
          elementSaveTarget.val(this.text);
          // 入力データをMarkdownに変換してプレビューHTMLへ代入
          const markup = md.render(this.text);
          elementPreviewTarget.val(markup)
        })
      }
    }
  });

全体コード

/**
 * Markdown-it Plugin + highlight.js
 */
const md = window.markdownit({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return hljs.highlight(lang, str).value;
      } catch (__) {}
    }
    return '';
  }
});
(function($){
  const elementTags = {
    saveName: '#contentField7',
    targetName: '#contentField6',
    previewName: '#contentField5'
  }
  const elementSaveData = $(elementTags.saveName + ' textarea').val();
  const elementSaveTarget = $(elementTags.saveName + ' textarea')
  const elementTarget = $(elementTags.targetName);
  const elementPreviewTarget = $(elementTags.previewName + ' textarea');
  elementTarget.append('<textarea class="form-control text high html5-form content-field markdown" v-model="text" @change="update"></textarea>');
  const markdownEditor = new Vue({
    el: elementTags.targetName,
    data: {
      text: elementSaveData
    },
    methods: {
      update: function () {
        this.$nextTick(function () {
          // 保存用のフィールドに入力データ格納
          elementSaveTarget.val(this.text);
          // 入力データをMarkdownに変換してプレビューHTMLへ代入
          const markup = md.render(this.text);
          elementPreviewTarget.val(markup)
        })
      }
    }
  });
})(jQuery);

CSSの調整

最後に入力に必要のないフィールドは外から見えないようにdisplay:noneをしつつプレビューHTMLは直接いじらせないように調整しましょう。

.markdown は appendした際に付与したクラスになります。textareaの高さ調整用のスタイルになります。
#contentField6 textarea[name="content-field-6"] はMarkdownEditor用に作ったフィールド自体になります。ご自身のID名に置き換えしてください。

#contentField5 はプレビューHTMLのフィールドになります。
pointer-events : none; で操作させないようにします。

#contentField7 は保存用のフィールドになります。
このフィールド自体はMarkdownEditorから動的に反映するため表示側では不要になります。

.markdown {
  height: 300px!important;
}
#contentField6 textarea[name="content-field-6"] {
  display: none;
}
#contentField5 textarea {
  height: 300px;
  pointer-events : none;
}

#contentField7 {
  display: none;
}

最後に出力させるためのフィールドをテンプレートに記述して完了になります。

<mt:ContentField content_field="プレビューHTML">
  <mt:ContentFieldValue />
</mt:ContentField>

完成画面はこちらになります。

コンテンツタイプMarkdown

株式会社HAMWORKS
株式会社HAMWORKS

Discussion