Como adiciono suporte a editor de rich text à minha extensão de markdown?

Criei uma extensão que adiciona algumas tags bbcode para renderizar snapblocks. Quando o editor de rich text foi introduzido, isso quebrou minha extensão, ou pelo menos a tornou inutilizável com o editor de rich text. Quero adicionar suporte para o editor de rich text, então dei uma olhada na extensão spoiled para ver como ela faz isso, e atualmente tenho isso funcionando parcialmente. Eu consigo inserir os snapblocks na postagem no editor de rich text, mas não consigo fazer com que as tags snapblocks existentes sejam convertidas para o editor de rich text.

Aqui está o que tenho até agora.

No momento, estou focado especificamente na conversão real. Tenho uma ideia básica de como funciona, mas até agora não estou obtendo nenhum resultado. Então, como faço o parsing funcionar?

Depois de muita dor de cabeça e de adicionar muitos console.log() ao discourse, descobri por que não estava funcionando e como fazê-lo funcionar.

O parsing não é feito com o HTML renderizado, mas sim com os tokens do markdownit. Eu estava usando state.push(html_raw) na função replace() para renderizar minhas tags bbcode [snapblocks].

// assets/javascripts/lib/discourse-markdown/snapblocks-discourse.js

export function(helper) {
  md.block.bbcode.ruler.push("snapblocks", {
      tag: "snapblocks",
      replace(state, tagInfo, content) {
        let token = state.push('html_raw', '', 0)
        token.content = `<pre class="snapblocks-blocks">${content}</pre>`
      },
    });
}

O problema era que o tipo de token html_raw é ignorado pelo conversor do editor de rich text. O que significa que você não pode usar isso se quiser suporte ao editor de rich text.

Descobri que, usando bbcode_open, text e bbcode_close, consigo fazer funcionar.

// assets/javascripts/lib/discourse-markdown/snapblocks-discourse.js

export function(helper) {
  md.block.bbcode.ruler.push("snapblocks", {
      tag: "snapblocks",
      replace(state, tagInfo, content) {
        let token = state.push('bbcode_open', 'pre', 1)
        token.attrs = [['class', 'snapblocks-discourse']]

        token = state.push('text', '', 0)
        token.content = content

        token = token.push('bbcode_close', 'pre', -1)
        token.attrs = [['class', 'snapblocks-discourse']] // necessário para verificações posteriores
      },
    });
}

Uma vez feito isso, isso não será mais ignorado.

Movendo para rich-text-editor-extension.js, você então faz isso.

// assets/javascripts/lib/rich-text-editor-extension.js

import { i18n } from "discourse-i18n";

const SNAPBLOCKS_NODES = ["inline_snapblocks", "snapblocks"];

/** @type {RichEditorExtension} */
const extension = {
    snapblocks: {
      attrs: { rendered: { default: true } },
      code: true,
      group: "block",
      content: "text*", // Isso é necessário
      createGapCursor: true,
      parseDOM:[{ tag: "pre.snapblocks-blocks" }],
      toDOM: () => ["pre", { class: "snapblocks-blocks" }, 0],
    },
  },
  parse: {
    bbcode_open(state, token) {
      // O token aqui é o mesmo objeto de token `bbcode_open`
      // do código anterior
      if (token.attrGet('class') === 'snapblocks-blocks') {
        state.openNode(state.schema.nodes.snapblocks, {
          open: token.attrGet("open") !== null,
        });
        return true;
      }
    },
    bbcode_close(state, token) {
      if (token.attrGet('class') === 'snapblocks-blocks') {
        state.closeNode();
        return true;
      }
    },
  },
  serializeNode: {
    snapblocks(state, node) {
      // Isso apenas o converte de volta para texto simples markdown
      state.write("[snapblocks]\n");
      state.renderContent(node);
      state.write("\n[/snapblocks]\n\n");
    },
  },
}

As chaves na propriedade parse são parsers para o tipo de token que pode ser usado no parser de markdown.

Definitivamente há mais do que apenas isso, mas pelo menos isso esclarece muito mais do que antes.

Não exatamente, mas já temos a extensão html-block lidando com os tokens html_raw do markdown-it, que é uma maneira genérica de lidar com conteúdo passthrough.

Você pode verificar todos os tipos de extensões permitidas via registerRichEditorExtension, e existem muitas extensões de plugins assim como as que são registradas por padrão que podem ser usadas como inspiração.

Por favor, nos informe se tiver alguma dúvida.

Obrigado pelo esclarecimento.


Estou agora a deparar-me com um problema em que alternar a versão de bloco não está a funcionar. Consigo alternar blocos em linha, mas não o nó de nível de bloco.

A certa altura, gostaria que ele renderizasse os blocos quando o cursor não estivesse sobre eles, mas sinto que isso pode vir depois de apenas fazê-lo funcionar.

Desculpe, não entendi. O que você quer dizer com “alternar a versão de bloco”?

Minha tag bbcode tem [snapblocks] e [sb], onde [sb] é inline e [snapblocks] é de nível de bloco. Ao pressionar o botão da barra de ferramentas, ele está alternando a tag inline corretamente, mas não está alternando a tag de nível de bloco quando seleciono um bloco de texto.