Discourse utilise un moteur Markdown appelé Markdown-it.
Voici quelques notes de développement qui vous aideront soit à corriger des bogues dans le cœur, soit à créer vos nouvelles extensions.
Les bases
Discourse ne contient que quelques aides au-dessus du moteur, donc la grande majorité de l’apprentissage nécessaire consiste à comprendre Markdown It.
Le répertoire docs contient la documentation actuelle.
Je recommande fortement de lire :
-
Le document d’architecture pour comprendre au plus haut niveau comment le moteur fonctionne.
-
Développement pour les directives de développement de base
-
La documentation de l’API pour une référence très détaillée
-
Et enfin, le code source qui est très bien documenté et clair.
Lorsque je développe des extensions pour le moteur, j’ai l’habitude d’ouvrir un deuxième éditeur en regardant les règles existantes. Le moteur se compose d’une longue liste de règles et chaque règle se trouve dans un fichier dédié qui est raisonnablement facile à suivre.
Si je travaille sur une règle en ligne, je réfléchirai à la règle en ligne existante qui lui ressemble plus ou moins et je baserai mon travail dessus.
Gardez à l’esprit que vous pouvez parfois vous en sortir en modifiant simplement un rendu pour obtenir la fonctionnalité souhaitée, ce qui est généralement beaucoup plus facile.
Comment structurer une extension ?
Lorsque le moteur markdown s’initialise, il recherche dans tous les modules.
Si un module est nommé /discourse-markdown\\/|markdown-it\\// (ce qui signifie qu’il se trouve dans un répertoire discourse-markdown ou markdown-it), il sera candidat à l’initialisation.
Si le module exporte une méthode appelée setup, elle sera appelée par le moteur lors de l’initialisation.
Le protocole de configuration
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... votre code va ici
}
Une méthode setup obtient l’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 surtruelorsque le nouveau moteur est utilisé. Pour une bonne rétrocompatibilité, vous voudrez la vérifier. -
registerOptions(cb(opts,siteSettings,state)): la fonction fournie est appelée avant que le moteur markdown ne soit initialisé, 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 avec notre assainisseur. -
registerPlugin(func(md)): cette méthode est utilisée pour enregistrer un plugin Markdown It.
Tout mettre ensemble
function amazingMarkdownItInline(state, silent) {
// l'extension inline markdown it standard 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 que vous pouvez utiliser pour des balises BBCode personnalisées. Une règle de niveau inline et une de niveau bloc.
Les règles bbcode inline sont celles qui se trouvent dans un paragraphe inline 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 inline qui sont appliquées dans l’ordre.
md.block.bbcode.ruler contient une liste de règles de niveau bloc
Il existe de nombreux exemples de règles inline à : bbcode-inline.js
Les citations et les sondages sont de bons exemples de règles de bloc bbcode.
Règles BBCode inline
Les règles BBCode inline 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",
});
Provoquera
test [u]test[/u]
Être converti en :
test <span>test</span>
Les règles inline peuvent soit envelopper, soit remplacer le texte. Lors de l’enveloppement, vous pouvez également passer une fonction pour gagner en flexibilité supplémentaire.
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 {
// simplement supprimer 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 garantit qu'il n'est pas supprimé
startToken.type = "html_inline";
}
return false;
},
});
La fonction d’enveloppement fournit l’accès à :
-
Les 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 de début de l’inline
-
Le jeton de fin de l’inline
-
Le contenu de l’inline bbcode
En utilisant ces informations, vous pouvez gérer toutes sortes de besoins d’enveloppement.
Occasionnellement, vous voudrez peut-être remplacer l’intégralité du 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 entier [code]bloc de code[/code] 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>
La fonction d’enveloppement 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 obtenir un contrôle total sur le rendu de bloc avec les règles before et after, cela vous permet de faire des choses comme imbriquer une balise deux fois, 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 propose une règle de base spéciale supplémentaire pour appliquer des expressions régulières au texte.
md.core.textPostProcess.ruler
Pour utiliser :
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //les drapeaux regex NE SONT PAS supportés
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 contrôlé par version - suggérez des modifications sur github.