Discourse utilizza un motore Markdown chiamato Markdown-it.
Ecco alcune note per gli sviluppatori che ti aiuteranno a correggere bug nel core o a creare i tuoi nuovi plugin.
Le Basi
Discourse contiene solo alcuni helper sopra il motore, quindi la stragrande maggioranza dell’apprendimento necessario consiste nel comprendere Markdown It.
La directory docs contiene la documentazione attuale.
Raccomando vivamente di leggere:
-
Il documento sull’architettura per comprendere a livello generale come funziona il motore.
-
Sviluppo per le linee guida di sviluppo di base
-
La documentazione API per un riferimento molto dettagliato
-
E infine, il codice sorgente che è molto ben documentato e chiaro.
Mentre sviluppo estensioni per il motore, di solito apro un secondo editor guardando le regole esistenti. Il motore è costituito da un lungo elenco di regole e ogni regola si trova in un file dedicato ragionevolmente facile da seguire.
Se sto lavorando su una regola inline, penserò a quale regola inline esistente funziona più o meno come quella che voglio creare e baserò il mio lavoro su di essa.
Tieni presente che a volte puoi cavartela semplicemente modificando un renderer per ottenere la funzionalità desiderata, il che di solito è molto più semplice.
Come strutturare un’estensione?
Quando il motore markdown si inizializza, cerca attraverso tutti i moduli.
Se un modulo è chiamato /discourse-markdown\\/|markdown-it\\// (il che significa che risiede in una directory discourse-markdown o markdown-it) sarà un candidato per l’inizializzazione.
Se il modulo esporta un metodo chiamato setup, questo verrà chiamato dal motore durante l’inizializzazione.
Il protocollo setup
/my-plugins/assets/javascripts/discourse-markdown/awesome-extension.js
export function setup(helper) {
// ... il tuo codice va qui
}
Un metodo setup ottiene accesso a un oggetto helper che può utilizzare per l’inizializzazione. Questo contiene i seguenti metodi e variabili:
-
bool markdownIt: questa proprietà è impostata sutruequando viene utilizzato il nuovo motore. Per una corretta retrocompatibilità, vorrai controllarla. -
registerOptions(cb(opts,siteSettings,state)): la funzione fornita viene chiamata prima che il motore markdown sia inizializzato, puoi usarla per determinare se abilitare o disabilitare il motore. -
allowList([spec, ...]): questo metodo viene utilizzato per mettere in whitelist l’HTML con il nostro sanitizer. -
registerPlugin(func(md)): questo metodo viene utilizzato per registrare un plugin Markdown It.
Mettere tutto insieme
function amazingMarkdownItInline(state, silent) {
// l'estensione inline markdown it standard va qui.
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);
});
}
Estensioni specifiche di Discourse
BBCode
Discourse contiene 2 ruler che puoi usare per tag BBCode personalizzati. Un ruler a livello inline e uno a livello di blocco.
Le regole bbcode inline sono quelle che risiedono in un paragrafo inline come [b]grassetto[/b]
Le regole a livello di blocco si applicano a più righe di testo come:
[poll]
- opzione 1
- opzione 2
[/poll]
md.inline.bbcode.ruler contiene un elenco di regole inline che vengono applicate in ordine.
md.block.bbcode.ruler contiene un elenco di regole a livello di blocco
Ci sono molti esempi per le regole inline su: bbcode-inline.js
Le citazioni e i sondaggi sono buoni esempi di regole bbcode a livello di blocco.
Regole BBCode Inline
Le regole BBCode inline sono un oggetto contenente informazioni su come gestire un tag.
Per esempio:
md.inline.bbcode.ruler.push("underline", {
tag: "u",
wrap: "span.bbcode-u",
});
Causano la conversione di:
test [u]test[/u]
In:
test <span>test</span>
Le regole inline possono avvolgere o sostituire il testo. Quando si avvolge, è possibile passare anche una funzione per ottenere maggiore flessibilità.
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 {
// semplicemente rimuovi il tag bbcode
endToken.content = "";
startToken.content = "";
// caso limite, non vogliamo che questo venga rilevato come onebox se è auto-collegato
// questo assicura che non venga rimosso
startToken.type = "html_inline";
}
return false;
},
});
La funzione di wrapping fornisce accesso a:
-
Il tagInfo, che è un dizionario di chiavi/valori specificati tramite bbcode.
[test=testing]→{_default: "testing"}
[test a=1]→{a: "1"} -
Il token che inizia l’inline
-
Il token che termina l’inline
-
Il contenuto dell’inline bbcode
Usando queste informazioni puoi gestire tutti i tipi di esigenze di wrapping.
Occasionalmente potresti voler sostituire l’intero blocco BBCode, per quello puoi usare 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;
},
});
In questo caso stiamo sostituendo un intero [code]blocco di codice[code] con un singolo token code_inline.
Regole BBCode a Blocco
Le regole bbcode a blocco ti permettono di sostituire un intero blocco. Le API per i blocchi sono le stesse per i casi semplici:
md.block.bbcode.ruler.push("happy", {
tag: "happy",
wrap: "div.happy",
});
[happy]
ciao
[/happy]
diventerà
<div class="happy">ciao</div>
La funzione wrapper ha un’API leggermente diversa poiché non ci sono token di wrapping.
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]
Diventerà
<div data-money="100">
<b>test</b>
</div>
Puoi ottenere il controllo completo sul rendering dei blocchi con le regole before e after, questo ti permette di fare cose come annidare un tag due volte e così via.
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]
diventerà
<div>
<div>test</div>
</div>
Gestione delle sostituzioni di testo
Discourse fornisce una regola core speciale aggiuntiva per applicare espressioni regolari al testo.
md.core.textPostProcess.ruler
Per usare:
md.core.textPostProcess.ruler.push("onlyfastcars", {
matcher: /(car)|(bus)/, //i flag delle regex NON sono supportati
onMatch: function (buffer, matches, state) {
let token = new state.Token("text", "", 0);
token.content = "fast " + matches[0];
buffer.push(token);
},
});
Mi piacciono le auto e gli autobus
Diventerà
<p>Mi piacciono le auto veloci e gli autobus veloci</p>
Questo documento è controllato tramite versione - suggerisci modifiche su github.