Reempacotando uma extensão markdown-it como um plugin Discourse

markdown.it Discourse usa o motor CommonMark, que possui uma vasta gama de plugins

Âncoras de cabeçalho, listas de definição, setas inteligentes e a lista continua.

Primeiro um aviso

:warning: O CommonMark deve ser… Comum. Quanto mais você se afasta da especificação, menos Comum seu Markdown se torna. Isso pode dificultar a portabilidade para outras soluções e, se não for cuidadoso, causar inconsistências internas na análise. Antes de reempacotar qualquer coisa, certifique-se de responder à pergunta “eu realmente quero reempacotar isso?”

Acabei de terminar de reempacotar Discourse Footnote e tenho algumas lições para compartilhar sobre como fazer isso corretamente.

Passos para os preguiçosos

Se você é preguiçoso e só quer começar, a maneira mais fácil é simplesmente fazer um fork do footnotes e trocar arquivos e nomes de variáveis. Eu fui bem cuidadoso para garantir que ele siga as melhores práticas, então você deve ter um bom exemplo.

Movimentos de abertura, um reempacotamento mínimo

Pelo que pude perceber, a maioria dos plugins do markdown.it é distribuída como arquivos js puros. Em muitos casos, os plugins são simplesmente distribuídos como um único arquivo js, como este: markdown-it-mark.js.

Idealmente, você deseja deixar o original intacto, o que significa que você pode simplesmente copiar uma versão atualizada do arquivo para o seu plugin sem precisar mexer no plugin existente.

O primeiro problema que você encontrará é que você tem que ensinar seu plugin a carregar este JavaScript no servidor, pois o motor Markdown também é executado no servidor. Para fazer isso, você pode simplesmente copiar o arquivo como está para assets/javascripts/vendor/original-plugin.js e, em seu arquivo plugin.rb, você adicionaria:

# isso ensina nosso motor markdown a carregar seu arquivo js puro
register_asset "javascripts/vendor/original-plugin.js", :vendored_pretty_text

Depois de incluir o corpo real do plugin, você precisa ensinar nosso pipeline a carregar e inicializar:

Crie um arquivo chamado assets/javascripts/lib/discourse-markdown/your-extension.js

Este arquivo será carregado automaticamente porque termina com .js E está no diretório discourse-markdown.

Um exemplo simples pode ser:

export function setup(helper) {
  // isso permite que você carregue sua extensão apenas se uma configuração do site estiver ativada
  helper.registerOptions((opts, siteSettings) => {
    opts.features["your-extension"] = !!siteSettings.enable_my_plugin;
  });

  // lista de permissões para quaisquer atributos que você precise suportar,
  // caso contrário, nosso sanitizador os removerá
  helper.whiteList(["div.amazingness"]);

  // você também pode fazer coisas sofisticadas como esta
  helper.whiteList({
    custom(tag, name, value) {
      if ((tag === "a" || tag === "li") && name === "id") {
        return !!value.match(/^fn(ref)?\\d+$/);
      }
    },
  });

  // finalmente, esta é a mágica que você usaria para registrar a extensão em
  // nosso pipeline. whateverGlobal é o nome do global que o plugin expõe
  // ele recebe uma única variável (md) que é então usada para complementar o pipeline
  helper.registerPlugin(window.whateverGlobal);
}

Sempre esteja testando

O bin/rake autospec do Discourse está ciente de plugins :innocent:

Isso significa que, quando você adiciona o arquivo spec/pretty_text_spec.rb, toda vez que você o salva, o arquivo de teste do plugin será executado.

Eu uso isso extensivamente porque torna o trabalho muito mais rápido.

Digamos que você adicionou um plugin que muda todos os números em uma postagem para 8 círculos, você pode chamá-lo de discourse-magic-8-ball.

Veja como eu estruturaria os testes:

require "rails_helper"

describe PrettyText do
  it "pode ser desativado" do
    SiteSetting.enable_magic_8_ball = false

    markdown = <<-MD
      1 coisa
    MD

    html = <<-HTML
      <p>1 coisa</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end

  it "suporta magic 8 ball" do
    markdown = <<-MD
      1 coisa
    MD

    html = <<-HTML
      <p>8 círculo coisa</p>
    HTML

    cooked = PrettyText.cook markdown.strip
    expect(cooked).to eq(html.strip)
  end
end

Você pode precisar “decorar as postagens”

Em alguns casos, os plugins funcionam melhor quando adicionam recursos extras “dinâmicos” às suas postagens. Exemplos disso são o plugin poll ou o plugin footnotes, que adiciona um “…” que exibe dinamicamente uma dica de ferramenta.

se você precisar “decorar” postagens, adicione assets/javascripts/api-initializers/your-initializer.js

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer((api) => {
  const siteSettings = api.container.lookup("service:site-settings");
  if (!siteSettings.enable_magic_8_ball) {
    return;
  }

  api.decorateCookedElement((elem) => {
    // sua mágica incrível vai aqui
  });
});

Você pode precisar “pós-processar” as postagens

Em alguns casos, você pode precisar “pós-processar” as postagens, o motor de renderização markdown, por design, não está ciente de certas informações como, por exemplo, post_id. Em alguns casos, você pode querer acesso no lado do servidor à postagem e ao HTML “cozido”, isso pode permitir que você faça coisas como acionar trabalhos em segundo plano, sincronizar campos personalizados ou “corrigir” HTML gerado automaticamente.

Para notas de rodapé, eu precisava de um id distinto para cada nota de rodapé, o que significava que eu precisava de acesso ao post_id, então fui forçado a fazer alterações no HTML no processador de postagem (que é executado no sidekiq)

Para se conectar, você adicionaria o seguinte ao seu arquivo plugin.rb:

DiscourseEvent.on(:before_post_process_cooked) do |doc, post|
  doc.css("a.always-bing").each do |a|
    # isso sempre deve ir para o bing
    a["href"] = "https://bing.com"
  end
end

Você pode precisar de algum CSS personalizado

Se você quiser enviar CSS personalizado, certifique-se de registrar o arquivo em plugin.rb

Adicione seu css a assets/stylesheets/magic.scss e então execute

register_asset "stylesheets/magic.scss"

Lembre-se que nós “recarregamos automaticamente” as alterações para que você possa alterar o CSS do seu plugin e ver as alterações em tempo real no desenvolvimento.

Boa sorte com suas aventuras de reempacotamento :four_leaf_clover:


Este documento é controlado por versão - sugira alterações no github.

29 curtidas

Vejo algumas ferramentas superset que estendem o Markdown, como o Quarkdown que parece ser muito poderoso.

Existe uma maneira de substituir a extensão markdown-it por ferramentas como o Quarkdown?