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

投稿内に顧客用バナー(アフィリエイト目的)を追加したいと考えています。具体的には<article id="post_1..." の直後に、この(例示の)HTML ブロックを追加したいのです。

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

つまり、以下のような形になります。

CSS の :after では限られた成果しか得られませんでした。そこで、この例 のような <script></HEAD> 内で使用して、この処理を実行できるか疑問に思っています。

編集: さらに少し試行錯誤した結果、<div class="topic-map"> の末尾にバナーを挿入する方がより適切だと考えられます。

<div class="topic-map">
    <section class="map map-collapsed">...</section>
    ここにカスタム HTML
</div>

投稿はウィジェットです。つまり、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>

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

AからZまでの過程を説明してくれる解決策にはいつも感謝しています。単なる答えだけでなく、どうやってたどり着くのかを教えてくれるので。ありがとうございます。

この投稿は気に入りましたが、一点付け加えさせてください。これは熱狂的な説明です。このような投稿には本当に感謝しています。

それは、@johani:fire: :hot_pepper: :muscle: だからです。