Discourse verwendet eine Markdown-Engine namens Markdown-it.
Hier sind einige Entwicklerhinweise, die dir helfen, entweder Fehler im Kern zu beheben oder neue Plugins zu erstellen.
Die Grundlagen
Discourse enthält nur wenige Hilfsfunktionen über der Engine, daher besteht der Großteil des Lernens darin, Markdown It zu verstehen.
Das Dokumentationsverzeichnis enthält die aktuelle Dokumentation.
Ich empfehle dringend, Folgendes zu lesen:
-
Das Architektur-Dokument, um auf oberster Ebene zu verstehen, wie die Engine funktioniert.
-
Entwicklung für grundlegende Entwicklungshinweise
-
API-Dokumentation für eine sehr detaillierte Referenz
-
Und schließlich der Quellcode, der sehr gut dokumentiert und klar strukturiert ist.
Während ich Erweiterungen für die Engine entwickle, öffne ich normalerweise einen zweiten Editor, um mir vorhandene Regeln anzusehen. Die Engine besteht aus einer langen Liste von Regeln, und jede Regel befindet sich in einer eigenen Datei, die sich relativ leicht nachvollziehen lässt.
Wenn ich an einer Inline-Regel arbeite, überlege ich, welche vorhandene Inline-Regel in etwa ähnlich funktioniert, und basiere meine Arbeit darauf.
Denke daran, dass man sich manchmal damit begnügen kann, einfach nur einen Renderer zu ändern, um die gewünschte Funktionalität zu erhalten, was in der Regel viel einfacher ist.
Wie strukturiert man eine Erweiterung?
Wenn die Markdown-Engine initialisiert wird, durchsucht sie alle Module.
Wenn ein Modul dem Muster /discourse-markdown\/|markdown-it\// entspricht (was bedeutet, dass es sich in einem discourse-markdown- oder markdown-it-Verzeichnis befindet), wird es als Kandidat für die Initialisierung betrachtet.
Wenn das Modul eine Methode namens setup exportiert, wird diese von der Engine während der Initialisierung aufgerufen.
Das Setup-Protokoll
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... hier kommt dein Code hin
}
Eine setup-Methode erhält Zugriff auf ein Hilfsobjekt, das für die Initialisierung verwendet werden kann. Dieses enthält die folgenden Methoden und Variablen:
-
bool markdownIt: Diese Eigenschaft wird auftruegesetzt, wenn die neue Engine verwendet wird. Für eine korrekte Abwärtskompatibilität solltest du dies überprüfen. -
registerOptions(cb(opts, siteSettings, state)): Die übergebene Funktion wird aufgerufen, bevor die Markdown-Engine initialisiert wird. Du kannst sie verwenden, um zu bestimmen, ob die Engine aktiviert oder deaktiviert werden soll. -
allowList([spec, ...]): Diese Methode wird verwendet, um HTML in unserem Sanitizer zu erlauben (Allowlist). -
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 hier hin.
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
Erstellen von Hashtag-Referenzen in Ruby
Verwende HashtagAutocompleteService#hashtags_for, um Referenzen für generiertes Markdown zu erstellen:
HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")
Wenn eine sichtbare Kategorie den Slug bug hat, lautet das Ergebnis #bug::tag #support.
Übergib Namen, die bereits für die Sichtbarkeit des Betrachters gefiltert wurden. Reihenfolge und Groß-/Kleinschreibung werden
beibehalten, Kollisionen werden gegen Typen mit höherer Priorität geprüft, und ein Name, den die
Hashtag-Regel nicht zuordnen kann, fällt auf Klartext zurück.
BBCode
Discourse enthält 2 Ruler, die du für benutzerdefinierte BBCode-Tags verwenden kannst. Einen für Inline- und einen für Blockebene.
Inline-BBCode-Regeln sind solche, die in einem Inline-Absatz leben, wie [b]fett[/b]
Blockebenen-Regeln gelten für mehrere Zeilen Text wie:
[poll]
- option 1
- options 2
[/poll]
md.inline.bbcode.ruler hält eine Liste von Inline-Regeln, die in dieser Reihenfolge angewendet werden.
md.block.bbcode.ruler hält eine Liste von Blockebenen-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",
});
Verursacht, dass
test [u]test[/u]
umgewandelt wird in:
test <span class="bbcode-u">test</span>
Inline-Regeln können Text entweder umschließen (wrap) oder ersetzen (replace). Beim Umschließen kannst du 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 {
// entferne einfach das bbcode-tag
endToken.content = "";
startToken.content = "";
// Sonderfall, wir wollen nicht, dass dies als Onebox erkannt wird, wenn automatisch verlinkt
// dies stellt sicher, dass es nicht entfernt wird
startToken.type = "html_inline";
}
return false;
},
});
Die Umschließungsfunktion bietet Zugriff auf:
-
Die tagInfo, die ein Wörterbuch von Schlüssel/Wert-Paaren ist, die über bbcode angegeben wurden.
[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
Das Token, das den Inline-Bereich beginnt
-
Das Token, das den Inline-Bereich beendet
-
Der Inhalt des bbcode-Inline-Bereichs
Mit diesen Informationen kannst du alle Arten von Umschließungsbedürfnissen behandeln.
Gelegentlich möchtest du möglicherweise den gesamten BBCode-Block ersetzen, dafür kannst du 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]code block[code] durch ein einzelnes code_inline-Token.
Block-BBCode-Regeln
Block-bbcode-Regeln erlauben es dir, einen gesamten Block zu ersetzen. Die Block-APIs sind für einfache Fälle dieselben:
md.block.bbcode.ruler.push("happy", {
tag: "happy",
wrap: "div.happy",
});
[happy]
hello
[/happy]
wird zu
<div class="happy">hello</div>
Die Funktions-Wrapper hat eine leicht andere API, da es keine Umschließungs-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>
Du kannst mit den Regeln before und after die volle Kontrolle über die Block-Renderung erlangen, was es dir ermöglicht, Dinge wie doppelte Verschachtelung eines Tags und so weiter 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>
Behandlung von Textersatz
Discourse wird mit einer zusätzlichen speziellen Kernregel ausgeliefert, um reguläre Ausdrücke auf Text anzuwenden.
md.core.textPostProcess.ruler
Zur Verwendung:
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //regex flags are NOT supported
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 ist versioniert - schlage Änderungen auf github vor.