Guide du développeur aux extensions Markdown

Discourse utilise un moteur Markdown appelé Markdown-it.

Voici quelques notes de développement qui vous aideront à corriger des bugs dans le cœur du système ou à créer vos nouveaux plugins.

Les bases

Discourse ne contient que quelques aides au-dessus du moteur, la grande majorité de l’apprentissage nécessaire consiste donc à comprendre Markdown It.

Le dossier de la documentation contient la documentation actuelle.

Je recommande vivement de lire :

Lorsque je développe des extensions pour le moteur, j’ouvre généralement un second éditeur pour examiner les règles existantes. Le moteur est composé d’une longue liste de règles et chaque règle se trouve dans un fichier dédié, relativement facile à suivre.

Si je travaille sur une règle en ligne (inline), je me demande quelle règle en ligne existante fonctionne plus ou moins de la même manière et je base mon travail dessus.

Gardez à l’esprit que vous pouvez parfois vous contenter de modifier un rendu (renderer) pour obtenir la fonctionnalité souhaitée, ce qui est généralement beaucoup plus simple.

Comment structurer une extension ?

Lors de son initialisation, le moteur Markdown parcourt tous les modules.

Si un module correspond à /discourse-markdown\/|markdown-it\// (c’est-à-dire qu’il se trouve dans un dossier discourse-markdown ou markdown-it), il devient un candidat pour l’initialisation.

Si le module exporte une méthode appelée setup, celle-ci sera appelée par le moteur lors de l’initialisation.

Le protocole setup

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

export function setup(helper) {
  // ... votre code va ici
}

La méthode setup a accès à un objet helper qu’elle peut utiliser pour l’initialisation. Celui-ci contient les méthodes et variables suivantes :

  • bool markdownIt : cette propriété est définie à true lorsque le nouveau moteur est utilisé. Pour une compatibilité ascendante appropriée, vous souhaitez la vérifier.

  • registerOptions(cb(opts, siteSettings, state)) : la fonction fournie est appelée avant l’initialisation du moteur Markdown, vous pouvez l’utiliser pour déterminer s’il faut activer ou désactiver le moteur.

  • allowList([spec, ...]) : cette méthode est utilisée pour autoriser le HTML dans notre assainisseur (sanitizer).

  • registerPlugin(func(md)) : cette méthode est utilisée pour enregistrer un plugin Markdown It.

Mettre tout en place

function amazingMarkdownItInline(state, silent) {
   // l'extension en ligne standard markdown it va ici.
   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);
   });
}

Extensions spécifiques à Discourse

BBCode

Discourse contient 2 règles (rulers) que vous pouvez utiliser pour des balises BBCode personnalisées. Une règle en ligne (inline) et une règle de niveau bloc (block level).

Les règles bbcode en ligne sont celles qui se trouvent dans un paragraphe en ligne comme [b]gras[/b]

Les règles de niveau bloc s’appliquent à plusieurs lignes de texte comme :

[poll]
- option 1

- options 2
[/poll]

md.inline.bbcode.ruler contient une liste de règles en ligne appliquées dans l’ordre.

md.block.bbcode.ruler contient une liste de règles de niveau bloc

Il y a de nombreux exemples de règles en ligne sur : bbcode-inline.js

Les citations et les sondages sont de bons exemples de règles bbcode de bloc.

Règles BBCode en ligne

Les règles BBCode en ligne sont un objet contenant des informations sur la manière de gérer une balise.

Par exemple :

md.inline.bbcode.ruler.push("underline", {
  tag: "u",
  wrap: "span.bbcode-u",
});

Cela provoquera

test [u]test[/u]

À être converti en :

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

Les règles en ligne peuvent soit envelopper (wrap) soit remplacer (replace) du texte. Lors de l’enveloppement, vous pouvez également passer une fonction pour gagner en flexibilité.

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 {
      // supprime simplement la balise bbcode
      endToken.content = "";
      startToken.content = "";

      // cas limite, nous ne voulons pas que cela soit détecté comme un onebox si lié automatiquement
      // cela s'assure qu'il n'est pas supprimé
      startToken.type = "html_inline";
    }

    return false;
  },
});

La fonction d’enveloppement fournit l’accès à :

  • Le tagInfo, qui est un dictionnaire de clés/valeurs spécifiées via bbcode.

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

  • Le jeton (token) qui commence l’élément en ligne

  • Le jeton qui termine l’élément en ligne

  • Le contenu de l’élément bbcode en ligne

En utilisant ces informations, vous pouvez gérer tous types de besoins d’enveloppement.

Parfois, vous voudrez peut-être remplacer tout le bloc BBCode, pour cela vous pouvez utiliser 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;
  },
});

Dans ce cas, nous remplaçons un bloc [code]code block[code] entier par un seul jeton code_inline.

Règles BBCode de bloc

Les règles bbcode de bloc vous permettent de remplacer un bloc entier. Les API de bloc sont les mêmes pour les cas simples :

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

deviendra

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

L’enveloppe fonctionnelle a une API légèrement différente car il n’y a pas de jetons d’enveloppement.

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]

deviendra

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

Vous pouvez prendre un contrôle total sur le rendu des blocs avec les règles before et after, ce qui vous permet de faire des choses comme imbriquer deux fois une balise, 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]

deviendra

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

Gestion des remplacements de texte

Discourse est livré avec une règle de cœur spéciale supplémentaire pour appliquer des expressions régulières au texte.

md.core.textPostProcess.ruler

Pour l’utiliser :

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, // les drapeaux regex ne sont PAS pris en charge
  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

deviendra

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

Ce document est sous contrôle de version - suggérez des modifications sur github.

35 « J'aime »