【VSCode/Cursor】Markdownプレビューをおしゃれに！高機能コールアウトで表現力UP（Markdown Preview Enhanced 拡張）


## この記事でできること

VSCodeやCursorでMarkdownプレビュー拡張機能「Markdown Preview Enhanced（MPE）」を使っているあなたへ。この記事では、**デザイン性の高い、カスタマイズ自由なコールアウト**をMarkdownで簡単に作成する方法を解説します。

「ちょっとした注意書きを目立たせたい」「重要な情報を強調したい」「ブログのデザインに合わせて特別な囲み枠を使いたい」そんなニーズに答えます。

`:::`でテキストを囲むだけで、目を引くコールアウトがあなたのMarkdownドキュメントに加わり、**記事の見やすさと表現力が格段に向上**します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665424/4889817f-9785-4630-9e23-54df16ba47e2.png)

## 対象環境

* Cursor: 0.48.9
* Markdown Preview Enhanced: 1.28.0

## 設定方法：簡単2ステップ！

以下の手順で、すぐにコールアウトを使えるようになります。

1.  **parser.jsを編集して、コールアウトの定義を追加**
2.  **Customize CSS（style.less）で、デザインをカスタマイズ**

### ステップ1：parser.jsにコールアウトの定義を追加

まずは、Markdownプレビューの解析を行う`parser.js`に、新しいコールアウトのルールを記述します。

Cursorをお使いの場合は、`Shift + Ctrl + P`（Macの場合は `Shift + Cmd + P`）を押してコマンドパレットを開き、`Markdown Preview Enhanced: Extend Parser`と入力します。

設定範囲の選択肢が表示されるので、お好みのほうを選んでください。

* **Global:** すべてのMarkdownファイルに適用したい場合
* **Workspace:** 現在開いているプロジェクトのみに適用したい場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665424/114e550f-18e6-4b32-bb9d-a6a045bf37ee.png)

選択すると`parser.js`というファイルが開きます。既存のコードはすべて削除し、以下のコードを**上書き**してください。

```javascript:parser.js
({
  onWillParseMarkdown: async function (markdown) {
    const SVG_ICONS = {
      memo: `
    <svg width="20" height="20" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
      <path d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.7 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160V416c0 53 43 96 96 96H352c53 0 96-43 96-96V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H96z"/>
    </svg>
  `,
      info: `
    <svg width="20" height="20" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
    </svg>
  `,
      checkBox: `
    <svg width="20" height="20" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/>
    </svg>
  `,
      exclamation: `
    <svg width="20" height="20" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/>
    </svg>
  `,
      xmark: `
    <svg width="20" height="20" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
      <path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/>
    </svg>
  `,
    };

    // Callout type definitions
    const CALLOUT_TYPES = [
      { type: "memo", icon: SVG_ICONS.memo },
      { type: "alert", icon: SVG_ICONS.xmark },
      // { type: "success", icon: SVGs.checkBox },
      { type: "info", icon: SVG_ICONS.info },
      { type: "warn", icon: SVG_ICONS.exclamation },
    ];

    // Replaces callout syntax with HTML elements
    function replaceCallout(markdown, type, icon) {
      const regex = new RegExp(`:::${type}[\\s\\S]*?:::`, "gm");
      markdown = markdown.replace(regex, (match) => {
        const content = "\n" + match.slice(type.length + 3, -3);
        return `<div class="InlineCalloutElement callout-${type}">
            <div class="IconContainer">${icon}</div>
            <div class="ContentContainer">${content}</div>
            </div>
            `;
      });
      return markdown;
    }

    // Process all callout types
    for (const { type, icon } of CALLOUT_TYPES) {
      markdown = await replaceCallout(markdown, type, icon);
    }
    
    return markdown;
    
  },

  onDidParseMarkdown: async function (html) {
    return html;
  },
});

```

#### コードの解説

* **`SVG_ICONS`:** コールアウト内で使用するアイコンをSVG形式で定義しています。CDNなどのURLを指定することも可能ですが、この記事では**オフラインでも動作するように**コード内に直接記述しています。
* **`CALLOUT_TYPES`:** コールアウトの名前（例: `info`, `warning`）と、対応するアイコンを紐付けています。ここで新しい名前とアイコンの組み合わせを定義することで、**独自のコールアウトを簡単に追加**できます。

### ステップ2：Customize CSS（style.less）でデザインを調整

次に、コールアウトのデザインを定義するために、`style.less`ファイルを編集します。

再びコマンドパレットを開き（`Shift + Ctrl + P`）、`Markdown Preview Enhanced: Customize CSS`と入力します。

