15
7

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 3 years have passed since last update.

JSL(日本システム技研)Advent Calendar 2020

Day 22

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

Last updated at Posted at 2020-12-22

前書き

この記事は 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くんに邪魔されることもありますが、仲良くできればいくらでも活かすことができそうです!夢が膨らみます

あとがき

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

15
7
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
15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?