我一直在编写一个 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');
pfaffman
(Jay Pfaffman)
2023 年11 月 16 日 10:56
2
我认为您想将代码放在初始化程序中,而不是放在 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”中,然后单击应用。
Chris Dawson:
正确的方法是使用插件添加代码
不正确。
如果你不修改 API(例如,使用 100% 的 JavaScript),则无需使用插件,因为插件部署和切换起来更麻烦。
如果你只是修改或添加一些 JavaScript,主题组件应该就足够了。
2 个赞
好的。但是,当我切换主题(或者允许用户选择自己的主题)时,我是否面临这样的问题:我需要确保每个主题都 1) 可编辑,以便我添加新的 head 标签,甚至更糟糕的是 2) 需要在所有主题中维护我的代码?
当我开始使用不同的主题时,有些主题表明要编辑主题需要修改 github 仓库。这似乎非常繁重且不灵活。但是,在每个主题中维护我的脚本标签似乎非常容易出错,而且是一个更大的问题。
我错过了什么?有没有办法仅通过使用主题来解决这些问题?
1 个赞
在这种情况下,您会将其制作成一个主题组件,并将该组件添加到所有主题中。(我承认这会增加一些复杂性)。
如果您想专业地运行该网站,确保您的所有代码都在 Github 上确实是一个非常好的主意。
但是,最初当您只是尝试一些想法时,当然,您可以随意在“本地”进行修改。
当代码更改稳定下来后,您应该将其放入源代码管理中,但我认为越早越好。
一种将快速演进与 GitHub 结合的方法是将其与此结合:
The [discourse] Discourse Theme CLI is a ruby gem that allows you to use your editor of choice when developing Discourse themes and theme components. As you save files the CLI will update the remote theme or component and changes to it will appear live!
Installing
To play with it, make sure you have Ruby 2.5 or up installed.
[image]
If you are on Windows, you have 2 options:
Option 1: Windows Subsystem for Linux .
Windows 10 has access to a full Linux environment, you can use it to install …
并在测试主题中部署到测试主题组件……但现在我们真的要玩得开心了……
这允许您即时部署,然后一旦满意,就可以将您的更改固定到 git 存储库。
1 个赞
pfaffman
(Jay Pfaffman)
2023 年11 月 18 日 13:52
11
RGJ
(Richard - Communiteq)
2023 年11 月 18 日 13:56
12
这就是您需要的 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 个赞
您好 @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 接收的内容。
RGJ
(Richard - Communiteq)
2023 年11 月 19 日 14:26
15
您只需\n\nimport { withPluginApi } from \"discourse/lib/plugin-api\";
3 个赞
您应该浏览 Theme component 中链接的源(生态系统几乎都是开源的——尽情使用吧!)。
您会注意到导入是必需的,也是常见的(api.onPageChange 和其他有用的 api 函数的使用也是如此)。
4 个赞
system
(system)
关闭
2023 年12 月 19 日 18:30
17
This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.