post_1 の後にカスタム HTML を追加する方法

投稿はウィジェットです。つまり、HTML を追加するだけでは済まず、もう少し手間がかかります。

Discourse のテーマにはウィジェットを装飾する機能があるため、それを利用できます。

ウィジェットの装飾については上記のリンクで解説されていますので、ここではあなたがやりたいこと、つまり「すべてのトピックの最初の投稿の後にマークアップを追加する」ことに焦点を当てましょう。

まずはすべての投稿にマークアップを追加することから始めます。以下のようなコードです。

<script type="text/discourse-plugin" version="0.8">
api.decorateWidget("post:after", helper => {
  return helper.h("div", "test text");
});
</script>

これをテーマのヘッダーセクションに追加します。これで各投稿の下部に「test text」が表示されるはずです。

上記のスクリプトを分解して説明します。

api.decorateWidget("post:after", helper => {

decorateWidget メソッドを呼び出します。対象のウィジェットは post、対象の位置は after です。つまり、投稿ウィジェットの後に追加されます。

helper

これは組み込みのヘルパーで、後で説明するさまざまな機能にアクセスできます。

return helper.h("div", "test text")

これが追加したいマークアップです。HTML が含まれていないことに気づくかもしれませんが、Discourse のウィジェットは生の HTML ではなく仮想ノードを生成するためです。

仮想ノードとは何か、あるいは構文がどのように機能するかを説明するのはこのトピックの範囲外なので省略します。仮想ノードの作成方法についてのハウツーを書く予定ですが、ここではいくつかの例を示します。

helper.h("div", "test text")

は以下のものをレンダリングします。

<div>test text</div>

また、

return helper.h("div#custom-ad", [
  helper.h(
    "a.custom-ad-link",
    { href: "example.com" },
    helper.h("img", { src: "https://picsum.photos/id/74/750/90" })
  )
]);

は以下のものをレンダリングします。

<div id="custom-ad">
  <a href="example.com" class="custom-ad-link">
    <img src="https://picsum.photos/id/74/750/90">
  </a>
</div>

要するに、ノードは以下のような形をしています。

helper.h(selector, {properties}, children)

これは仮想ノードのハウツーで詳しく説明します。

さて、ノードの準備が整ったので、スクリプト全体をテーマのヘッダーセクションに追加するだけです。以下のようなコードです。

<script type="text/discourse-plugin" version="0.8">
  api.decorateWidget("post:after", helper => {
    return helper.h("div#custom-ad", [
      helper.h(
        "a.custom-ad-link",
        { href: "example.com" },
        helper.h("img", { src: "https://picsum.photos/id/74/750/90" })
      )
    ]);
  });
</script>

ただし、まだ問題があります。広告がストリーム内のすべての投稿の下に挿入されてしまうのは理想的ではありません。

ここでヘルパーが役立ちます。投稿の属性がヘルパーに渡されるため、以下のように簡単に確認できます。

console.log(helper)

これにより、作業に使用できるすべての投稿属性が表示されます。

これらは単なる例で、他にも多くの属性があります。

幸いなことに、firstPost 属性が利用可能です。したがって、残っているのは条件分岐を追加することだけです。これが最初の投稿である場合にのみ広告マークアップをレンダリングし、そうでない場合は何もしないという条件です。以下のようなコードです。

<script type="text/discourse-plugin" version="0.8">
api.decorateWidget("post:after", helper => {
  const firstPost = helper.attrs.firstPost;
  const h = helper.h;
  if (firstPost) {
    return h("div#custom-ad", [
      h(
        "a.custom-ad-link",
        { href: "example.com" },
        h("img", { src: "https://picsum.photos/id/74/750/90" })
      )
    ]);
  }
});
</script>

これで広告が最初の投稿の後にのみ挿入されます。もう一つ追加すべきことは、画像に高さを設定することです。そうしないと、読み込み時に画面が揺らぐ(ジッター)原因になります。前述の通り、画像タグの height 属性はプロパティなので、src の隣に追加する必要があります。

これらをすべて組み合わせた、あなたが目指す機能を実現するための最終的なコードは以下の通りです。

<script type="text/discourse-plugin" version="0.8">
api.decorateWidget("post:after", helper => {
  const firstPost = helper.attrs.firstPost;
  const h = helper.h;
  if (firstPost) {
    return h("div#custom-ad", [
      h(
        "a.custom-ad-link",
        { href: "example.com" },
        h("img", { src: "https://picsum.photos/id/74/750/90", height: "90" })
      )
    ]);
  }
});
</script>

最後に強調したいのは、仮想ノードが難しすぎる場合は生の HTML を使用することも可能ですが、推奨はされません。仮想ノードを使用する方がはるかに優れています。生の HTML を使用した場合のスクリプトは以下のようになります。

<script type="text/discourse-plugin" version="0.8">
  const RawHtml = require("discourse/widgets/raw-html").default;
  api.decorateWidget("post:after", helper => {
    const firstPost = helper.attrs.firstPost;
    if (firstPost) {
      return [
        new RawHtml({
          html: `<div id="custom-ad">
                   <a href="example.com">
                     <img src="https://picsum.photos/id/74/750/90" height="90">
                   </a>
                 </div>`
        })
      ];
    }
  });
</script>

ただし、繰り返しになりますが、これは推奨されません。