markdown.it,即 Discourse 使用的 CommonMark 引擎,拥有丰富的插件生态系统
包括标题锚点、定义列表、智能箭头等等,不胜枚举。
首先是一个警告
CommonMark 的设计初衷是“通用”的。你偏离规范越远,你的 Markdown 就越不“通用”。这可能导致难以迁移到其他解决方案,如果不谨慎处理,还可能在解析时引起内部不一致。在重新打包任何东西之前,请务必先问自己:“我真的需要重新打包这个吗?”
我刚刚完成了对 Discourse Footnote 的重新打包,并有一些关于如何正确执行此操作的经验分享。
懒人步骤
如果你很懒,只想快速开始,最简单的方法是直接 fork footnotes 插件,然后替换文件和变量名。我在编写时非常注意遵循最佳实践,因此它应该是一个可靠的示例。
初步步骤:最小化重新打包
据我所知,大多数 markdown.it 插件都是作为普通的 js 文件发布的。在很多情况下,插件只是作为单个 js 文件发布,例如:markdown-it-mark.js。
理想情况下,你希望保持原始文件不变,这意味着你可以简单地将更新后的文件副本放入你的插件中,而无需修改现有的插件文件。
你会遇到的第一个问题是,你必须教会你的插件在服务器上加载这段 JavaScript,因为 Markdown 引擎也在服务器端运行。要做到这一点,你可以简单地按原样将文件复制到 assets/javascripts/vendor/original-plugin.js,然后在你的 plugin.rb 文件中添加:
# 这教会我们的 markdown 引擎加载你的普通 js 文件
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text
一旦包含了插件的实际主体代码,你需要教会我们的管道如何加载并初始化它:
创建一个名为 assets/javascripts/lib/discourse-markdown/your-extension.js 的文件
由于该文件以 .js 结尾且位于 discourse-markdown 目录中,它将被自动加载。
一个简单的示例可以是:
export function setup(helper) {
// 这允许你仅在站点设置启用时加载扩展
helper.registerOptions((opts, siteSettings) => {
opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
});
// 允许你需要支持的任何属性,
// 否则我们的清理器会将其剥离
helper.allowList(["div.amazingness"]);
// 你也可以做类似这样的复杂操作
helper.allowList({
custom(tag, name, value) {
if ((tag === "a" || tag === "li") && name === "id") {
return !!value.match(/^fn(ref)?\d+$/);
}
},
});
// 最后,这是你在我们的管道中注册扩展所使用的神秘魔法
// whateverGlobal 是插件暴露的全局变量名称
// 它接收一个 (md) 变量,用于修改管道
helper.registerPlugin(window.whateverGlobal);
}
始终进行测试
Discourse 的 bin/rake autospec 支持插件 ![]()
这意味着当你添加文件 spec/pretty_text_spec.rb 后,每次保存它时,插件测试文件都会运行。
我广泛使用它,因为它极大地提高了工作效率。
假设你添加了一个插件,将帖子中的每个数字更改为 8 号球,你可以将其命名为 discourse-magic-8-ball。
这是我组织测试的方法:
require "rails_helper"
describe PrettyText do
it "can be disabled" do
SiteSetting.enable_magic_8_ball = false
markdown = <<~MD
1 thing
MD
html = <<~HTML
<p>1 thing</p>
HTML
cooked = PrettyText.cook markdown.strip
expect(cooked).to eq(html.strip)
end
it "supports magic 8 ball" do
markdown = <<~MD
1 thing
MD
html = <<~HTML
<p>8 circle thing</p>
HTML
cooked = PrettyText.cook markdown.strip
expect(cooked).to eq(html.strip)
end
end
你可能需要“装饰帖子”
在某些情况下,当插件为帖子添加额外的“动态”功能时效果最好。例如 poll 插件或 footnotes 插件,后者添加了一个“…”以动态显示工具提示。
如果你需要“装饰”帖子,请添加 assets/javascripts/api-initializers/your-initializer.js
import { apiInitializer } from "discourse/lib/api";
export default apiInitializer((api) => {
const siteSettings = api.container.lookup("service:site-settings");
if (!siteSettings.enable_magic_8_ball) {
return;
}
api.decorateCookedElement((elem) => {
// 你的神奇魔法代码写在这里
});
});
你可能需要对帖子进行“后处理”
在某些情况下,你可能需要对帖子进行“后处理”。Markdown 渲染引擎在设计上并不知晓某些信息,例如 post_id。在某些情况下,你可能需要在服务器端访问帖子和“渲染后的” HTML,这可以让你的执行诸如触发后台任务、同步自定义字段或“修正”自动生成的 HTML 等操作。
对于脚注,我需要为每个脚注提供一个唯一的 id,这意味着我需要访问 post_id,因此我不得不修改后处理器中的 HTML(后处理器在 sidekiq 中运行)
要挂钩到此过程,请在你的 plugin.rb 文件中添加以下内容:
DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
doc.css("a.always-bing").each do |a|
# 这应该始终指向 bing
a["href"] = "https://bing.com"
end
end
你可能需要一些自定义 CSS
如果你想发布自定义 CSS,请确保在 plugin.rb 中注册该文件
将你的 css 添加到 assets/stylesheets/magic.scss,然后运行
register_asset "stylesheets/magic.scss"
请记住,我们会“自动重载”更改,因此在开发过程中,你可以修改插件 CSS 并即时查看更改。
祝你的重新打包冒险顺利 ![]()
此文档受版本控制 - 可在 github 上提交修改建议。