Discourse utiliza un motor de Markdown llamado Markdown-it.
Aquí hay algunas notas de desarrollo que te ayudarán a corregir errores en el núcleo o a crear tus nuevos complementos.
Lo Básico
Discourse solo contiene algunos ayudantes sobre el motor, por lo que la gran mayoría del aprendizaje que se necesita hacer es comprender Markdown It.
El directorio docs contiene la documentación actual.
Recomiendo encarecidamente leer:
-
El documento de arquitectura para comprender a alto nivel cómo funciona el motor.
-
Desarrollo para las pautas básicas de desarrollo
-
Documentación de la API para una referencia muy detallada
-
Y finalmente, el código fuente que está muy bien documentado y es claro.
Mientras desarrollo extensiones para el motor, suelo abrir un segundo editor mirando las reglas existentes. El motor consta de una larga lista de reglas y cada regla está en un archivo dedicado que es razonablemente fácil de seguir.
Si estoy trabajando en una regla en línea, pensaré en qué regla en línea existente funciona más o menos como la que quiero y basaré mi trabajo en ella.
Ten en cuenta que a veces puedes arreglártelas solo cambiando un renderizador para obtener la funcionalidad deseada, lo cual suele ser mucho más fácil.
¿Cómo estructurar una extensión?
Cuando el motor de markdown se inicializa, busca a través de todos los módulos.
Si algún módulo se llama /discourse-markdown\\/|markdown-it\\// (lo que significa que reside en un directorio discourse-markdown o markdown-it) será un candidato para la inicialización.
Si el módulo exporta un método llamado setup, será llamado por el motor durante la inicialización.
El protocolo de configuración (setup)
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... tu código va aquí
}
Un método setup obtiene acceso a un objeto helper que puede usar para la inicialización. Este contiene los siguientes métodos y variables:
-
bool markdownIt: esta propiedad se establece entruecuando se está utilizando el nuevo motor. Para una correcta compatibilidad con versiones anteriores, querrás comprobarlo. -
registerOptions(cb(opts,siteSettings,state)): la función proporcionada se llama antes de que se inicialice el motor de markdown, puedes usarla para determinar si habilitar o deshabilitar el motor. -
allowList([spec, ...]): este método se utiliza para incluir en la lista blanca el HTML con nuestro sanitizador. -
registerPlugin(func(md)): este método se utiliza para registrar un plugin de Markdown It.
Juntándolo todo
function amazingMarkdownItInline(state, silent) {
// la extensión inline estándar de markdown it va aquí.
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);
});
}
Extensiones específicas de Discourse
BBCode
Discourse contiene 2 rulers (regras) que puedes usar para etiquetas BBCode personalizadas. Una regla de nivel inline y una de nivel block.
Las reglas BBCode inline son aquellas que residen en un párrafo en línea como [b]negrita[/b]
Las reglas de nivel block se aplican a múltiples líneas de texto como:
[poll]
- opción 1
- opción 2
[/poll]
md.inline.bbcode.ruler contiene una lista de reglas inline que se aplican en orden.
md.block.bbcode.ruler contiene una lista de reglas de nivel block
Hay muchos ejemplos de reglas inline en: bbcode-inline.js
Citas y encuestas son buenos ejemplos de reglas BBCode block.
Reglas BBCode Inline
Las reglas BBCode inline son un objeto que contiene información sobre cómo manejar una etiqueta.
Por ejemplo:
md.inline.bbcode.ruler.push("underline", {
tag: "u",
wrap: "span.bbcode-u",
});
Hará que
prueba [u]prueba[/u]
Se convierta a:
prueba <span>test</span>
Las reglas inline pueden envolver o reemplazar texto. Al envolver, también puedes pasar una función para obtener mayor flexibilidad.
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 {
// simplemente elimina la etiqueta bbcode
endToken.content = "";
startToken.content = "";
// caso extremo, no queremos que esto se detecte como un onebox si está auto enlazado
// esto asegura que no sea eliminado
startToken.type = "html_inline";
}
return false;
},
});
La función de envoltorio proporciona acceso a:
-
tagInfo, que es un diccionario de clave/valor especificado a través de bbcode.[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
El token que inicia lo inline
-
El token que finaliza lo inline
-
El contenido de lo inline bbcode
Usando esta información puedes manejar todo tipo de necesidades de envoltura.
Ocasionalmente, es posible que desees reemplazar todo el bloque BBCode; para eso puedes 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;
},
});
En este caso, estamos reemplazando un bloque completo [code]bloque de código[code] con un único token code_inline.
Reglas BBCode Block
Las reglas BBCode block te permiten reemplazar un bloque completo. Las APIs de block son las mismas para casos simples:
md.block.bbcode.ruler.push("happy", {
tag: "happy",
wrap: "div.happy",
});
[happy]
hola
[/happy]
se convertirá en
<div class="happy">hola</div>
La envoltura de función tiene una API ligeramente diferente ya que no hay tokens de envoltura.
md.block.bbcode.ruler.push("money", {
tag: "money",
wrap: function (token, tagInfo) {
token.attrs = [["data-money", tagInfo.attrs["_default"]]];
return true;
},
});
[money=100]
**prueba**
[/money]
Se convertirá en
<div data-money="100">
<b>prueba</b>
</div>
Puedes obtener control total sobre la renderización block con las reglas before y after, esto te permite hacer cosas como anidar una etiqueta doble y demás.
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]
prueba
[/ddiv]
se convertirá en
<div>
<div>prueba</div>
</div>
Manejo de reemplazos de texto
Discourse incluye una regla central especial adicional para aplicar expresiones regulares al texto.
md.core.textPostProcess.ruler
Para usar:
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //NO se soportan flags de regex
onMatch: function (buffer, matches, state) {
let token = new state.Token("text", "", 0);
token.content = "fast " + matches[0];
buffer.push(token);
},
});
Me gustan los coches y los autobuses
Se convertirá en
<p>Me gustan los coches rápidos y los autobuses rápidos</p>
Este documento está controlado por versiones: sugiere cambios en github.