Guía del desarrollador para extensiones de Markdown

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:

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 en true cuando 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.

35 Me gusta