Eine markdown-it-Erweiterung als Discourse-Plugin neu verpacken

markdown.it ist die von Discourse verwendete CommonMark-Engine und verfügt über eine breite Palette von Plugins

Header-Anker, Definitionslisten, Smart-Pfeile und die Liste geht weiter und weiter.

Zuerst eine Warnung

:warning: CommonMark soll… Common sein. Je weiter Sie sich von der Spezifikation entfernen, desto weniger Common wird Ihr Markdown. Dies kann den Port zu anderen Lösungen erschweren und, wenn Sie nicht vorsichtig sind, zu internen Inkonsistenzen beim Parsen führen. Bevor Sie etwas neu verpacken, stellen Sie sicher, dass Sie die Frage beantworten: „Möchte ich das wirklich neu verpacken?“

Ich habe gerade Discourse Footnote neu verpackt und habe einige Lektionen darüber zu teilen, wie man dies richtig macht.

Schritte für Faule

Wenn Sie faul sind und einfach nur loslegen möchten, ist der einfachste Weg, Footnotes zu forken und Dateien und Variablennamen auszutauschen. Ich war sehr darauf bedacht, sicherzustellen, dass es Best Practices folgt, sodass Sie ein solides Beispiel haben sollten.

Eröffnungszüge, eine minimale Neuverpackung

Soweit ich das beurteilen kann, werden die meisten markdown.it-Plugins als Vanilla-JS-Dateien ausgeliefert. In vielen Fällen werden Plugins einfach als einzelne JS-Datei ausgeliefert, wie diese: markdown-it-mark.js.

Idealerweise möchten Sie das Original intakt lassen. Das bedeutet, Sie können einfach eine aktualisierte Version der Datei in Ihr Plugin kopieren, ohne sich mit dem vorhandenen Plugin herumschlagen zu müssen.

Das erste Problem, auf das Sie stoßen werden, ist, dass Sie Ihrem Plugin beibringen müssen, dieses JavaScript auf dem Server zu laden, da die Markdown-Engine auch auf dem Server läuft. Dazu können Sie die Datei einfach unverändert in assets/javascripts/vendor/original-plugin.js kopieren. Dann würden Sie in Ihrer plugin.rb-Datei Folgendes hinzufügen:

# dies lehrt unsere Markdown-Engine, Ihre Vanilla-JS-Datei zu laden
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text

Sobald Sie den eigentlichen Inhalt des Plugins eingebunden haben, müssen Sie unserer Pipeline beibringen, wie sie es lädt und initialisiert:

Erstellen Sie eine Datei namens assets/javascripts/lib/discourse-markdown/your-extension.js

Diese Datei wird automatisch geladen, da sie auf .js endet UND sich im Verzeichnis discourse-markdown befindet.

Ein einfaches Beispiel könnte sein:

export function setup(helper) {
  // dies ermöglicht es Ihnen, Ihre Erweiterung nur zu laden, wenn eine Site-Einstellung aktiviert ist
  helper.registerOptions((opts, siteSettings) => {
    opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
  });

  // Whitelisten Sie alle Attribute, die Sie unterstützen müssen,
  // andernfalls entfernt unser Sanitäter sie
  helper.whiteList(["div.amazingness"]);

  // Sie können auch so etwas Schönes machen
  helper.whiteList({
    custom(tag, name, value) {
      if ((tag === "a" || tag === "li") && name === "id") {
        return !!value.match(/^fn(ref)?\d+$/);
      }
    },
  });

  // Schließlich ist dies die Magie, die Sie verwenden würden, um die Erweiterung in
  // unserer Pipeline zu registrieren. whateverGlobal ist der Name des Globals, das das Plugin bereitstellt
  // es nimmt eine einzelne (md) Variable entgegen, die dann verwendet wird, um die Pipeline zu ändern
  helper.registerPlugin(window.whateverGlobal);
}

Immer testen

Discourse’s bin/rake autospec ist Plugin-bewusst :innocent:

Das bedeutet, dass jedes Mal, wenn Sie die Datei spec/pretty_text_spec.rb hinzufügen und speichern, die Plugin-Testdatei ausgeführt wird.

Ich benutze dies ausgiebig, da es die Arbeit so viel schneller macht.

Angenommen, Sie haben ein Plugin hinzugefügt, das jede Zahl in einem Beitrag in eine 8 verwandelt, Sie können es discourse-magic-8-ball nennen.

So würde ich die Tests strukturieren:

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

Möglicherweise müssen Sie Beiträge „dekorieren“

In manchen Fällen funktionieren Plugins am besten, wenn sie Ihren Beiträgen zusätzliche „dynamische“ Funktionen hinzufügen. Beispiele hierfür sind das poll-Plugin oder das footnotes-Plugin, das ein „…“ hinzufügt, das dynamisch einen Tooltip anzeigt.

Wenn Sie Beiträge „dekorieren“ müssen, fügen Sie assets/javascripts/api-initializers/your-initializer.js hinzu

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) => {
    // Ihre erstaunliche Magie kommt hierher
  });
});

Möglicherweise müssen Sie die Beiträge „nachbearbeiten“

In manchen Fällen müssen Sie Beiträge „nachbearbeiten“. Die Markdown-Rendering-Engine weiß bauartbedingt nichts von bestimmten Informationen wie zum Beispiel post_id. In manchen Fällen möchten Sie serverseitigen Zugriff auf den Beitrag und den „gekochten“ HTML-Code haben. Dies kann Ihnen Dinge wie das Auslösen von Hintergrundjobs, das Synchronisieren benutzerdefinierter Felder oder das „Korrigieren“ automatisch generierter HTML-Codes ermöglichen.

Für Fußnoten benötigte ich eine eindeutige id für jede Fußnote, was bedeutete, dass ich Zugriff auf die post_id benötigte. Daher war ich gezwungen, Änderungen am HTML im Post-Prozessor (der in sidekiq läuft) vorzunehmen.

Um sich einzuklinken, würden Sie Folgendes zu Ihrer plugin.rb-Datei hinzufügen:

DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
  doc.css("a.always-bing").each do |a|
    # dies sollte immer zu bing gehen
    a["href"] = "https://bing.com"
  end
end

Möglicherweise benötigen Sie benutzerdefiniertes CSS

Wenn Sie benutzerdefiniertes CSS versenden möchten, stellen Sie sicher, dass Sie die Datei in plugin.rb registrieren.

Fügen Sie Ihr CSS zu assets/stylesheets/magic.scss hinzu und führen Sie dann

register_asset "stylesheets/magic.scss"

Denken Sie daran, dass wir Änderungen „automatisch neu laden“, sodass Sie Ihr Plugin-CSS ändern und die Änderungen während der Entwicklung sofort sehen können.

Viel Glück bei Ihren Neuverpackungsabenteuern :four_leaf_clover:


Dieses Dokument wird versioniert – schlagen Sie Änderungen auf github vor.

29 „Gefällt mir“

Ich sehe einige Superset-Tools, die Markdown erweitern, wie z. B. Quarkdown, das sehr leistungsfähig zu sein scheint.

Gibt es eine Möglichkeit, die markdown-it-Erweiterung durch Tools wie Quarkdown zu ersetzen?