Discourse utilise un moteur Markdown appelé Markdown-it.
Voici quelques notes de développement qui vous aideront à corriger des bugs dans le cœur du système ou à créer vos nouveaux plugins.
Les bases
Discourse ne contient que quelques aides quelques au-dessus du moteur, donc la grande majorité de l’apprentissage nécessaire consiste à comprendre Markdown It.
Le dossier de documentation 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’ouvre généralement un second éditeur pour examiner les règles existantes. Le moteur est composé d’une longue liste de règles et chaque règle est dans un fichier dédié qui est raisonnablement facile à suivre.
Si je travaille sur une règle en ligne (inline), je cherche quelle règle en ligne existante fonctionne plus ou moins de la même manière et je base mon travail dessus.
Gardez à l’esprit que vous pouvez parfois vous contenter de modifier un rendu (renderer) pour obtenir la fonctionnalité souhaitée, ce qui est généralement beaucoup plus simple.
Comment structurer une extension ?
Lorsque le moteur Markdown s’initialise, il parcourt tous les modules.
Si un module correspond à /discourse-markdown\/|markdown-it\// (ce qui signifie qu’il se trouve dans un dossier discourse-markdown ou markdown-it), il sera candidat à l’initialisation.
Si le module exporte une méthode appelée setup, celle-ci sera appelée par le moteur pendant l’initialisation.
Le protocole de configuration (setup)
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... votre code va ici
}
Une méthode setup a 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 compatibilité ascendante correcte, vous devez vérifier cette valeur. -
registerOptions(cb(opts, siteSettings, state)): la fonction fournie est appelée avant l’initialisation du moteur Markdown, vous pouvez l’utiliser pour déterminer si le moteur doit être activé ou désactivé. -
allowList([spec, ...]): cette méthode est utilisée pour autoriser le HTML dans notre assainisseur (sanitizer). -
registerPlugin(func(md)): cette méthode est utilisée pour enregistrer un plugin Markdown It.
Mettre tout en place
function amazingMarkdownItInline(state, silent) {
// l'extension en ligne standard markdown it 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
Génération de références de hashtags en Ruby
Utilisez HashtagAutocompleteService#hashtags_for pour construire des références pour le Markdown généré :
HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")
Si une catégorie visible a le slug bug, le résultat est #bug::tag #support.
Transmettez des noms déjà filtrés selon la visibilité de l’utilisateur. L’ordre et la casse sont
préservés, les collisions sont vérifiées par rapport aux types de priorité supérieure, et un nom que
la règle de hashtag ne peut pas correspondre bascule sur du texte brut.
BBCode
Discourse contient 2 règles (rulers) que vous pouvez utiliser pour des balises BBCode personnalisées. Une règle en ligne (inline) et une règle de bloc (block).
Les règles bbcode en ligne sont celles qui se trouvent dans un paragraphe en ligne comme [b]gras[/b]
Les règles de bloc s’appliquent à plusieurs lignes de texte comme :
[poll]
- option 1
- options 2
[/poll]
md.inline.bbcode.ruler contient une liste de règles en ligne appliquées dans l’ordre.
md.block.bbcode.ruler contient une liste de règles de bloc
Il y a de nombreux exemples de règles en ligne ici : bbcode-inline.js
Les citations et les sondages sont de bons exemples de règles de bloc bbcode.
Règles BBCode en ligne
Les règles BBCode en ligne 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",
});
Cela transformera
test [u]test[/u]
En :
test <span class="bbcode-u">test</span>
Les règles en ligne peuvent soit envelopper (wrap) soit remplacer (replace) le texte. Lors de l’enveloppement, vous pouvez également passer une fonction pour gagner en 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 {
// supprime 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 s'assure qu'il n'est pas supprimé
startToken.type = "html_inline";
}
return false;
},
});
La fonction d’enveloppement donne accès à :
-
Le tagInfo, qui est un dictionnaire de clés/valeurs spécifiées via le bbcode.
[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
Le jeton (token) qui commence l’élément en ligne
-
Le jeton qui termine l’élément en ligne
-
Le contenu du bbcode en ligne
En utilisant ces informations, vous pouvez gérer tous les types de besoins d’enveloppement.
Parfois, 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]code block[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>
L’enveloppe par fonction 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 prendre le contrôle total du rendu des blocs avec les règles before et after, ce qui 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>
Gérer les remplacements de texte
Discourse est livré avec une règle spéciale du cœur 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 sous contrôle de version - suggérez des modifications sur github.