Guida per sviluppatori alle estensioni Markdown

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:

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 su true quando 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.

35 Mi Piace