关于 "on-discourse" javascript 用于为各页面设置自定义JS的反馈?

我一直在编写一个 Svelte 应用,它需要在每个页面上自行安装。我找到了将我的 JS 文件添加到 head 部分并在首次加载页面时加载它的方法。但是,在我进行实验时,我意识到 discourse 通过 XHR 拉取新内容并替换某些部分,因此我的应用在加载新页面时没有重新初始化。

我尝试了各种方法来在页面更改时获得通知,但 Ember 似乎不提供钩子,而且我找不到可以监听的自定义事件。

看起来一种方法是向 DOM 添加一个突变观察器并监视更改。我发现“#topic div 似乎是重新加载的那个(并且属性会更改,因此您只需监视属性更改)。我在那里设置了一个突变观察器(突变观察器是监视 DOM 更改的新颖且高效的方法)。它的工作方式是监视更改,当更改发生时,运行一个回调来为该页面重新加载我的 Svelte 应用。

我喜欢这种方法,并希望得到反馈。

一个问题:我是否应该改为监视 URL 更改?注册 popstate 的侦听器是否是更好的主意?

要使用它,请在您的主题/head 中执行类似操作:

<script src="https://files.extrastatic.dev/community/on-discourse.js"></script>
<script src="https://files.extrastatic.dev/community/index.js"></script>
<link rel="stylesheet" type="text/css" href="https://files.extrastatic.dev/community/index.css">

然后,在您的库中,您可以像这样调用 on-discourse:

function log(...msg) {
  console.log('svelte', ...msg);
}

// 这是 Svelte 安装代码
function setup() {
  try {
    const ID = 'my-special-target-id';
    log('Inside setup()');
    const el = document.getElementById(ID);
    if (el) {
      log('Removed existing element', ID);
      el.remove();
    }
    const target = document.createElement("div");
    target.setAttribute("id", ID);
    log('Created target');
    document.body.appendChild(target);
    log('Appended child to body');
    const app = new App({
      // eslint-disable-next-line no-undef
      target
    });
    log('Created app and installed');
  } catch(err) {
    console.error('Unable to complete setup()', err.toString() );
  }
}

