在主题中嵌入文本内的窗口小部件

你好!!:slight_smile:

我希望这也能引起其他人的兴趣:我正在尝试创建一个“社交媒体动态”主题,在其中我想嵌入不同平台的组件,以便用户(以及管理员!)可以在一个页面上快速查看所有内容,这样他们就能留在论坛内,避免因需要在众多社交平台之间切换以获取快速更新而产生的负担(考虑到它们通常分享相同的内容),从而提高使用论坛作为社区发展枢纽的积极性。

我发现的一个不错的组件生成器是 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 内容,包括您的脚本
  </head>
  <body>
    <section id="main">
      页面内容
    </section>
  </body>
</html>

当您导航到另一个页面时,唯一重新加载的内容是以下部分内部的内容:

<section id="main">

因此,DOM 已更改,您的自定义脚本不会再次触发。如果您尝试直接访问主题页面,您会发现它可以正常加载。

那么,现在的问题是如何使其与 Discourse 协同工作。

plugin-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;
};

// 创建帖子装饰器
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 代码,会注意到其顶部有两个选项。

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

WOXO_SCRIPT_SRC 更改为 woxo 提供的 src 地址。对于您创建的所有嵌入内容,该地址应相同。

PREVIEW_ICON 更改为您希望在编辑器预览中使用的图标名称。在编辑器中运行此代码开销较大,因此编辑器会显示一个静态预览,如下所示:

您选择的图标将显示在中间。

如果您想跟随了解代码的具体执行过程,以下是带注释的代码副本:

带注释的代码
<script type="text/discourse-plugin" version="0.8">
// 选项
const WOXO_SCRIPT_SRC = "https://cdn2.woxo.tech/a.js#616348fb53c1e8001686c619";
const PREVIEW_ICON = "heart";

// 我们使用 Discourse 的 Load script 库来确保脚本正确加载。
// 不用担心,它足够智能,如果脚本已加载则不会重复加载。
const loadScript = require("discourse/lib/load-script").default;

// 我们加载 Discourse 的 iconHTML 函数以获取我们要在静态编辑器预览中使用的图标 SVG。
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;
};

// 创建帖子装饰器
api.decorateCookedElement(
  post => {
    // 此帖子是否包含 woxo 小部件?
    const woxoWidgets = post.querySelectorAll("div[data-mc-src]");

    // 是的,那么让我们做一些处理。
    if (woxoWidgets.length) {
      // 遍历每个 woxo 小部件
      woxoWidgets.forEach(woxoWidget => {
        // 如果是编辑器中的小部件,将其替换为静态预览并提前退出
        if (post.classList.contains("d-editor-preview")) {
          woxoWidget.innerHTML = previewMarkup();
          return;
        }

        // 如果不在编辑器中,则加载 woxo 脚本。
        loadScript(WOXO_SCRIPT_SRC).then(() => {
          // woxo 脚本非常特殊。如果脚本标签没有空的 data-usrc 属性,它将无法工作。因此,让我们添加它。
          const script = document.head.querySelector(
            `script[src*="cdn2.woxo.tech"]`
          );
          script.dataset.usrc = "";

          // 一切就绪,让我们调用 woxo 脚本中的 init 方法。
          window.MC.Loader.init();
        });
      });
    }
  },
  // 为装饰器添加 id 以避免内存泄漏
  { id: "render-woxo-widgets" }
);

</script>
3 个赞

首先:天啊,非常感谢你如此多的帮助 :exploding_head:
我对回应的深度感到惊讶,看到如此大量的工作,我唯一的安慰是,这肯定会对许多人产生巨大的帮助。这开启了许多新的可能性,所有这些都利于将论坛打造为社区枢纽的发展。

其次:非常抱歉我回复得这么晚,而你们却如此迅速地作出了回应。首先,我一时无法尽快使用电脑;其次,我只希望在确实仔细通读了你如此慷慨地逐行注释的代码后(一行一行地 :flushed:,天哪,太感谢你了)再回复。当然,一切运行完美,整个加载速度非常快……我欠你一个人情 :pray:

再次感谢,Johan,我真心希望未来能以自己的经验贡献一份力量 :pray:

附注:我会完全保留用于静态预览的心形图标。

1 个赞

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