Markdown拡張機能の開発者ガイド

Discourse は Markdown-it という Markdown エンジンを使用しています。

コアのバグを修正したり、新しいプラグインを作成したりするのに役立つ開発者ノートを以下に示します。

基本事項

Discourse はエンジン上にいくつかのヘルパーしか含まれていないため、学ぶべきことの大部分は Markdown It を理解することです。

docs ディレクトリ に現在のドキュメントが含まれています。

以下を読むことを強くお勧めします。

エンジン用の拡張機能を開発するとき、通常は既存のルールを確認するために 2 つ目のエディタを開きます。エンジンは多数のルールのリストで構成されており、各ルールは追跡が比較的簡単な専用のファイルに格納されています。

インラインルールに取り組んでいる場合は、既存のどのインラインルールがそれに似ているかを考え、それに基づいて作業を構築します。

多くの場合、レンダラーを変更するだけで目的の機能を実現できることに注意してください。これは通常、はるかに簡単です。

拡張機能の構造化方法

Markdown エンジンが初期化されると、すべてのモジュールを検索します。

モジュールが /discourse-markdown\\/|markdown-it\\// という名前である場合(つまり、discourse-markdown または markdown-it ディレクトリ内にある場合)、初期化の候補となります。

モジュールが setup という名前のメソッドをエクスポートしている場合、初期化中にエンジンによって呼び出されます。

セットアップ プロトコル

/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js

export function setup(helper) {
  // ... コードはここに記述します
}

setup メソッドは、初期化に使用できるヘルパーオブジェクトへのアクセスを提供します。これには、次のメソッドと変数に関する情報が含まれます。

  • bool markdownIt : このプロパティは、新しいエンジンが使用されている場合に true に設定されます。適切な後方互換性のために確認する必要があります。

  • registerOptions(cb(opts,siteSettings,state)) : 提供された関数は、Markdown エンジンが初期化される前に呼び出され、エンジンを有効にするか無効にするかを決定するために使用できます。

  • allowList([spec, ...]): このメソッドは、HTML をサニタイザーで許可リストに登録するために使用されます。

  • registerPlugin(func(md)): このメソッドは、Markdown It プラグインを登録するために使用されます。

すべてをまとめる

function amazingMarkdownItInline(state, silent) {
   // 標準の markdown it インライン拡張機能をここに記述します。
   return false;
}

export function setup(helper) {
   if(!helper.markdownIt) { return; }

   helper.registerOptions((opts,siteSettings)=>{
      opts.features.['my_extension'] = !!siteSettings.my_extension_enabled;
   });

   helper.allowList(['span.amazing', 'div.amazing']);

   helper.registerPlugin(md=>{
      md.inline.push('amazing', amazingMarkdownItInline);
   });
}

Discourse 固有の拡張機能

BBCode

Discourse には、カスタム BBCode タグに使用できる 2 つのルーラーが含まれています。インラインとブロックレベルのルーラーです。

インライン bbcode ルールは、[b]太字[/b] のようなインライン段落内に存在するものです。

ブロックレベル ルールは、次のような複数行のテキストに適用されます。

[poll]
- option 1

- options 2
[/poll]

md.inline.bbcode.ruler は、順序で適用されるインラインルールのリストを保持します。

md.block.bbcode.ruler は、ブロックレベルルールのリストを保持します。

インラインルールの例は、bbcode-inline.js で多数見つけることができます。

引用符と投票は、bbcode ブロックルールの良い例です。

インライン BBCode ルール

インライン BBCode ルールは、タグの処理方法に関する情報を含むオブジェクトです。

例:

md.inline.bbcode.ruler.push("underline", {
  tag: "u",
  wrap: "span.bbcode-u",
});

これにより、次のようになります。

テスト [u]テスト[/u]

次のように変換されます。

テスト <span>テスト</span>

インラインルールは、テキストをラップすることも、テキストを置き換えることもできます。ラップする場合、追加の柔軟性を得るために関数を渡すこともできます。

md.inline.bbcode.ruler.push("url", {
  tag: "url",
  wrap: function (startToken, endToken, tagInfo, content) {
    const url = (tagInfo.attrs["_default"] || content).trim();

    if (simpleUrlRegex.test(url)) {
      startToken.type = "link_open";
      startToken.tag = "a";
      startToken.attrs = [
        ["href", url],
        ["data-bbcode", "true"],
      ];
      startToken.content = "";
      startToken.nesting = 1;

      endToken.type = "link_close";
      endToken.tag = "a";
      endToken.content = "";
      endToken.nesting = -1;
    } else {
      // bbcode タグを削除するだけ
      endToken.content = "";
      startToken.content = "";

      // エッジケース、自動リンクされた場合にこれが onebox として検出されないようにしたい
      // これにより、削除されないことが保証されます
      startToken.type = "html_inline";
    }

    return false;
  },
});

ラッパー関数は、以下へのアクセスを提供します。

  • bbcode 経由で指定されたキー/値の辞書である tagInfo。

    [test=testing]{_default: "testing"}
    [test a=1]{a: "1"}

  • インラインを開始するトークン

  • インラインを終了するトークン

  • bbcode インラインの内容

この情報を使用して、あらゆる種類のラッパーのニーズに対応できます。

時には BBCode ブロック全体を置き換えたい場合があります。その場合は、replace を使用できます。

md.inline.bbcode.ruler.push("code", {
  tag: "code",
  replace: function (state, tagInfo, content) {
    let token;
    token = state.push("code_inline", "code", 0);
    token.content = content;
    return true;
  },
});

この場合、[code]コードブロック[/code] 全体を単一の code_inline トークンに置き換えています。

ブロック BBCode ルール

ブロック bbcode ルールでは、ブロック全体を置き換えることができます。単純なケースでは、ブロック API は同じです。

md.block.bbcode.ruler.push("happy", {
  tag: "happy",
  wrap: "div.happy",
});
[happy]
こんにちは
[/happy]

次のように変換されます。

<div class="happy">こんにちは</div>

関数ラッパーは、ラップトークンがないため、わずかに異なる API を持ちます。

md.block.bbcode.ruler.push("money", {
  tag: "money",
  wrap: function (token, tagInfo) {
    token.attrs = [["data-money", tagInfo.attrs["_default"]]];
    return true;
  },
});
[money=100]
**テスト**
[/money]

次のように変換されます。

<div data-money="100">
  <b>テスト</b>
</div>

before および after ルールを使用すると、ブロックレンダリングに対する完全な制御を取得できます。これにより、タグを二重ネストするなどを行うことができます。

md.block.bbcode.ruler.push("ddiv", {
  tag: "ddiv",
  before: function (state, tagInfo) {
    state.push("div_open", "div", 1);
    state.push("div_open", "div", 1);
  },
  after: function (state) {
    state.push("div_close", "div", -1);
    state.push("div_close", "div", -1);
  },
});
[ddiv]
テスト
[/ddiv]

次のように変換されます。

<div>
  <div>テスト</div>
</div>

テキストの置き換えの処理

Discourse には、正規表現をテキストに適用するための特別なコアルールが付属しています。

md.core.textPostProcess.ruler

使用方法:

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, //正規表現フラグはサポートされていません
  onMatch: function (buffer, matches, state) {
    let token = new state.Token("text", "", 0);
    token.content = "fast " + matches[0];
    buffer.push(token);
  },
});
私は車とバスが好きです

次のように変換されます。

<p>私は fast 車と fast バスが好きです</p>

このドキュメントはバージョン管理されています。変更の提案はgithubで行ってください。

「いいね!」 35