markdown.it Discourse 使用的 CommonMark 引擎有一个广泛的插件
标题锚点、定义列表、智能箭头等等不胜枚举。
首先是一个警告
CommonMark 旨在保持……通用。你偏离规范越远,你的 Markdown 就越不通用。这可能会使移植到其他解决方案变得更加困难,并且如果不小心,可能会导致解析中的内部不一致。在重新打包任何东西之前,请务必回答“我真的想重新打包这个吗?”这个问题。
我刚刚完成了对 Discourse Footnote 的重新打包,并分享一些关于如何正确做这件事的经验教训。
懒人步骤
如果你很懒,只想开始,最简单的方法是分叉脚注插件,然后交换文件和变量名。我非常仔细地确保它遵循了最佳实践,所以你应该有一个可靠的例子。
开场白,最小化重新打包
据我所知,大多数 markdown.it 插件都作为纯 JavaScript 文件分发。在许多情况下,插件只是作为一个单独的 JS 文件分发,例如:markdown-it-mark.js。
理想情况下,你想保持原始文件不变,这意味着你可以简单地将更新后的文件版本复制到你的插件中,而无需修改现有插件。
你会遇到的第一个问题是,你必须教你的插件在服务器上加载这个 JavaScript,因为 Markdown 引擎也在服务器上运行。要做到这一点,你可以简单地将文件原封不动地复制到 assets/javascripts/vendor/original-plugin.js,然后在你的 plugin.rb 文件中添加:
# 这会教我们的 markdown 引擎加载你的 vanilla 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.whiteList(["div.amazingness"]);
// 你也可以做一些花哨的事情,比如这样
helper.whiteList({
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,因此我被迫在后处理器(在 sidekiq 中运行)中更改 HTML。
要挂入,你需要在 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 上建议更改。