📝

RemarkでZenn形式のmarkdownを再現する

2023/09/25に公開

概要

この記事はブログとZennに同時投稿しているのですが、その際にZen独自のmarkdown記法を使いたいときがあります。ブログ側ではmarkdownの表示にremarkを使っているのでremarkでそれらを表示したいという趣旨です。

Directive

まずはディレクティブ記法です。

:::message
メッセージをここに
:::

このようなコードで

が表示されるというものです。これはどうやら一般にdirectiveと呼ばれる記法のようです。
これはなんかメジャーっぽいしお目当てのプラグインで既にあるのではと思い探すとありました。これです。

これでdirective記法はクリア…かと思いきや実はこのプラグインではZennの記法を再現することはできません。これはZennでは

:::message alert
:::

:::details タイトル
内容
:::

のようにディレクティブ名の後にタイトルなどを記述するのですがこれはremark-directiveではサポートされていないからです。

なのでしょうがないのでremark-directiveにパッチを当てることにしました。パッチの内容は以下です。

micromark-extension-directive@2.2.1.patch
diff --git a/lib/factory-name.js b/lib/factory-name.js
index 4599862b23fcad95ac6ccc87a35ebca1a86aaa0b..083bbc056f41dd141e15f93e486f619aa7e3c67a 100644
--- a/lib/factory-name.js
+++ b/lib/factory-name.js
@@ -29,11 +29,11 @@ export function factoryName(effects, ok, nok, type) {
 
   /** @type {State} */
   function name(code) {
-    if (code === 45 || code === 95 || asciiAlphanumeric(code)) {
+    if (code && code !== 10 && code > 0) {
       effects.consume(code)
       return name
     }
     effects.exit(type)
-    return self.previous === 45 || self.previous === 95 ? nok(code) : ok(code)
+    return self.previous === 45 || self.previous === 95 || self.previous === 32 ? nok(code) : ok(code)
   }
 }
diff --git a/package.json b/package.json
index 43ac4e41363a82066fb338ef385bed9eefa83075..0ef29ce297f92a9fedda1a3771bd01b941647690 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,6 @@
   ],
   "exports": {
     "types": "./index.d.ts",
-    "development": "./dev/index.js",
     "default": "./index.js"
   },
   "dependencies": {

このパッチでは、英字か数字のみがdirectiveのラベルで有効だったものを全ての文字で有効になるように変えています。これによって:::directive{.class}のような記法は使えなくなりますが今回は問題ないのでよしとします。

remark-directiveはこのプラグインを読み込んだ後に独自のプラグインで実際にディレクティブを有効なhtmlデータなどに変換します。
messageディレクティブとdetailsディレクティブを実装したものがこちらです。detailsの方は無理くりmdxの内部形式に変換しています。

import { h } from "hastscript";
import { Root } from "mdast";
import type { Plugin } from "unified";
import { visit } from "unist-util-visit";

export const remarkCustomDirective: Plugin<[], Root> = () => {
  return (tree) => {
    visit(tree, (node: any) => {
      if (
        node.type === "textDirective" ||
        node.type === "leafDirective" ||
        node.type === "containerDirective"
      ) {
        const [name, ...n] = node.name.split(" ");
        let value: string | null = null;
        if (n.length > 0) {
          value = n.join(" ");
        } else {
          value = null;
        }
        if (name === "message") {
          node.attributes.class = `note ${value ?? "warn"}`;

          const data = node.data || (node.data = {});
          const tagName = node.type === "textDirective" ? "span" : "div";

          data.hName = tagName;

          data.hProperties = h(tagName, node.attributes).properties;
        } else if (name === "details") {
          const children = [...node.children];
          node.type = "mdxJsxFlowElement";
          node.name = "details";
          node.attributes = [];
          node.children = value
            ? [
              {
                type: "mdxJsxFlowElement",
                name: "summary",
                attributes: [],
                children: [
                  {
                    type: "text",
                    value,
                  },
                ],
                data: {
                  _mdxExplicitJsx: true,
                },
              },
              ...children,
            ]
            : children;
          node.data = {
            _mdxExplicitJsx: true,
          };
        }
      }
    });
  };
};

これを使うにはremark-directiveと上のremarkCustomDirectiveの両方のプラグインを読み込みます。

この記事は https://note.nazo6.dev/blog/remark-zenn-markdown とのクロスポストです。

GitHubで編集を提案

Discussion