마크다운 확장 기능에 리치 텍스트 에디터 지원을 추가하는 방법은?

머리를 쥐어뜯고 discourse에 console.log()를 많이 추가한 끝에, 왜 작동하지 않았는지 그리고 어떻게 작동하게 만들 수 있는지 발견했습니다.

파싱은 렌더링된 HTML이 아니라 markdownit 토큰으로 수행됩니다. 저는 [snapblocks] bbcode 태그를 렌더링하기 위해 replace() 함수에서 state.push(html_raw)를 사용하고 있었습니다.

// 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>`
      },
    });
}

문제는 html_raw 토큰 유형이 리치 텍스트 에디터 변환기에 의해 무시된다는 점입니다. 즉, 리치 텍스트 에디터 지원을 원한다면 이 방법을 사용할 수 없습니다.

bbcode_open, text, bbcode_close를 사용하면 작동하게 된다는 것을 발견했습니다.

// 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']] // 나중에 검사하는 데 필요
      },
    });
}

이렇게 하면 더 이상 무시되지 않습니다.

rich-text-editor-extension.js로 이동하여 다음을 수행합니다.

// 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*", // 이것은 필요합니다
      createGapCursor: true,
      parseDOM: [{ tag: "pre.snapblocks-blocks" }],
      toDOM: () => ["pre", { class: "snapblocks-blocks" }, 0],
    },
  },
  parse: {
    bbcode_open(state, token) {
      // 여기서 토큰은 이전 코드와 동일한 `bbcode_open` 토큰 객체입니다
      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) {
      // 이것은 단순히 일반 텍스트 마크다운으로 변환합니다
      state.write("[snapblocks]\n");
      state.renderContent(node);
      state.write("\n[/snapblocks]\n\n");
    },
  },
}

parse 속성의 키들은 마크다운 파서에서 사용할 수 있는 토큰 유형에 대한 파서입니다.

이것보다 훨씬 더 많은 것이 있지만, 적어도 이것으로 상황이 훨씬 명확해질 것이며, 제가 해야 할 머리를 쥐어뜯는 횟수가 줄어들 것입니다.