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 algunas funciones de ayuda sobre el motor, por lo que la gran mayoría del aprendizaje que se necesita 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 ella y basaré mi trabajo en ella.

Ten en cuenta que a veces puedes arreglártelas simplemente 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 verificarla.

  • 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 permitir explícitamente 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 en línea de markdown it estándar 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 reglas que puedes usar para etiquetas BBCode personalizadas. Una regla de nivel en línea y una de nivel de bloque.

Las reglas bbcode en línea son aquellas que residen en un párrafo en línea como [b]negrita[/b]

Las reglas de nivel de bloque 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 en línea que se aplican en orden.

md.block.bbcode.ruler contiene una lista de reglas de nivel de bloque

Hay muchos ejemplos de reglas en línea en: bbcode-inline.js

Citas y encuestas son buenos ejemplos de reglas de bloque bbcode.

Reglas BBCode en línea

Las reglas BBCode en línea 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 en:

prueba <span>test</span>

Las reglas en línea pueden envolver o reemplazar texto. Al envolver, también puedes pasar una función para obtener flexibilidad adicional.

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 especial, no queremos que esto se detecte como un onebox si está enlazado automáticamente
      // esto asegura que no se elimine
      startToken.type = "html_inline";
    }

    return false;
  },
});

La función de envoltura proporciona acceso a:

  • El 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 el elemento en línea

  • El token que finaliza el elemento en línea

  • El contenido del elemento en línea 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 de bloque

Las reglas bbcode de bloque te permiten reemplazar un bloque completo. Las API de bloque 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 de bloques 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 admiten las banderas 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