Discourse 使用了一个名为 Markdown-it 的 Markdown 引擎。
以下是一些开发笔记,可以帮助你修复核心代码中的 bug 或创建新的插件。
基础
Discourse 仅在引擎之上提供了一些辅助功能,因此需要学习的大部分内容都是理解 Markdown It。
文档目录 包含当前的文档。
我强烈建议阅读以下内容:
在为引擎开发扩展时,我通常会打开第二个编辑器来查看现有的规则。引擎由一长串规则组成,每条规则都位于一个专门的文件中,且相对容易理解。
如果我在处理内联(inline)规则,我会思考哪些现有的内联规则与之类似,并基于它们来开展工作。
请记住,有时你只需更改渲染器(renderer)即可获得所需的功能,这通常要简单得多。
如何构建扩展?
当 Markdown 引擎初始化时,它会遍历所有模块。
如果任何模块的路径匹配 /discourse-markdown\/|markdown-it\//(即它位于 discourse-markdown 或 markdown-it 目录中),它将成为初始化的候选项。
如果模块导出了一个名为 setup 的方法,引擎将在初始化期间调用它。
setup 协议
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... 你的代码在这里
}
setup 方法可以访问一个 helper 对象,用于初始化。该对象包含以下方法和变量:
-
bool markdownIt: 当使用新引擎时,该属性被设置为true。为了正确的向后兼容性,你需要检查它。 -
registerOptions(cb(opts, siteSettings, state)): 提供的函数在 Markdown 引擎初始化之前被调用,你可以使用它来确定是否启用或禁用引擎。 -
allowList([spec, ...]): 此方法用于通过我们的净化器(sanitizer)允许特定的 HTML。 -
registerPlugin(func(md)): 此方法用于注册 Markdown It 插件。
将所有内容整合在一起
function amazingMarkdownItInline(state, silent) {
// 标准的 markdown it 内联扩展代码在这里。
return false;
}
export function setup(helper) {
if(!helper.markdownIt) { return; }
helper.registerOptions((opts,siteSettings)=>{
opts.features.['my_extension'] = !!siteSettings.my_extension_enabled;
});
helper.allowList(['span.amazing', 'div.amazing']);
helper.registerPlugin(md=>{
md.inline.push('amazing', amazingMarkdownItInline);
});
}
Discourse 特定扩展
在 Ruby 中生成标签引用
使用 HashtagAutocompleteService#hashtags_for 来为生成的 Markdown 构建引用:
HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")
如果一个可见类别的 slug 是 bug,结果将是 #bug::tag #support。
传入已经针对查看者可见性过滤过的名称。顺序和大小写保持不变,冲突会与更高优先级的类型进行检查,如果标签规则无法匹配某个名称,则回退为纯文本。
BBCode
Discourse 包含两个你可以用于自定义 BBCode 标签的 ruler(规则器)。一个是内联级别,另一个是块级(block level)。
内联 bbcode 规则是存在于内联段落中的规则,例如 [b]bold[/b]
块级规则适用于多行文本,例如:
[poll]
- option 1
- options 2
[/poll]
md.inline.bbcode.ruler 保存了一个按顺序应用的内联规则列表。
md.block.bbcode.ruler 保存了一个块级规则列表
内联规则的许多示例位于:bbcode-inline.js
引用 和投票是 bbcode 块规则的良好示例。
内联 BBCode 规则
内联 BBCode 规则是一个包含有关如何处理标签的信息的对象。
例如:
md.inline.bbcode.ruler.push("underline", {
tag: "u",
wrap: "span.bbcode-u",
});
这将导致
test [u]test[/u]
被转换为:
test <span class="bbcode-u">test</span>
内联规则可以包裹(wrap)或替换(replace)文本。在包裹时,你还可以传入一个函数以获得额外的灵活性。
md.inline.bbcode.ruler.push("url", {
tag: "url",
wrap: function (startToken, endToken, tagInfo, content) {
const url = (tagInfo.attrs["_default"] || content).trim();
if (simpleUrlRegex.test(url)) {
startToken.type = "link_open";
startToken.tag = "a";
startToken.attrs = [
["href", url],
["data-bbcode", "true"],
];
startToken.content = "";
startToken.nesting = 1;
endToken.type = "link_close";
endToken.tag = "a";
endToken.content = "";
endToken.nesting = -1;
} else {
// 仅移除 bbcode 标签
endToken.content = "";
startToken.content = "";
// 边缘情况,我们不希望这在自动链接时被检测为 onebox
// 这确保它不会被移除
startToken.type = "html_inline";
}
return false;
},
});
包裹函数提供了对以下内容的访问:
-
tagInfo,它是通过 bbcode 指定的键/值字典。
[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
开始内联的 token
-
结束内联的 token
-
bbcode 内联的内容
利用这些信息,你可以处理各种包裹需求。
偶尔你可能想替换整个 BBCode 块,为此你可以使用 replace
md.inline.bbcode.ruler.push("code", {
tag: "code",
replace: function (state, tagInfo, content) {
let token;
token = state.push("code_inline", "code", 0);
token.content = content;
return true;
},
});
在这种情况下,我们将整个 [code]code block[code] 替换为单个 code_inline token。
块级 BBCode 规则
块级 bbcode 规则允许你替换整个块。对于简单情况,块级 API 是相同的:
md.block.bbcode.ruler.push("happy", {
tag: "happy",
wrap: "div.happy",
});
[happy]
hello
[/happy]
将变为
<div class="happy">hello</div>
函数包装器具有稍微不同的 API,因为没有包裹 token。
md.block.bbcode.ruler.push("money", {
tag: "money",
wrap: function (token, tagInfo) {
token.attrs = [["data-money", tagInfo.attrs["_default"]]];
return true;
},
});
[money=100]
**test**
[/money]
将变为
<div data-money="100">
<b>test</b>
</div>
你可以使用 before 和 after 规则获得对块渲染的完全控制,这允许你执行诸如双重嵌套标签等操作。
md.block.bbcode.ruler.push("ddiv", {
tag: "ddiv",
before: function (state, tagInfo) {
state.push("div_open", "div", 1);
state.push("div_open", "div", 1);
},
after: function (state) {
state.push("div_close", "div", -1);
state.push("div_close", "div", -1);
},
});
[ddiv]
test
[/ddiv]
将变为
<div>
<div>test</div>
</div>
处理文本替换
Discourse 附带了一个额外的特殊核心规则,用于对文本应用正则表达式。
md.core.textPostProcess.ruler
用法:
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //不支持正则表达式标志
onMatch: function (buffer, matches, state) {
let token = new state.Token("text", "", 0);
token.content = "fast " + matches[0];
buffer.push(token);
},
});
I like cars and buses
将变为
<p>I like fast cars and fast buses</p>
本文档受版本控制 - 请在 github 上建议更改。