Leitfaden für Entwickler zu Markdown-Erweiterungen

Discourse verwendet eine Markdown-Engine namens Markdown-it.

Hier sind einige Entwicklerhinweise, die Ihnen helfen, Fehler im Kern zu beheben oder neue Plugins zu erstellen.

Die Grundlagen

Discourse enthält nur einige Hilfsprogramme über der Engine, daher besteht der Großteil des zu erlernenden Wissens darin, Markdown It zu verstehen.

Das docs-Verzeichnis enthält die aktuelle Dokumentation.

Ich empfehle dringend, Folgendes zu lesen:

Wenn ich Erweiterungen für die Engine entwickle, öffne ich normalerweise einen zweiten Editor und schaue mir bestehende Regeln an. Die Engine besteht aus einer langen Liste von Regeln, und jede Regel befindet sich in einer eigenen Datei, die einigermaßen leicht zu verfolgen ist.

Wenn ich an einer Inline-Regel arbeite, überlege ich, welche bestehende Inline-Regel ihr mehr oder weniger ähnlich ist, und basiere meine Arbeit darauf.

Denken Sie daran, dass Sie manchmal damit durchkommen, nur einen Renderer zu ändern, um die gewünschte Funktionalität zu erhalten, was normalerweise viel einfacher ist.

Wie strukturiert man eine Erweiterung?

Wenn die Markdown-Engine initialisiert wird, durchsucht sie alle Module.

Wenn ein Modul /discourse-markdown\\/|markdown-it\\// heißt (d. h. es befindet sich in einem discourse-markdown- oder markdown-it-Verzeichnis), ist es ein Kandidat für die Initialisierung.

Wenn das Modul eine Methode namens setup exportiert, wird diese während der Initialisierung von der Engine aufgerufen.

Das Setup-Protokoll

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

export function setup(helper) {
  // ... Ihr Code kommt hierher
}

Eine setup-Methode erhält Zugriff auf ein Hilfsobjekt, das sie zur Initialisierung verwenden kann. Dieses enthält die folgenden Methoden und Variablen:

  • bool markdownIt: Diese Eigenschaft wird auf true gesetzt, wenn die neue Engine verwendet wird. Für eine ordnungsgemäße Abwärtskompatibilität sollten Sie dies überprüfen.

  • registerOptions(cb(opts,siteSettings,state)): Die bereitgestellte Funktion wird aufgerufen, bevor die Markdown-Engine initialisiert wird. Sie können sie verwenden, um zu bestimmen, ob die Engine aktiviert oder deaktiviert werden soll.

  • allowList([spec, ...]): Diese Methode wird verwendet, um HTML mit unserem Sanitizer auf die Whitelist zu setzen.

  • registerPlugin(func(md)): Diese Methode wird verwendet, um ein Markdown It Plugin zu registrieren.

Alles zusammenfügen

function amazingMarkdownItInline(state, silent) {
   // Standard markdown it inline-Erweiterung kommt hierher.
   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);
   });
}

Discourse-spezifische Erweiterungen

BBCode

Discourse enthält 2 Ruler, die Sie für benutzerdefinierte BBCode-Tags verwenden können. Einen Inline- und einen Block-Level-Ruler.

Inline-BBCode-Regeln sind solche, die in einem Inline-Absatz wie [b]fett[/b] stehen.

Block-Level-Regeln gelten für mehrere Textzeilen wie:

[poll]
- option 1

- options 2
[/poll]

md.inline.bbcode.ruler enthält eine Liste von Inline-Regeln, die in der Reihenfolge angewendet werden.

md.block.bbcode.ruler enthält eine Liste von Block-Level-Regeln.

Es gibt viele Beispiele für Inline-Regeln unter: bbcode-inline.js

Zitate und Umfragen sind gute Beispiele für BBCode-Blockregeln.

Inline BBCode-Regeln

Inline BBCode-Regeln sind ein Objekt, das Informationen darüber enthält, wie ein Tag zu behandeln ist.

Zum Beispiel:

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

Bewirkt, dass

test [u]test[/u]

konvertiert wird zu:

test <span>test</span>

Inline-Regeln können Text entweder umschließen oder ersetzen. Beim Umschließen können Sie auch eine Funktion übergeben, um zusätzliche Flexibilität zu erhalten.

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 {
      // einfach den bbcode-tag entfernen
      endToken.content = "";
      startToken.content = "";

      // sonderfall, wir wollen nicht, dass dies als onebox erkannt wird, wenn es automatisch verlinkt ist
      // dies stellt sicher, dass es nicht entfernt wird
      startToken.type = "html_inline";
    }

    return false;
  },
});

Die Wrapping-Funktion bietet Zugriff auf:

  • Die tagInfo, ein Wörterbuch von Schlüssel/Werten, die über BBCode angegeben werden.

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

  • Das Token, das die Inline-Struktur startet

  • Das Token, das die Inline-Struktur beendet

  • Den Inhalt des BBCode-Inlines

Mithilfe dieser Informationen können Sie alle Arten von Wrapping-Anforderungen behandeln.

Gelegentlich möchten Sie den gesamten BBCode-Block ersetzen. Dafür können Sie replace verwenden.

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;
  },
});

In diesem Fall ersetzen wir einen gesamten [code]Codeblock[code] durch ein einzelnes code_inline-Token.

Block BBCode-Regeln

Block-BBCode-Regeln ermöglichen es Ihnen, einen gesamten Block zu ersetzen. Die Block-APIs sind für einfache Fälle gleich:

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

wird zu

<div>hello</div>

Die Funktions-Wrapper hat eine etwas andere API, da es keine umgebenden Tokens gibt.

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]

wird zu

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

Sie können die vollständige Kontrolle über das Block-Rendering mit den Regeln before und after erhalten. Dies ermöglicht es Ihnen, Dinge wie das doppelte Verschachteln eines Tags usw. zu tun.

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]

wird zu

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

Umgang mit Textersetzungen

Discourse liefert eine zusätzliche spezielle Kernregel zur Anwendung von regulären Ausdrücken auf Text.

md.core.textPostProcess.ruler

Zur Verwendung:

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, //regex flags werden NICHT unterstützt
  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

wird zu

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

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

35 „Gefällt mir“