O Discourse usa um motor Markdown chamado Markdown-it.
Aqui estão algumas notas de desenvolvimento que ajudarão você a corrigir bugs no núcleo ou a criar seus novos plugins.
O Básico
O Discourse contém apenas alguns auxiliares sobre o motor, então a grande maioria do aprendizado necessário é entender o Markdown It.
O diretório docs contém a documentação atual.
Eu recomendo fortemente a leitura:
-
O documento de arquitetura para entender em alto nível como o motor funciona.
-
Desenvolvimento para diretrizes básicas de desenvolvimento
-
Documentação da API para uma referência muito detalhada
-
E, finalmente, o código-fonte, que é muito bem documentado e claro.
Enquanto desenvolvo extensões para o motor, geralmente abro um segundo editor olhando as regras existentes. O motor consiste em uma longa lista de regras e cada regra está em um arquivo dedicado que é razoavelmente fácil de seguir.
Se estou trabalhando em uma regra inline, penso em qual regra inline existente funciona mais ou menos como ela e baseio meu trabalho nela.
Tenha em mente que, às vezes, você pode se safar apenas alterando um renderizador para obter a funcionalidade desejada, o que geralmente é muito mais fácil.
Como estruturar uma extensão?
Quando o motor Markdown é inicializado, ele procura por todos os módulos.
Se algum módulo for chamado /discourse-markdown\\/|markdown-it\\// (o que significa que ele reside em um diretório discourse-markdown ou markdown-it), ele será um candidato à inicialização.
Se o módulo exportar um método chamado setup, ele será chamado pelo motor durante a inicialização.
O protocolo de configuração (setup)
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... seu código vai aqui
}
Um método setup obtém acesso a um objeto auxiliar que pode ser usado para inicialização. Este contém os seguintes métodos e variáveis:
-
bool markdownIt: esta propriedade é definida comotruequando o novo motor está em uso. Para uma compatibilidade com versões anteriores adequada, você deve verificar isso. -
registerOptions(cb(opts,siteSettings,state)): a função fornecida é chamada antes que o motor Markdown seja inicializado, você pode usá-la para determinar se deve habilitar ou desabilitar o motor. -
allowList([spec, ...]): este método é usado para permitir explicitamente (allowlist) HTML com nosso sanitizador. -
registerPlugin(func(md)): este método é usado para registrar um plugin Markdown It.
Juntando tudo
function amazingMarkdownItInline(state, silent) {
// extensão inline markdown it padrão vai aqui.
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);
});
}
Extensões específicas do Discourse
BBCode
O Discourse contém 2 rulers (regras) que você pode usar para tags BBCode personalizadas. Uma regra de nível inline e outra de nível de bloco.
Regras bbcode inline são aquelas que residem em um parágrafo inline como [b]negrito[/b]
Regras de nível de bloco se aplicam a múltiplas linhas de texto como:
[poll]
- opção 1
- opção 2
[/poll]
md.inline.bbcode.ruler contém uma lista de regras inline que são aplicadas em ordem.
md.block.bbcode.ruler contém uma lista de regras de nível de bloco
Existem muitos exemplos para regras inline em: bbcode-inline.js
Citações e enquetes são bons exemplos de regras de bloco bbcode.
Regras BBCode Inline
Regras BBCode inline são um objeto contendo informações sobre como lidar com uma tag.
Por exemplo:
md.inline.bbcode.ruler.push("underline", {
tag: "u",
wrap: "span.bbcode-u",
});
Causará
teste [u]teste[/u]
Para ser convertido em:
teste <span>test<span class="bbcode-u">teste</span></span>
Regras inline podem envolver (wrap) ou substituir (replace) texto. Ao envolver, você também pode passar uma função para obter flexibilidade extra.
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 {
// apenas remove a tag bbcode
endToken.content = "";
startToken.content = "";
// caso extremo, não queremos que isso seja detectado como um onebox se for linkado automaticamente
// isso garante que não seja removido
startToken.type = "html_inline";
}
return false;
},
});
A função de envolvimento fornece acesso a:
-
O
tagInfo, que é um dicionário de chave/valor especificado via bbcode.[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
O token que inicia o inline
-
O token que finaliza o inline
-
O conteúdo do inline bbcode
Usando essas informações, você pode lidar com todos os tipos de necessidades de envolvimento.
Ocasionalmente, você pode querer substituir todo o bloco BBCode; para isso, você pode usar 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;
},
});
Neste caso, estamos substituindo um bloco inteiro [code]bloco de código[code] por um único token code_inline.
Regras BBCode de Bloco
Regras bbcode de bloco permitem que você substitua um bloco inteiro. As APIs de bloco são as mesmas para casos simples:
md.block.bbcode.ruler.push("happy", {
tag: "happy",
wrap: "div.happy",
});
[happy]
olá
[/happy]
se tornará
<div class="happy">olá</div>
O wrapper de função tem uma API ligeiramente diferente, pois não há tokens de envolvimento.
md.block.bbcode.ruler.push("money", {
tag: "money",
wrap: function (token, tagInfo) {
token.attrs = [["data-money", tagInfo.attrs["_default"]]];
return true;
},
});
[money=100]
**teste**
[/money]
Se tornará
<div data-money="100">
<b>teste</b>
</div>
Você pode obter controle total sobre a renderização de blocos com as regras before e after, isso permite que você faça coisas como aninhar uma tag duas vezes e assim por diante.
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]
teste
[/ddiv]
se tornará
<div>
<div>teste</div>
</div>
Lidando com substituições de texto
O Discourse envia uma regra principal especial extra para aplicar expressões regulares ao texto.
md.core.textPostProcess.ruler
Para usar:
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //flags de regex NÃO são suportadas
onMatch: function (buffer, matches, state) {
let token = new state.Token("text", "", 0);
token.content = "fast " + matches[0];
buffer.push(token);
},
});
Eu gosto de carros e ônibus
Se tornará
<p>Eu gosto de fast carros e fast ônibus</p>
Este documento é controlado por versão - sugira alterações no github.