トピック内でウィジェットをテキストに埋め込む

こんにちは!! :slight_smile:

もし誰かの参考になればと思い投稿します。私は「ソーシャルメディアフィード」トピックを作成しようとしています。ここでは、さまざまなプラットフォームのウィジェットを埋め込んで、ユーザー(そして管理者も!)がすべての情報をたった1ページで素早く確認できるようにしたいと考えています。これにより、ユーザーはフォーラムから離れることなく、多数のソーシャルプラットフォームを切り替えて更新を確認する手間(およびそれによる負荷)を減らすことができます。また、それらのプラットフォームは通常同じコンテンツを共有しているため、フォーラムをコミュニティ開発のハブとして利用する意欲を高める効果も期待できます。

目的に合致するきれいでシンプルなウィジェットジェネレーターとして「Woxo」を見つけました。しかし、現在そのウィジェットをトピックに埋め込む方法がわからず困っています。iframe などの回避策があるか調べていますが、そもそもこれが可能なのかどうか、ご意見をお聞かせください。

以下は、Woxo から取得した Instagram フィードのコードです。

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>
        
<script 
  src="https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619" 
  async data-usrc>
</script>

これまでに試したことは以下の通りです:

  • <script> タグを <body> セクション、<footer><header> に配置してみる(現在は header に残しています)
  • スクリプトのソース URL がホワイトリストに登録されていることを確認する(この場合は https://cdn2.woxo.tech/)
  • defer を追加しても効果なし(念のため残しています)

ページを検証すると、スクリプトは body セクションの下部(内部)に表示されています。ソースがホワイトリストに登録されているため、正常に動作するはずです。ブラウザの問題かと思い確認しましたが、W3Schools Tryit Editor でこの HTML を実行すると問題なく動作します。

エラーの原因を特定したところ、JS スクリプト内の特定の関数にありました。以下の呼び出しで null 値が返されます。これが唯一のランタイムエラーです。

e=document.querySelector("div[data-mc-src]")
e is null

その div はトピック内に記述されています(<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div> の部分)。これは純粋な HTML コードとして残っているため、読み取れるはずです。何らかの理由で、スクリプトがそれを見つけることができません。

defer 属性を使い、スクリプトを footer に配置するとエラーは発生しなくなります(以前エラーが出ていたことから、JS ファイルの URL は確かにホワイトリストに登録されていることが証明されます)。しかし、なぜ何も動作しないのか、今は手がかりがありません。

ご意見いただければ幸いです。お時間を割いていただき、ありがとうございます! :slight_smile:
Lisandro

追記:最終的に断念せざるを得ませんでした。iframe のみサポートされているため、現在は iframe を提供してくれる良いウェブサービスを探しています。無料サービスは制限が強すぎる場合が多く、有料版はフォーラムホスティングサービスの料金の倍以上のコストがかかります。愚痴になってしまい申し訳ありませんが、無料の HTML コードをただ挿入できないことに腹を立てて大声で叫びたくなりました :cry:

「いいね!」 2

Discourse はシングルページアプリケーションです。あなたが直面している問題は、使用しているスクリプトがその仕組みを認識していないことに起因します。Discourse のホームページや他のページにアクセスすると、以下のような構造が表示されます。

<html>
  <head>
    head content including your script
  </head>
  <body>
    <section id="main">
      page content
    </section>
  </body>
</html>

別のページに移動すると、再読み込みされるのは以下のセクション内のコンテンツだけです。

<section id="main">

したがって、DOM が変更されるため、カスタムスクリプトは再度実行されません。トピックページに直接アクセスしようとすると、正常に読み込まれることが確認できます。

.

さて、問題はこれを Discourse で動作させる方法です。

プラグイン API には、投稿を「装飾」するためのメソッドがあります。

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.js#L282-L318

これを使用して、投稿がレンダリングされたときにサードパーティのスクリプトを実行できます。

以下に必要なコードです。これをテーマの common > header タブに追加してください。

<script type="text/discourse-plugin" version="0.8">
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

const loadScript = require("discourse/lib/load-script").default;
const { iconHTML } = require("discourse-common/lib/icon-library");

const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;
  return markup;
};

