我一直在编写一个 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 放在单独的文件中。
非常感谢您的回复!
我认为这部分解释了我为什么感到困惑,我很难找到任何关于通过添加自己的 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(在适当的情况下)和插件插槽来编写。
使用临时外部脚本时,您将遇到的主要问题是让它们在正确的时间触发。
为确保这一点,您需要将它们链接到组件中的操作(该组件可以设置为在插入或更新时触发)。
这很好地证实了这一点。但是,我必须说,我对我的突变观察者代码非常满意。它能正确检测页面内容何时发生变化,然后允许我运行自定义的 JavaScript 代码。它运行得很完美,我也不必学习 Ember,也无需以任何方式修改 RoR 应用程序。我现在对这个解决方案非常满意。感谢所有的评论。
我刚尝试使用了一个 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,主题组件应该就足够了。
好的。但是,当我切换主题(或者允许用户选择自己的主题)时,我是否面临这样的问题:我需要确保每个主题都 1) 可编辑,以便我添加新的 head 标签,甚至更糟糕的是 2) 需要在所有主题中维护我的代码?
当我开始使用不同的主题时,有些主题表明要编辑主题需要修改 github 仓库。这似乎非常繁重且不灵活。但是,在每个主题中维护我的脚本标签似乎非常容易出错,而且是一个更大的问题。
我错过了什么?有没有办法仅通过使用主题来解决这些问题?
在这种情况下,您会将其制作成一个主题组件,并将该组件添加到所有主题中。(我承认这会增加一些复杂性)。
如果您想专业地运行该网站,确保您的所有代码都在 Github 上确实是一个非常好的主意。
但是,最初当您只是尝试一些想法时,当然,您可以随意在“本地”进行修改。
当代码更改稳定下来后,您应该将其放入源代码管理中,但我认为越早越好。
一种将快速演进与 GitHub 结合的方法是将其与此结合:
[discourse] Discourse 主题 CLI 是一个 ruby gem,它允许您在开发 Discourse 主题和主题组件时使用您选择的编辑器。当您保存文件时,CLI 将更新远程主题或组件,并且对它的更改将实时显示!
安装
要试用它,请确保您已安装 Ruby 2.5 或更高版本。
[image]
如果您使用的是 Windows,您有 2 个选择:
选项 1: 适用于 Linux 的 Windows 子系统 。
Windows 10 可以访问完整的 Linux 环境,您可以使用 sudo apt-get install ruby 简单地安装 ruby,这将为您提供 Ruby 2.3。
选项 2: 旧版 Windows
旧版 Windows 无法访问 WSL,您可以使用 Ruby Installer 轻松安装 Ruby,选择推荐的版本和默认的安装设置。
[image]
Mac OS 版本 10.13.3 预装了 Ruby 2.3,无需特殊操作。如果您运行的是早期版本的 Mac OS,请考虑使用 rvm 、rbenv 或 homebrew 来安装较新的 ruby…
并在测试主题中部署到测试主题组件……但现在我们真的要玩得开心了……
这允许您即时部署,然后一旦满意,就可以将您的更改固定到 git 存储库。
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>
您好 @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\";
你真的应该浏览一下 Customization > Theme component 中链接的资源(整个生态几乎全是开源的——尽情探索吧!)。
你会注意到,导入(Imports)往往是必要且常见的(api.onPageChange 以及其他有用的 API 函数的使用也是如此)。