先ほどと同様に、GlobalかWorkspaceのいずれかを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665424/ff36bf63-3cca-46b0-b4fc-5b4795d8d348.png)

`style.less`ファイルが開いたら、既存のコードをすべて削除し、以下のコードを**上書き**してください。

```css:style.less
.main() {
  /* https://trap.jp/post/1791/ */
  /* Common styles for all callout types */
  .InlineCalloutElement {
    padding: 16px;
    margin: 0 0 24px;
    border: 1px solid transparent;
    border-radius: 8px;
    box-sizing: border-box;
    display: flex;
  }

  /* Icon styles */
  .IconContainer {
    display: block;
    line-height: 0;
    box-sizing: border-box;
    text-align: start;
  }

  /* Content styles */
  .ContentContainer {
    display: block;
    box-sizing: border-box;
    font-size: 15px;
    line-height: 20px;
    margin-inline-start: 8px;
    text-align: start;
    text-size-adjust: 100%;
    > p {
      margin-bottom: 0;
    }
  }

  /* Specific styles for each callout type */
  .callout-memo {
    background-color: #f8f8fb;
    border-color: #d8d8db;
  }

  .callout-alert {
    color: #a94442;
    fill: #d60a34;
    background-color: #feebee;
    border-color: #ebccd1;
  }

  .callout-info {
    color: #3c763d;
    fill: #55c500;
    background-color: #e3f7df;
    border-color: #d6e9c6;
  }

  .callout-warn {
    color: #8a6d3b;
    fill: #f7a535;
    background-color: #fdf9e2;
    border-color: #faebcc;
  }
}

/* bodyとmarkdown-preview.markdown-previewに適用して、previewとpdfのCSSを共通にする */
.main;

.markdown-preview.markdown-preview {
  .main;
}

```

`style.less`を保存すれば、設定は完了です！

## 使い方：Markdownで記述

お好きなMarkdownファイルを開き、以下のようにコールアウトしたいコンテンツを`:::`で囲みコールアウト名を記述します。

```markdown
:::info
**重要なお知らせ:**
新しいサービスが本日リリースされました。詳細はこちらをご覧ください。
サービスの詳細
:::

:::memo
**メモ:**
この設定を変更する前に、必ず現在の設定をバックアップしてください。
バックアップは設定画面から行えます。
:::

:::alert
**警告:**
システムに重大な脆弱性が発見されました。直ちに最新バージョンにアップデートしてください。
アップデート手順はこちらをご確認ください。
:::

:::warn
**注意:**
この操作には時間がかかる場合があります。完了するまでしばらくお待ちください。
処理中は画面を閉じないでください。
:::

```

設定が正しく完了していれば、Markdownプレビュー上で以下のように表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665424/4889817f-9785-4630-9e23-54df16ba47e2.png)

## さらに拡張する

今回の設定では、基本的なコールアウトのデザインを定義しましたが、`style.less`を編集することで、**フォント、色、ボーダー、アニメーションなど、あらゆる要素を自由に変更**できます。

また、`parser.js`の`CALLOUT_TYPES`に新しい名前と`SVG_ICONS`で定義したアイコンを追加し、`style.less`にその新しい名前に対するスタイルを記述することで、**独自のコールアウトタイプを好きなだけ追加**できます。

**例：**

* **アイコンの変更:** `SVG_ICONS`に新しいアイコンのSVGコードを追加し、`CALLOUT_TYPES`でそれを使用する。
* **アニメーションの追加:** `style.less`で`@keyframes`と`animation`プロパティを組み合わせて、動きのあるコールアウトを作成する。
* **より複雑なレイアウト:** `style.less`でFlexboxやGridレイアウトを適用し、アイコンとテキストの配置を細かく制御する。


## 参考にした記事

この記事を作成するにあたり、以下の記事を参考にさせていただきました。

* [VSCodeのMarkdownプレビューをQiitaに近づけてみる #Markdown - Qiita](https://qiita.com/yukimituki11/items/4c56dade735c0bccd475)
* [VSCodeで最強のMarkdownノートエディタを作ろう | 東京科学大学デジタル創作同好会traP](https://trap.jp/post/1791/)
* [Markdown Badges for VSCode markdown-preview-enhanced users - DEV Community](https://dev.to/supportic/markdown-badges-for-vscode-markdown-preview-enhanced-users-3e84)
* [Admonitions - Material for MkDocs](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#inline)（アイコンの参考として）
* [X - ノート作成のヒント](https://communitynotes.x.com/guide/ja/contributing/examples)（ブロックのレイアウトの参考として）