(function start() {
  log('Starting custom Svelte app');
  // 重新安装更改
  window.onDiscourse && window.onDiscourse( setup );
  // 加载第一个页面加载的应用
  window.addEventListener('load', () => {
    setup();
  });
  log('Finished custom Svelte app);  
})();

基本上,您只需调用 window.onDiscourse(callback) 并传入您的回调(您可以多次运行它来安装多个回调),然后在发生突变时,将运行该回调来初始化您的应用。

这是 on-discourse.js 的完整代码。(编辑:我已更新此以使用 #topic,这似乎是一件好事,因为属性在页面加载时会更改,然后突变只需要监视属性更改,而不是查看 #main-outlet 的整个 DOM 树)

let mutationObservers = [];

function log(...msg) {
  console.log("on-discourse", ...msg);
}

function observeMainOutlet() {
  log('Observing main outlet');
  // 选择将要观察突变的节点
  const targetNode = document.getElementById("topic");
 
  if (targetNode) {
    // 观察者的选项(观察哪些突变)
    const config = { attributes: true };
    
   // 创建一个观察者实例,在 childList 更改时重置
    const observer = new MutationObserver(function(mutations) {
      let reset = false;
      mutations.forEach(function(mutation) {
	if (mutation.type === 'attributes') {
	  log('Found main-outlet mutation, running callbacks');
	    mutationObservers.forEach( (s,i) => {
		try {
		    log(`Running div callback ${i+1}`);	    
		    s();
		    log(`Finished div callback ${i+1}`);	    
		} catch( err ) {
		    log(`Div callback error (${i+1})`, err );	    	      
		}
	 });
       }
      });
    });
    
    // 开始观察目标节点以获取配置的突变
    observer.observe(targetNode, config);
    
    // 稍后,您可以停止观察
    // observer.disconnect();
    log('Done with outlet observer');
  } else {
    console.error('on-discourse FATAL ERROR: Unable to find main-outlet');
  }
}

window.addDiscourseDivMutationObserver = (cb) => {
    log('Adding on-discourse div mutation callback');  
    mutationObservers.push(cb);
    log('Added on-discourse div mutation callback');    
}

window.addEventListener("load", () => {
    log('Setting up topic observer');
    if (mutationObservers.length > 0) {
	observeMainOutlet();
    }
    log('Created topic observer');  
});

log('Completed setup of on-discourse.js');

我认为您想将代码放在初始化程序中,而不是放在 head 中。请查看开发者指南,了解如何将 JS 放在单独的文件中。

1 个赞

非常感谢您的回复!

我认为这部分解释了我为什么感到困惑,我很难找到任何关于通过添加自己的 JS 来扩展 Discourse 的参考。

当我搜索“Discourse 开发者指南”时,我看到的第一个链接是 GitHub 仓库的链接。

下一个链接是“Discourse 高级开发者安装指南”。本指南旨在告诉您如何为开发设置 Rails,但 AFAIK 没有关于如何安装自定义 JS 的链接。我想避免复杂的构建过程,这正是我从 Rails 时代回忆起来的。我真的很想独立开发这个 JS 扩展代码,然后将一个脚本标签放入我的网站。所以,我真的不想在本地设置一个 Rails 环境来构建它;也许我错过了它的用处?但是,我真的很喜欢能够通过一个包含几个 <script> 标签的主题来更新一个 Docker 容器。

下一个链接是“Discourse 主题开发入门指南”,这是关于开发主题的,不是我需要的,对吗?

我看到了指向 Discourse API 的链接,这显然不是我想要的。

如果我搜索“discourse javascript initializer”,我会看到这个 5 年前的链接:Execute JavaScript code from a plugin once after load 但这似乎是在连接到 Rails,我觉得应该有更简单的方法,而且这个帖子似乎也没有解决?

另一个指向“discourse javascript initializer”的链接建议做我正在做的事情来安装 JS,但没有关于如何确保在页面内容更改时(无论是通过完全页面刷新还是 XHR “turbolinks”之类的请求)的建议:https://stackoverflow.com/questions/48611621/how-do-i-add-an-external-javascript-file-into-discourse

我应该查看这个讨论吗? A versioned API for client side plugins

或者,也许是这个?乍一看我不明白语法(那些注解看起来不像 JS,那是 Rails 约定吗?),所以我不知道这是否是我需要的:Using Plugin Outlet Connectors from a Theme or Plugin

是的,这并非易事。

Discourse 是一个 EmberJS 应用。

理想情况下,JavaScript 扩展应该使用 EmberJS 框架、主题组件(或插件)、Discourse JavaScript API(在适当的情况下)和插件插槽来编写。

使用临时外部脚本时,您将遇到的主要问题是让它们在正确的时间触发。

为确保这一点,您需要将它们链接到组件中的操作(该组件可以设置为在插入或更新时触发)。

1 个赞

这很好地证实了这一点。但是,我必须说,我对我的突变观察者代码非常满意。它能正确检测页面内容何时发生变化,然后允许我运行自定义的 JavaScript 代码。它运行得很完美,我也不必学习 Ember,也无需以任何方式修改 RoR 应用程序。我现在对这个解决方案非常满意。感谢所有的评论。

1 个赞

我刚尝试使用了一个 popstate 事件观察器。如果它能正常工作,那么代码将是 5 行而不是 20 行。但是,点击周围似乎不会触发该事件。如果我使用前进或后退按钮,我确实会看到该事件。我对 popstate 的理解不够深入,但目前我坚持使用 div mutation observer。

我意识到我在这里做了一件愚蠢的事情。我正在修改主题并将代码添加到 head 中。如果我切换到另一个主题,这些更改就会丢失。正确的方法是使用插件添加代码。我在这里使用了模板:https://github.com/discourse/discourse-plugin-skeleton/。然后,我像这样添加了 JavaScript 代码:

export default {
  name: 'alert',
    initialize() {
        const scripts = [
            'https://files.extrastatic.dev/community/on-discourse.js',
            'https://files.extrastatic.dev/community/index.js'
        ];
        scripts.forEach( s => {
            const script = document.createElement('script');
            script.setAttribute('src', s);
            document.head.appendChild(script);
        });

        const link = document.createElement('link');
        link.setAttribute('rel', "stylesheet");
        link.setAttribute('type', "text/css");
        link.setAttribute('href', "https://files.extrastatic.dev/community/index.css");
        document.head.appendChild(link);
    }
};

然后,我运行了 ./launcher rebuild app,然后启用了插件。
最后,我需要在设置中添加 CSP 策略来允许加载这些脚本。我转到 admin->settings->security,然后将 files.extrastatic.dev 添加到“content security policy script src”中,然后单击应用。

不正确。

如果你不修改 API(例如,使用 100% 的 JavaScript),则无需使用插件,因为插件部署和切换起来更麻烦。

如果你只是修改或添加一些 JavaScript,主题组件应该就足够了。

2 个赞

好的。但是,当我切换主题(或者允许用户选择自己的主题)时,我是否面临这样的问题:我需要确保每个主题都 1) 可编辑,以便我添加新的 head 标签,甚至更糟糕的是 2) 需要在所有主题中维护我的代码?

