O Discourse usa um motor de 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.
Os Fundamentos
O Discourse contém apenas alguns helpers sobre o motor, portanto, a grande maioria do aprendizado necessário é entender o Markdown It.
O diretório de documentação contém a documentação atual.
Recomendo fortemente a leitura de:
-
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, por fim, o código-fonte, que está muito bem documentado e é claro.
Enquanto desenvolvo extensões para o motor, geralmente abro um segundo editor olhando para 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 da mesma forma e baseio meu trabalho nela.
Lembre-se, às vezes você pode se sair bem apenas alterando um renderer para obter a funcionalidade desejada, o que geralmente é muito mais fácil.
Como estruturar uma extensão?
Quando o motor de markdown é inicializado, ele percorre todos os módulos.
Se qualquer módulo for chamado /discourse-markdown\/|markdown-it\// (ou seja, estiver em um diretório discourse-markdown ou markdown-it), ele será um candidato para inicialização.
Se o módulo exportar um método chamado setup, ele será chamado pelo motor durante a inicialização.
O protocolo de setup
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... seu código vai aqui
}
Um método setup tem acesso a um objeto helper que pode ser usado para inicialização. Ele contém os seguintes métodos e variáveis:
-
bool markdownIt: esta propriedade é definida comotruequando o novo motor está em uso. Para uma compatibilidade reversa adequada, você deseja verificá-la. -
registerOptions(cb(opts, siteSettings, state)): a função fornecida é chamada antes que o motor de markdown seja inicializado; você pode usá-la para determinar se deve habilitar ou desabilitar o motor. -
allowList([spec, ...]): este método é usado para permitir HTML com nosso sanitizadores. -
registerPlugin(func(md)): este método é usado para registrar um plugin do Markdown It.
Juntando tudo
function amazingMarkdownItInline(state, silent) {
// extensão inline padrão do markdown it 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
Gerando referências de hashtag em Ruby
Use HashtagAutocompleteService#hashtags_for para construir referências para o Markdown gerado:
HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")
Se uma categoria visível tiver o slug bug, o resultado será #bug::tag #support.
Passar nomes já filtrados para a visibilidade do espectador. A ordem e a caixa (maiúsculas/minúsculas) são
preservadas, as colisões são verificadas contra tipos de maior prioridade, e um nome que a
regra de hashtag não consegue corresponder volta para texto simples.
BBCode
O Discourse contém 2 rulers que você pode usar para tags BBCode personalizadas. Um ruler de nível inline e um de nível de bloco.
Regras bbcode inline são aquelas que ficam 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ções 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
Há muitos exemplos de regras inline em: bbcode-inline.js
Citações e pesquisas são bons exemplos de regras de bloco bbcode.
Regras de BBCode inline
Regras de 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",
});
Isso causará
test [u]test[/u]
A ser convertido em:
test <span class="bbcode-u">test</span>
Regras inline podem envolver ou substituir texto. Ao envolver, você também pode passar uma função para ganhar 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 remover a tag bbcode
endToken.content = "";
startToken.content = "";
// caso de borda, não queremos que isso seja detectado como um onebox se for auto linkado
// 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 chaves/valores especificados 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 bbcode inline
Usando essas informações, você pode atender a 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 [code]bloco de código[code] inteiro por um único token code_inline.
Regras de BBCode de bloco
Regras de 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]
hello
[/happy]
se tornará
<div class="happy">hello</div>
A função de envolvimento tem uma API ligeiramente diferente porque 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]
**test**
[/money]
Se tornará
<div data-money="100">
<b>test</b>
</div>
Você pode ganhar controle total sobre a renderização de blocos com as regras before e after, isso permite que você faça coisas como aninhar duplamente uma tag 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]
test
[/ddiv]
se tornará
<div>
<div>test</div>
</div>
Lidando com substituições de texto
O Discourse vem com uma regra especial extra do núcleo 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);
},
});
I like cars and buses
Se tornará
<p>I like fast cars and fast buses</p>
Este documento é controlado por versão - sugira alterações no github.