Reconditionnement d'une extension markdown-it en plugin Discourse

markdown.it le moteur CommonMark utilisé par Discourse dispose d’un large éventail de plugins

Ancres d’en-tête, listes de définition, flèches intelligentes et la liste continue encore et encore.

D’abord un avertissement

:warning: CommonMark est censé être… Commun. Plus vous vous éloignez de la spécification, moins votre Markdown devient Commun. Cela peut rendre la portabilité vers d’autres solutions plus difficile et, si vous n’y prenez garde, provoquer des incohérences internes dans l’analyse syntaxique. Avant de reconditionner quoi que ce soit, assurez-vous de répondre à la question « ai-je vraiment besoin de reconditionner ceci ? »

Je viens de terminer le reconditionnement de Discourse Footnote et j’ai quelques leçons à partager sur la façon de le faire correctement.

Étapes pour les paresseux

Si vous êtes paresseux et que vous voulez simplement commencer, le moyen le plus simple est de forker les notes de bas de page et d’échanger les fichiers et les noms de variables. J’ai fait très attention à ce que cela suive les meilleures pratiques, vous devriez donc avoir un exemple solide.

Premiers pas, un reconditionnement minimal

D’après ce que j’ai pu constater, la majorité des plugins markdown.it sont distribués sous forme de fichiers js standards. Dans de nombreux cas, les plugins sont simplement distribués sous la forme d’un seul fichier js, comme ceci : markdown-it-mark.js.

Idéalement, vous souhaitez laisser l’original intact, ce qui signifie que vous pouvez simplement copier une version mise à jour du fichier dans votre plugin sans avoir besoin de modifier le plugin existant.

Le premier problème que vous rencontrerez est que vous devez apprendre à votre plugin à charger ce JavaScript sur le serveur, car le moteur Markdown s’exécute également sur le serveur. Pour ce faire, vous pouvez simplement copier le fichier tel quel dans assets/javascripts/vendor/original-plugin.js, puis dans votre fichier plugin.rb, vous ajouteriez :

# ceci apprend à notre moteur markdown à charger votre fichier js standard
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text

Une fois que vous avez inclus le corps réel du plugin, vous devez apprendre à notre pipeline comment le charger et l’initialiser :

Créez un fichier nommé assets/javascripts/lib/discourse-markdown/your-extension.js

Ce fichier sera chargé automatiquement car il se termine par .js ET se trouve dans le répertoire discourse-markdown.

Un exemple simple peut être :

export function setup(helper) {
  // ceci vous permet de charger votre extension uniquement si un paramètre du site est activé
  helper.registerOptions((opts, siteSettings) => {
    opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
  });

  // mettre sur liste blanche tous les attributs que vous devez prendre en charge,
  // sinon notre assainisseur les supprimera
  helper.whiteList(["div.amazingness"]);

  // vous pouvez également faire des choses sophistiquées comme ceci
  helper.whiteList({
    custom(tag, name, value) {
      if ((tag === "a" || tag === "li") && name === "id") {
        return !!value.match(/^fn(ref)?\d+$/);
      }
    },
  });

  // enfin, c'est la magie que vous utiliseriez pour enregistrer l'extension dans
  // notre pipeline. whateverGlobal est le nom du global que le plugin expose
  // il prend une seule variable (md) qui est ensuite utilisée pour modifier le pipeline
  helper.registerPlugin(window.whateverGlobal);
}

Toujours tester

bin/rake autospec de Discourse est conscient des plugins :innocent:

Cela signifie que lorsque vous ajoutez le fichier spec/pretty_text_spec.rb, chaque fois que vous l’enregistrez, le fichier de test du plugin s’exécutera.

Je l’utilise intensivement car cela rend le travail beaucoup plus rapide.

Disons que vous avez ajouté un plugin qui change chaque nombre dans un message en 8 cercles, vous pouvez l’appeler discourse-magic-8-ball.

Voici comment je structurerais les tests :

require "rails_helper"

describe PrettyText do
  it "can be disabled" do
    SiteSetting.enable_magic_8_ball = false

    markdown = <<~MD
      1 thing
    MD

    html = <<~HTML
      <p>1 thing</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end

  it "supports magic 8 ball" do
    markdown = <<~MD
      1 thing
    MD

    html = <<~HTML
      <p>8 circle thing</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end
end

Vous pourriez avoir besoin de « décorer les messages »

Dans certains cas, les plugins fonctionnent mieux lorsqu’ils ajoutent des fonctionnalités « dynamiques » supplémentaires à vos messages. Des exemples en sont le plugin poll ou le plugin footnotes qui ajoute un « … » affichant dynamiquement une info-bulle.

si vous avez besoin de « décorer » les messages, ajoutez assets/javascripts/api-initializers/your-initializer.js

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  const siteSettings = api.container.lookup("service:site-settings");
  if (!siteSettings.enable_magic_8_ball) {
    return;
  }

  api.decorateCookedElement((elem) => {
    // votre magie incroyable va ici
  });
});

Vous pourriez avoir besoin de « post-traiter » les messages

Dans certains cas, vous pourriez avoir besoin de « post-traiter » les messages, le moteur de rendu markdown, par conception, n’est pas au courant de certaines informations telles que, par exemple, post_id. Dans certains cas, vous souhaiterez un accès côté serveur au message et au HTML « cuit », cela peut vous permettre de faire des choses comme déclencher des tâches d’arrière-plan, synchroniser des champs personnalisés ou « corriger » le HTML généré automatiquement.

Pour les notes de bas de page, j’avais besoin d’un id distinct pour chaque note de bas de page, ce qui signifiait que j’avais besoin d’accéder à post_id, j’ai donc été obligé d’apporter des modifications au HTML dans le post-processeur (qui s’exécute dans sidekiq)

Pour vous connecter, vous ajouteriez ce qui suit à votre fichier plugin.rb :

DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
  doc.css("a.always-bing").each do |a|
    # ceci doit toujours aller vers bing
    a["href"] = "https://bing.com"
  end
end

Vous pourriez avoir besoin de CSS personnalisé

Si vous souhaitez fournir du CSS personnalisé, assurez-vous d’enregistrer le fichier dans plugin.rb

Ajoutez votre css à assets/stylesheets/magic.scss puis exécutez

register_asset "stylesheets/magic.scss"

N’oubliez pas que nous « rechargeons automatiquement » les modifications, vous pouvez donc modifier le CSS de votre plugin et voir les changements à la volée en développement.

Bonne chance dans vos aventures de reconditionnement :four_leaf_clover:


Ce document est contrôlé par version - suggérez des modifications sur github.

29 « J'aime »

Je vois des outils superset qui étendent Markdown, comme Quarkdown qui semble très puissant.

Existe-t-il un moyen de remplacer l’extension markdown-it par des outils comme Quarkdown ?