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

snapblocks를 렌더링하기 위해 몇 가지 bbcode 태그를 추가하는 확장 기능을 만들었습니다. 리치 텍스트 에디터가 도입된 이후 이 확장 기능이 약간 깨지거나, 적어도 리치 텍스트 에디터와 함께 사용할 수 없는 상태가 되었습니다. 리치 텍스트 에디터 지원을 추가하기 위해 spoiled 확장 기능을 조사해 보니, 현재는 어느 정도 작동하는 상태로 만들었습니다. 실제로 리치 텍스트 에디터에서 게시물에 snapblocks를 삽입할 수는 있지만, 기존 snapblocks 태그를 리치 텍스트 에디터 형식으로 변환하는 방법을 찾지 못하고 있습니다.

지금까지 만든 내용은 다음과 같습니다.

현재 저는 실제 변환 과정에 집중하고 있습니다. 작동 방식에 대한 기본적인 추측은 있지만, 아직까지 결과를 얻지 못하고 있습니다. 그래서 파싱을 작동시키는 방법은 무엇일까요?

머리를 쥐어뜯고 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 속성의 키들은 마크다운 파서에서 사용할 수 있는 토큰 유형에 대한 파서입니다.

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

완전히 정확하지는 않지만, html-block 확장 기능이 이미 html_raw markdown-it 토큰을 처리하고 있으며, 이는 passthrough 콘텐츠를 처리하는 일반적인 방법입니다.

registerRichEditorExtension을 통해 허용되는 모든 확장 유형을 확인해 볼 수 있으며, 플러그인에서 제공하는 확장 기능기본적으로 등록된 확장 기능을 참고할 수 있습니다.

질문이 있으시면 알려주세요.

정리해 주셔서 감사합니다.


이제 블록 버전 토글이 작동하지 않는 문제에 직면했습니다. 인라인 블록은 토글할 수 있지만, 블록 레벨 노드는 토글할 수 없습니다.

어느 시점에 커서가 블록에 있지 않을 때 블록을 렌더링하도록 하고, 스타일 선택기도 추가하고 싶지만, 일단 기본 편집 기능이 정상적으로 작동하는 것이 먼저라고 생각합니다.

죄송하지만 잘 이해가 되지 않습니다. “블록 버전 토글링”이 정확히 무엇을 의미하는지 알려주실 수 있을까요?

제 BBCode 태그에는 [snapblocks][sb]가 있습니다. 여기서 [sb]는 인라인 태그이고, [snapblocks]는 블록 레벨 태그입니다. 툴바 버튼을 누르면 인라인 태그는 잘 토글되지만, 텍스트 블록을 선택한 상태에서 블록 레벨 태그는 토글되지 않습니다.