markdown.it は、Discourse が使用する CommonMark エンジンであり、幅広いプラグイン を備えています。
ヘッダーアンカー、定義リスト、スマートアローなど、枚挙にいとまがありません。
まず警告
CommonMark は… Common(一般的)であることを意図しています。仕様から離れるほど、Markdown は一般的でなくなります。他のソリューションへの移植が難しくなったり、注意しないと解析に内部的な不整合を引き起こしたりする可能性があります。何かを再パッケージ化する前に、「本当にこれを再パッケージ化したいのか?」という質問に答えるようにしてください。
私はちょうど Discourse Footnote の脚注を再パッケージ化し終えたところで、これを正しく行う方法についていくつかの教訓を共有できます。
怠け者のための手順
もしあなたが怠け者で、すぐに始めたいのであれば、脚注をフォークしてファイル名と変数名を入れ替えるのが最も簡単な方法です。私はベストプラクティスに従うように細心の注意を払ったので、しっかりとした例が得られるはずです。
最初の動き、最小限の再パッケージ化
私の知る限り、markdown.it プラグインの大部分はプレーンな JavaScript ファイルとして出荷されています。多くの場合、プラグインは単一の JS ファイルとして出荷されます。例:markdown-it-mark.js。
理想的には、オリジナルをそのままにしておきたいので、既存のプラグインをいじる必要なく、ファイルの更新版をプラグインにコピーするだけで済みます。
最初に直面する問題は、Markdown エンジンもサーバー側で実行されるため、プラグインにこの JavaScript をサーバー側で読み込ませる方法を教える必要があることです。そのためには、ファイルをそのまま assets/javascripts/vendor/original-plugin.js にコピーし、plugin.rb ファイルで以下を追加するだけです。
# これは、私たちの markdown エンジンにバニラ js ファイルを読み込ませる方法を教えます
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text
プラグインの本体を含めたら、パイプラインにそれを読み込み、初期化する方法を教える必要があります。
assets/javascripts/lib/discourse-markdown/your-extension.js という名前のファイルを作成します。
このファイルは、.js で終わり、かつ discourse-markdown ディレクトリ内にあるため、自動的に読み込まれます。
簡単な例は次のとおりです。
export function setup(helper) {
// これにより、サイト設定が有効な場合にのみ拡張機能を読み込むことができます
helper.registerOptions((opts, siteSettings) => {
opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
});
// サポートする必要のある属性をホワイトリストに登録します。
// そうしないと、サニタイザーによってそれらが削除されます
helper.whiteList(["div.amazingness"]);
// また、このように凝ったこともできます
helper.whiteList({
custom(tag, name, value) {
if ((tag === "a" || tag === "li") && name === "id") {
return !!value.match(/^fn(ref)?\d+$/);
}
},
});
// 最後に、これをパイプラインで拡張機能を登録するために使用する魔法のコードです
// whateverGlobal はプラグインが公開するグローバル変数の名前です
// 単一の (md) 変数を受け取り、それを使用してパイプラインを修正します
helper.registerPlugin(window.whateverGlobal);
}
常にテストする
Discourse の bin/rake autospec はプラグインを認識しています ![]()
これは、spec/pretty_text_spec.rb ファイルを追加すると、保存するたびにプラグインのテストファイルが実行されることを意味します。
私はこれを多用しています。作業がずっと速くなるからです。
たとえば、投稿内のすべての数値を 8 の円に変更するプラグインを追加したとします。これを discourse-magic-8-ball と呼びます。
テストの構成方法は次のとおりです。
require "rails_helper"
describe PrettyText do
it "can be disabled" do
SiteSetting.enable_magic_8_ball = false
markdown = <<~MD
1 thing
MD
html = <<~HTML
<p>1 thing</p>
HTML
cooked = PrettyText.cook markdown.strip
expect(cooked).to eq(html.strip)
end
it "supports magic 8 ball" do
markdown = <<~MD
1 thing
MD
html = <<~HTML
<p>8 circle thing</p>
HTML
cooked = PrettyText.cook markdown.strip
expect(cooked).to eq(html.strip)
end
end
投稿を「デコレート」する必要があるかもしれません
プラグインが投稿に余分な「動的」機能を追加するのに最適な場合があります。例として poll プラグインや、ツールチップを動的に表示する「…」を追加する footnotes プラグインがあります。
投稿を「デコレート」する必要がある場合は、assets/javascripts/api-initializers/your-initializer.js を追加します。
import { apiInitializer } from "discourse/lib/api";
export default apiInitializer((api) => {
const siteSettings = api.container.lookup("service:site-settings");
if (!siteSettings.enable_magic_8_ball) {
return;
}
api.decorateCookedElement((elem) => {
// ここにあなたの素晴らしい魔法を記述します
});
});
投稿を「ポストプロセス」する必要があるかもしれません
場合によっては、投稿を「ポストプロセス」する必要があるかもしれません。Markdown レンダリングエンジンは、たとえば post_id のような特定の情報を認識しないように設計されています。場合によっては、サーバー側で投稿と「調理済み」HTML にアクセスして、バックグラウンドジョブをトリガーしたり、カスタムフィールドを同期したり、「自動生成された」HTML を「修正」したりしたいことがあります。
脚注の場合、各脚注に固有の id が必要だったため、post_id へのアクセスが必要になり、ポストプロセッサ(sidekiq で実行される)で HTML に変更を加える必要がありました。
フックするには、plugin.rb ファイルに以下を追加します。
DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
doc.css("a.always-bing").each do |a|
# これは常に bing に行くはずです
a["href"] = "https://bing.com"
end
end
カスタム CSS が必要な場合があります
カスタム CSS を同梱したい場合は、plugin.rb でファイルを登録するようにしてください。
CSS を assets/stylesheets/magic.scss に追加し、次に
register_asset "stylesheets/magic.scss"
を実行します。
開発中は、プラグイン CSS を変更すると、変更がオンザフライで表示されるように、変更を「自動リロード」することを忘れないでください。
再パッケージ化の冒険がうまくいきますように ![]()
このドキュメントはバージョン管理されています - 変更の提案は github で行えます。