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 helpers par-dessus le moteur, donc la grande majorité de ce qu’il faut apprendre est de 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échis à la règle en ligne existante qui fonctionne plus ou moins comme celle que je veux créer et je base mon travail dessus.
Gardez à l’esprit que vous pouvez parfois vous en sortir en modifiant simplement un renderer 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 pendant l’initialisation.
Le protocole setup
/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 sanitizer. -
registerPlugin(func(md)): cette méthode est utilisée pour enregistrer un plugin Markdown It.
Mettre tout ensemble
function amazingMarkdownItInline(state, silent) {
// l'extension en ligne 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 rulers que vous pouvez utiliser pour des balises BBCode personnalisées. Un ruler de niveau inline et un de niveau block.
Les règles BBCode inline sont celles qui résident dans un paragraphe en ligne comme [b]gras[/b]
Les règles de niveau block 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 block
Il existe de nombreux exemples de règles inline ici : bbcode-inline.js
Les Guillemets et les sondages sont de bons exemples de règles BBCode block.
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 la conversion de :
test [u]test[/u]
En :
test <span>test</span>
Les règles inline peuvent soit envelopper (wrap), soit remplacer (replace) le texte. Lors de l’enveloppement, vous pouvez également passer une fonction pour obtenir plus de flexibilité.
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 {
// supprimer simplement la balise bbcode
endToken.content = "";
startToken.content = "";
// cas particulier, nous ne voulons pas que cela soit détecté comme un onebox si lié automatiquement
// cela garantit que ce n'est pas supprimé
startToken.type = "html_inline";
}
return false;
},
});
La fonction d’enveloppement fournit l’accès à :
-
Le
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 (token) 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 tout le 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 Block
Les règles BBCode block 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 block avec les règles before et after, ce qui vous permet de faire des choses comme imbriquer deux fois une balise, 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 supplémentaire spéciale pour appliquer des expressions régulières au texte.
md.core.textPostProcess.ruler
Pour l’utiliser :
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //les drapeaux regex NE sont PAS pris en charge
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.