// create a post decorator
api.decorateCookedElement(
  post => {
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    if (woxoWidgets.length) {
      woxoWidgets.forEach(woxoWidget => {
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        loadScript(WOXO_SCRIPT_SRC).then(() => {
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";
          window.MC.Loader.init();
        });
      });
    }
  },
  { id: "render-woxo-widgets" }
);
</script>

次に、CSP のためにいくつかのドメインを追加する必要があります。これらを以下のサイト設定に追加してください。

content_security_policy_script_src
https://*.woxo.tech/
https://us-central1-core-period-259421.cloudfunctions.net/availableComponentTracks

最後に、静的なコンポーザープレビューのために少し CSS を追加する必要があります。

これはテーマの common > CSS タブに入れます。

.woxo-preview {
  height: 400px;
  width: 100%;
  background: var(--primary-low);
  display: flex;
  align-items: center;
  justify-content: center;
  .woxo-preview-icon {
    font-size: var(--font-up-4);
    color: var(--primary-high);
  }
}

その後、任意の投稿に以下のコードを追加するだけで、ウィジェットがレンダリングされ、完全に機能するようになります。

<div data-mc-src="f4b43a8f-c188-4f80-8206-36d9f7529f13#instagram"></div>

JavaScript を見ると、非常に上部に 2 つのオプションがあることに気づくでしょう。

const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

WOXO_SCRIPT_SRC を woxo から提供される src に変更してください。作成するすべての埋め込みで同じであるはずです。

PREVIEW_ICON を、コンポーザープレビューで使用したいアイコンの名前に変更してください。このコードをコンポーザーで実行するのは少しコストがかかるため、コンポーザーには以下のような静的なプレビューがあります。

選択したアイコンが中央に表示されます。

何が起きているか追いたい方のために、コメント付きのコードのコピーを以下に示します。

commented code
<script type="text/discourse-plugin" version="0.8">
// options
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

// we use the Discourse Load script lib to ensure scripts are loaded
// properly. Don't worry, this is smart enough to not duplicate the script
// if it's already loaded
const loadScript = require("discourse/lib/load-script").default;

// we load the Discourse Icon HTML function to get the svg for the icon
// we want to use in the static composer preview
const { iconHTML } = require("discourse-common/lib/icon-library");

// setup the composer preview icon
const composerPreviewIcon = iconHTML(PREVIEW_ICON, {
  class: "woxo-preview-icon"
});

// create a helper function for the composer preview markup
const previewMarkup = () => {
  const markup = `<div class="woxo-preview">${composerPreviewIcon}</div>`;

  return markup;
};

// create a post decorator
api.decorateCookedElement(
  post => {
    // does this post have woxo widgets?
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    // Yes, so let's do some work.
    if (woxoWidgets.length) {
      // for each woxo widget
      woxoWidgets.forEach(woxoWidget => {
        // if it's a composer widget, swap it out for a static preview and
        // quit early
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        // if it's not in the composer, load the woxo script.
        loadScript(WOXO_SCRIPT_SRC).then(() => {
          // The woxo script is very strange. It won't work unless the script
          // tag has an empty data-usrc attribute. So, let's add it
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";

          // everything is ready, let's call the init method in the woxo script
          window.MC.Loader.init();
        });
      });
    }
  },
  // add an id to the decorator to avoid memory leaks
  { id: "render-woxo-widgets" }
);

</script>
「いいね!」 3

まず第一に、O-M-G、本当にありがとう:exploding_head:
こんなに迅速で丁寧な対応をいただけて驚いています。これほどの労力を注いでもらえたことは、多くの人にとって大きな助けになるに違いないという確信が、唯一の慰めです。これにより多くの新しい可能性が開け、コミュニティのハブとしてのフォーラムの発展に大きく寄与することでしょう。

次に、あなたがすぐに返信してくれたのに、私が遅れて返信してしまったことを心よりお詫びします。最初はすぐにパソコンにアクセスできず、さらに、あなたが親切にコメントしてくださったコード(行ごとに :flushed:、なんてこと!本当にありがとうございます)をじっくり確認してからお返事したかったからです。もちろん、すべてが完璧に動作し、フィード全体が非常に高速に読み込まれました。本当に感謝しています :pray:

もう一度、ヨハンさん、ありがとうございます。私自身の実経験を通じて貢献できることを心から願っています :pray:

追伸:静的プレビュー用のハートは完全にそのまま残します。

「いいね!」 1

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.