search
LoginSignup
7

More than 1 year has passed since last update.

posted at

updated at

Organization

Vuetify+TypeScriptでちょっとリッチなmarkdownを

前書き

この記事は JSL (日本システム技研) Advent Calendar 2020 - Qiita 22日目の記事です。
Vueでマークダウン書きたいよ、という要望に応えるために色々やってみた備忘録になります。
なんか、あまりこの組み合わせのやってみた記事を見かけないけどなんで・・・??? :thinking:

環境

  • MacOS: Mojave v10.14.5
  • Node: v10.19.0
  • TypeScript: v3.9.7
  • Vue.js: v2.6.12
  • Vuetify: v2.3.21

楽をするために我々はnpmの奥地へと向かった...

vuetify-markdown-editor

なんかそれっぽいのが見つかりました。 vuetify-markdown-editor ですね。覗いてみましょうか・・・

おおぉ・・・、もう今回の要件ほとんど満たしているじゃありませんか。

Vuetifyコンポーネントをベースにしているし、リッチですし、TypeScriptにも対応しているしで至れりつくせりです。

もうこれでいいじゃん...と思いましたが、今後でてくるであろう要望をどこまで吸収できるのかが不安でした(使用例があんまり見つからない)

とはいえ、サクッと用意したいならこれを使わない理由はないのではないでしょうか?

vuetify-markdown-editor 以外にVuetifyをベースにしたものは見つかりませんでした。
今後の自由度考えて独自に実装しちゃおう、ということで本編です。車輪の再開発?

本編

markdown-itを導入してみる

色々調べてみて、 markdown-it が一番一般的っぽかったのでこれにしました。マークダウン形式で書かれたテキストをパースしてhtmlにしてくれるパッケージです。

導入に当たっては、VuejsでMarkdownを使うときの最強な組み合わせ を参考にさせていただきました。 markdown-it にはプラグインがたくさんあって、結構カスタムできるみたいです。

で、以下のプラグインを導入することにしました。(タイトルは「リッチな」とうたっていますし豪華に行きましょう)

  • highlight.js : コードのハイライトしてくれるやつ(重要)
  • markdown-it-container : ↓こういうのを出せるやつ
    SS 2020-12-22 23.30.31.png

  • markdown-it-emoji : 名前の通り :rolling_eyes: 出すためのやつ

  • markdown-it-imsize : 画像サイズを調整できるようにするやつ

  • markdown-it-ins : 文字に下線ひけるやつ

  • markdown-it-sanitizer : XSS対策でサニタイズできるやつ

  • markdown-it-sub : ↓のような表示をできるようにするやつ
    SS 2020-12-22 23.38.25.png

  • markdown-it-task-checkbox : チェックボックス出せるようにするやつ

基本は参考記事の通りで問題ありませんが、ここでTypeScript特有の問題が発生します。
そう、型定義です。

@types/hoge パッケージが配布されていればいいのですが、以下のパッケージにはありませんでした。
declare module hoge でanyにしてもいいのですが、一応ちゃんとやってみることにしました。(えらいぞ自分)

まぁやることは簡単で、ちゃんと .d.ts ファイルに型定義を書いてあげればよいです。(たぶん)

markdown-it-plugins.d.ts
declare module "markdown-it-task-checkbox" {
  import { PluginSimple, PluginWithOptions } from "markdown-it";
  declare const checkbox: PluginSimple | PluginWithOptions;
  export = checkbox;
}

この要領で他のやつの分も追記していきます。

SS 2020-12-22 23.49.32.png

エディタもちゃんと分かってくれました(型を)

とりあえず試してみる

参考資料を元に実装してみました。動かしてみましょう。

SS 2020-12-22 23.58.17.png

コードブロックすらまともに出ない・・・だと???? :thinking:
悪さをしているのはVuetifyのCSS、 <code> タグになんかあたってました。
ならばこちらもCSS書いて上書きしてやるまでです。

Markdown.vue
// 省略

<style lang="scss" scoped>
// 参考記事のCSSインポートはここでやることに
@import "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.css";
@import "../../node_modules/highlight.js/styles/monokai.css";

.v-application .md ::v-deep {
  code {
    font-weight: initial;
    background-color: #2f2f2f;
    color: rgba(255, 255, 255, 0.9);
    font-size: 95%;
    display: inline-block;
    padding: 1px 0.5em 1px 0.5em;
    margin: 2px;
    margin-bottom: 14px;
  }

  pre code {
    display: block;
    padding: 0.5em 0.8em 0.5em 0.8em;
  }

  code:before,
  code:after {
    content: initial;
  }

  p {
    margin-bottom: 8px;
  }

  table {
    margin: 16px 0;
    color: #303030;
    width: auto;
    display: block;
    overflow-x: auto;
    border-collapse: collapse;

    th,
    td {
      padding: 10px;
      border: 1px solid #dbdbdb;
    }

    th {
      background-color: #f0f0f0;
      border-bottom: solid 2px #bfbfbf;
    }
  }
}

::v-deep は子コンポーネントにstyleを指定するためのセレクタです

ちょっと scss 書くの楽しくて他もいじっちゃいましたが許してください :bow:

これでどうでしょうか。

SS 2020-12-23 0.06.02.png

やりました。調教完了です

markdown-it-containerをVuetify標準にしよう

何もしないとこんなです

SS 2020-12-23 0.17.16.png

あれ、リッチとは???? :thinking:

その時思いました、Vuetifyのクラスをつけてあげれば勝手にVuetifyのデザインになるのでは、と。

早速やってみます。プラグインのオプションで変換処理を上書きして実現しようという魂胆です。Vuetifyの v-alart コンポーネントのクラスを拝借して・・・

markdownRender.ts

// 省略

type AlertType = "success" | "info" | "warning" | "error";

const createContainerOptions = (a: AlertType) => {
  const pattern = new RegExp(`^${a}$`);
  return [
    a,
    {
      render: (tokens: Token[], idx: number) => {
        const m = tokens[idx].info.trim().match(pattern);
        if (m && tokens[idx].nesting === 1) {
          return `<div class="v-alert v-alert--dense v-alert--text ${a}--text">\n`;
        }
        return `</div>\n`;
      }
    }
  ];
};

const md = markdownIt({
  highlight: function(code, lang) {
    return hljs.highlightAuto(code, [lang]).value;
  },
  html: true,
  linkify: true,
  breaks: true,
  typographer: true
})
  .use(container, ...createContainerOptions("success"))
  .use(container, ...createContainerOptions("info"))
  .use(container, ...createContainerOptions("warning"))
  .use(container, ...createContainerOptions("error"))

// 省略

こんなんでどうよ・・・

SS 2020-12-23 0.26.08.png

完☆璧

完成品

SS 2020-12-23 0.33.39.png

こんな感じで、ちょっとリッチな表示ができているのではないでしょうか。
styleとプラグインで自由に拡張できるのは楽しいですね。
Vuetifyくんに邪魔されることもありますが、仲良くできればいくらでも活かすことができそうです!夢が膨らみます

あとがき

エディタのツールバーとかはハリボテです・・・ごめんなさい・・・

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
What you can do with signing up
7