Guía para desarrolladores sobre extensiones de Markdown

Discourse utiliza un motor de Markdown llamado Markdown-it.

Aquí tienes algunas notas de desarrollo que te ayudarán a corregir errores en el núcleo o a crear tus nuevos plugins.

Lo básico

Discourse solo contiene unos pocos helpers sobre el motor, por lo que la mayor parte del aprendizaje necesario consiste en comprender Markdown It.

El directorio de documentación contiene la documentación actual.

Recomiendo encarecidamente leer:

Mientras desarrollo extensiones para el motor, generalmente abro un segundo editor para ver las reglas existentes. El motor consiste en 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 (inline), pensaré en qué regla en línea existente funciona de manera similar y me basaré en ella.

Ten en cuenta que a veces puedes salirte con la tuya 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 un módulo se llama /discourse-markdown\/|markdown-it\// (es decir, se encuentra 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 setup

/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js

export function setup(helper) {
  // ... tu código va aquí
}

Un método setup tiene 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 utiliza el nuevo motor. Para una compatibilidad hacia atrás adecuada, deseas verificarlo.

  • registerOptions(cb(opts, siteSettings, state)) : la función proporcionada se llama antes de que el motor de Markdown se inicialice, puedes usarla para determinar si habilitar o deshabilitar el motor.

  • allowList([spec, ...]): este método se utiliza para permitir HTML con nuestro saneador (sanitizer).

  • registerPlugin(func(md)): este método se utiliza para registrar un plugin de Markdown It.

Poniéndolo todo junto

function amazingMarkdownItInline(state, silent) {
   // la extensión en línea 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

Generar referencias de hashtags en Ruby

Usa HashtagAutocompleteService#hashtags_for para construir referencias para el Markdown generado:

HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")

Si una categoría visible tiene el slug bug, el resultado es #bug::tag #support.
Pasa nombres ya filtrados para la visibilidad del espectador. El orden y la mayúscula/minúscula se
conservan, las colisiones se comprueban contra tipos de mayor prioridad, y un nombre que
la regla de hashtag no puede coincidir se convierte en texto plano.

BBCode

Discourse contiene 2 reglas (rulers) 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 se encuentran 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

- opciones 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 bbcode de bloque.

Reglas de BBCode en línea

Las reglas de 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",
});

Causará que

test [u]test[/u]

Se convierta en:

test <span class="bbcode-u">test</span>

Las reglas en línea pueden envolver o reemplazar texto. Al envolver, también puedes pasar una función para ganar 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 eliminar la etiqueta bbcode
      endToken.content = "";
      startToken.content = "";

      // caso límite, no queremos que esto se detecte como un onebox si se enlaza 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 claves/valores especificados a través de bbcode.

    [test=testing]{_default: "testing"}
    [test a=1]{a: "1"}

  • El token que inicia la línea en línea

  • El token que finaliza la línea en línea

  • El contenido de la línea 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 [code]bloque de código[code] completo con un único token code_inline.

Reglas de BBCode de bloque

Las reglas bbcode de bloque te permiten reemplazar un bloque completo. Las APIs de bloque son las mismas para casos simples:

md.block.bbcode.ruler.push("happy", {
  tag: "happy",
  wrap: "div.happy",
});
[happy]
hello
[/happy]

se convertirá en

<div class="happy">hello</div>

El envoltorio de función tiene una API ligeramente diferente porque 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]
**test**
[/money]

Se convertirá en

<div data-money="100">
  <b>test</b>
</div>

Puedes obtener el control total sobre el renderizado de bloques con la regla before y after, esto te permite hacer cosas como anidar una etiqueta doble, etc.

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 convertirá en

<div>
  <div>test</div>
</div>

Manejo de reemplazos de texto

Discourse viene con una regla especial adicional del núcleo para aplicar expresiones regulares al texto.

md.core.textPostProcess.ruler

Para usar:

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, // las banderas regex NO son compatibles
  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 convertirá en

<p>I like fast cars and fast buses</p>

Este documento está bajo control de versiones - sugiere cambios en github.

35 Me gusta

¿Se puede/Se podrá usar esto en un componente de tema? ¿O es exclusivo de complementos?