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 è la comprensione di Markdown It.
La directory docs contiene la documentazione attuale.
Raccomando vivamente di leggere:
-
Il documento sull’architettura per capire a livello generale come funziona il motore.
-
Sviluppo per le linee guida di sviluppo di base
-
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 le assomiglia di più 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ù facile.
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, verrà chiamato dal motore durante l’inizializzazione.
Il protocollo di 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ò usare 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à vuoi 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 consentire elementi HTML con il nostro sanitizer. -
registerPlugin(func(md)): questo metodo viene utilizzato per registrare un plugin di Markdown It.
Mettiamo tutto insieme
function amazingMarkdownItInline(state, silent) {
// l'estensione inline standard di markdown it 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 a: bbcode-inline.js
Citazioni e sondaggi sono buoni esempi di regole bbcode a blocchi.
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",
});
Farà sì che
test [u]test[/u]
Venga convertito in:
test <span>test</span>
Le regole inline possono racchiudere (wrap) o sostituire il testo. Quando si racchiude, puoi anche passare 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 {
// basta rimuovere il tag bbcode
endToken.content = "";
startToken.content = "";
// caso limite, non vogliamo che questo venga rilevato come onebox se è linkato automaticamente
// 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 finisce 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 a blocco 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 fast auto e le fast autobus</p>
Questo documento è controllato tramite versione - suggerisci modifiche su github.