Discourse 使用一个名为 Markdown-it 的 Markdown 引擎。
以下是一些开发笔记,它们将帮助您修复核心错误或创建新的插件。
基础知识
Discourse 在引擎之上只包含一些辅助函数,因此需要学习的大部分内容是理解 Markdown It。
docs 目录 包含当前的文档。
我强烈建议阅读:
当我对引擎进行扩展开发时,我通常会打开第二个编辑器,查看现有的规则。引擎由一长串规则组成,每条规则都在一个专用的文件中,相当容易理解。
如果我正在处理一个行内规则,我会考虑哪个现有的行内规则与它或多或少相似,并以此为基础进行工作。
请记住,有时您可以通过更改渲染器来实现所需的功能,这通常要容易得多。
如何构建一个扩展?
当 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, ...]):此方法用于将 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 特定的扩展
BBCode
Discourse 包含 2 个您可以用于自定义 BBCode 标签的规则器。一个行内和一个块级规则器。
行内 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>test</span>
行内规则可以包裹或替换文本。当包裹时,您也可以传入一个函数以获得额外的灵活性。
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 行内内容的 content
利用这些信息,您可以处理所有类型的包裹需求。
偶尔您可能想替换整个 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 上建议更改。