Guida per sviluppatori alle estensioni Markdown

Discourse utilizza un motore Markdown chiamato Markdown-it.

Ecco alcune note di sviluppo che ti aiuteranno a correggere bug nel core o a creare i tuoi nuovi plugin.

Le basi

Discourse contiene solo pochi helper sopra il motore, quindi la maggior parte dell’apprendimento necessario consiste nel comprendere Markdown It.

La directory docs contiene la documentazione attuale.

Consiglio vivamente di leggere:

Mentre sviluppo estensioni per il motore, apro di solito un secondo editor per guardare le regole esistenti. Il motore consiste in una lunga lista di regole e ogni regola è in un file dedicato che è ragionevolmente facile da seguire.

Se sto lavorando su una regola inline, penso a quale regola inline esistente funzioni più o meno in modo simile e baso 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 corrisponde a /discourse-markdown\/|markdown-it\// (cioè si trova 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 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ò essere usato per l’inizializzazione. Questo contiene i seguenti metodi e variabili:

  • bool markdownIt : questa proprietà è impostata su true quando il nuovo motore è in uso. Per una corretta compatibilità con le versioni precedenti, è consigliabile verificarla.

  • registerOptions(cb(opts, siteSettings, state)) : la funzione fornita viene chiamata prima che il motore markdown venga inizializzato; puoi usarla per determinare se abilitare o disabilitare il motore.

  • allowList([spec, ...]): questo metodo viene usato per creare una lista di approvazione (allowlist) per l’HTML con il nostro sanitizzatore.

  • registerPlugin(func(md)): questo metodo viene usato per registrare un plugin Markdown It.

Mettere tutto insieme

function amazingMarkdownItInline(state, silent) {
   // qui va l'estensione inline standard di markdown it.
   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

Generare riferimenti hashtag in Ruby

Usa HashtagAutocompleteService#hashtags_for per costruire riferimenti per il Markdown generato:

HashtagAutocompleteService.new(user.guardian).hashtags_for("tag", ["bug", "support"]).join(" ")

Se una categoria visibile ha lo slug bug, il risultato è #bug::tag #support.
Passa nomi già filtrati per la visibilità dello spettatore. Ordine e maiuscole/minuscole sono
preservati, i conflitti vengono controllati rispetto ai tipi di priorità superiore e un nome che
la regola hashtag non riesce a corrispondere ricade sul testo normale.

BBCode

Discourse contiene 2 righelli (rulers) che puoi usare per tag BBCode personalizzati. Un righello inline e uno a livello di blocco.

Le regole bbcode inline sono quelle che si trovano in un paragrafo inline come [b]bold[/b]

Le regole a livello di blocco si applicano a più righe di testo come:

[poll]
- opzione 1

- opzioni 2
[/poll]

md.inline.bbcode.ruler contiene una lista di regole inline applicate in ordine.

md.block.bbcode.ruler contiene una lista di regole a livello di blocco

Ci sono molti esempi di regole inline in: bbcode-inline.js

I Citazioni e i sondaggi sono buoni esempi di regole bbcode a blocco.

Regole BBCode inline

Le regole BBCode inline sono un oggetto contenente informazioni su come gestire un tag.

Ad esempio:

md.inline.bbcode.ruler.push("underline", {
  tag: "u",
  wrap: "span.bbcode-u",
});

Causerà la conversione di

test [u]test[/u]

in:

test <span class="bbcode-u">test</span>

Le regole inline possono avvolgere (wrap) o sostituire il testo. Quando si avvolge, si può 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 {
      // rimuovi semplicemente il tag bbcode
      endToken.content = "";
      startToken.content = "";

      // caso limite, non vogliamo che questo venga rilevato come un onebox se collegato automaticamente
      // questo assicura che non venga rimosso
      startToken.type = "html_inline";
    }

    return false;
  },
});

La funzione di avvolgimento 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 ogni sorta di esigenze di avvolgimento.

A volte potresti voler sostituire l’intero blocco BBCode, per questo 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]code block[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 dei blocchi sono le stesse per i casi semplici:

md.block.bbcode.ruler.push("happy", {
  tag: "happy",
  wrap: "div.happy",
});
[happy]
hello
[/happy]

diventerà

<div class="happy">hello</div>

La funzione wrapper ha un’API leggermente diversa perché non ci sono token di avvolgimento.

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 sulla rendering dei blocchi con le regole before e after, questo ti permette di fare cose come annidare doppiamente un tag 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 è fornito con una regola core extra e speciale per applicare espressioni regolari al testo.

md.core.textPostProcess.ruler

Per usare:

md.core.textPostProcess.ruler.push("onlyfastcars", {
  matcher: /(car)|(bus)/, // le flag regex NON sono supportate
  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

Diventerà

<p>I like fast cars and fast buses</p>

Questo documento è versionato - suggerisci modifiche su github.

35 Mi Piace

Questo può/verrà utilizzato in un componente del tema? O è esclusivo dei plugin?