当我开始使用不同的主题时,有些主题表明要编辑主题需要修改 github 仓库。这似乎非常繁重且不灵活。但是,在每个主题中维护我的脚本标签似乎非常容易出错,而且是一个更大的问题。

我错过了什么?有没有办法仅通过使用主题来解决这些问题?

1 个赞

在这种情况下,您会将其制作成一个主题组件,并将该组件添加到所有主题中。(我承认这会增加一些复杂性)。

如果您想专业地运行该网站,确保您的所有代码都在 Github 上确实是一个非常好的主意。

但是,最初当您只是尝试一些想法时,当然,您可以随意在“本地”进行修改。

当代码更改稳定下来后,您应该将其放入源代码管理中,但我认为越早越好。

一种将快速演进与 GitHub 结合的方法是将其与此结合:

并在测试主题中部署到测试主题组件……但现在我们真的要玩得开心了……

这允许您即时部署,然后一旦满意,就可以将您的更改固定到 git 存储库。

1 个赞

请查看 GitHub - discourse/discourse-theme-skeleton: Template for Discourse themes

这就是您需要的 How do you force a script to refire on every page load in Discourse? - #5 by simon

<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() =>{
        // code
    });
</script>
3 个赞

Wow @RGJ,看起来太完美了!谢谢!

2 个赞

您好 @RGJ,我一直在尝试弄清楚如何做到这一点,不得不承认我感到很困惑。看来插件 API 已经随着时间的推移发生了一些变化,我不确定如何获取当前版本,或者查看示例。

您的代码看起来是放在某个 HTML 页面中,因为它被 script 标签包围着。我过去使用的是类似这样的代码,它本身就是一个 JS 文件。

https://extrastatic.dev/publicdo/publicdo-discourse-plugin/-/blob/main/assets/javascripts/discourse/initializers/kanji.js?ref_type=heads

我该如何修改我的插件来使用您提供的代码?您建议的这段代码是应该放在插件中,还是放在主题中,或者以其他方式添加到页面中?

我尝试使用类似这样的代码:

export default {
  name: 'publicdo',
    initialize() {
     withPluginApi('0.1', api => {
                api.onPageChange(() => {
                   console.log('在这里运行我的代码。');
                });
      });
    }
}

但是,这会失败并出现 Uncaught (in promise) ReferenceError: withPluginApi is not defined 错误,所以这显然不是一般加载的 JS 接收的内容。

您只需\n\nimport { withPluginApi } from \"discourse/lib/plugin-api\";

3 个赞

您应该浏览 Theme component 中链接的源(生态系统几乎都是开源的——尽情使用吧!)。

您会注意到导入是必需的,也是常见的(api.onPageChange 和其他有用的 api 函数的使用也是如此)。

4 个赞

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