사용자 정의 마크다운 인용 BBCode 태그 추가

snapblocks를 표시하기 위한 discourse 확장 기능을 개발 중입니다. bbcode 태그를 추가하여 블록으로 대체하는 기능은 구현할 수 있었지만, 불행히도 블록을 인용(quote)하면 원래의 bbcode 태그 대신 블록 내부의 텍스트가 코드 블록으로 표시됩니다.

spoiler 플러그인의 소스 코드를 살펴보고 시도해 보았지만, spoiler를 인용할 수 있도록 하는 메커니즘을 파악하지 못하겠습니다.

이 문제를 수정하고 사용자 정의 bbcode 태그 안에 원래의 블록 코드 텍스트를 포함시키는 방법이 있을까요?

개발 중인 플러그인의 소스 코드는 다음과 같습니다.

정말 짜증이 나는 점은, 쉽게 참고할 수 있는 표준 문서가 없다는 것입니다. 이 때문에 플러그인 개발이 훨씬 더 어려워졌습니다.

DecorateCallBack 함수일 것 같습니다:

관련 테스트:

선택된 텍스트의 내용을 반환하여 디버깅을 돕기 위해, 핵심 파일의 이 줄 뒤에 console.log(_selectedText);를 추가할 수 있습니다:

브라우저 콘솔 인터페이스의 스크린샷으로, 다양한 텍스트 요소와 내비게이션 바가 포함되어 있습니다. (AI 캡션)

해당 기능을 테스트해 봤고, 실제로 텍스트 주위에 [sb]를 추가할 수 있었습니다. 하지만 안타깝게도 요소에 있는 텍스트만 포함되었습니다. 대신 원래 텍스트를 인용하도록 하고 싶습니다. 원래 텍스트에는 블록을 재현하는 데 필요한 모든 것이 포함되어 있기 때문입니다.

현재 실제 SVG를 선택할 수 없도록 하고, SVG 아래에 원래 소스를 가진 숨겨진 선택 가능 요소를 두는 방법을 시도하고 있습니다.

이를 위해 SVG와 소스를 포함하는 두 개의 요소를 가진 컨테이너를 생성하고 있습니다. SVG에 position: relative를 적용하여 플로우 밖으로 빼고 소스 텍스트 위에 오도록 하며, 컨테이너의 크기도 SVG와 동일하게 설정하여 SVG가 여전히 텍스트 안에 있는 것처럼 보이게 하려 합니다. 또한 소스 텍스트에는 opacity: 0을 적용하고 크기를 매우 작게 하여 SVG를 벗어나는 경우에도 보이지 않도록 하려 합니다.

더 쉬운 방법이 있는지 궁금합니다. 제가 시도하고 있는 방식은 모든 게시물을 다시 처리해야 하며, 이렇게까지 해야 할 것 같지 않은 문제에 대해 다소 어설픈 해결책처럼 느껴지기 때문입니다.

수정: addTagDecorateCallback() 함수의 코드를 살펴본 결과, 콜백에서 텍스트를 반환하여 사용될 텍스트를 대체할 수 있는 것 같습니다. 이는 제가 시도했던 어설픈 시스템(매우 잘 동작하지 않던)을 다뤄야 할 필요가 없을 수 있으므로 훨씬 더 도움이 될 것입니다.

html 요소의 속성에 원본 텍스트를 저장해 두고, addTagDecorateCallback() 콜백 함수에서 해당 값을 반환하도록 하여 원본 텍스트를 가져오게 함으로써 문제를 해결할 수 있었습니다.

제가 어떤 방식으로 처리했는지 궁금한 분들을 위해 코드를 일부 공유합니다.

assets/javascripts/lib/discourse-markdown/snapblocks-discourse.js 파일에서 bbcode 태그를 초기화하는 부분에서, 나중에 이를 가져올 수 있도록 원본 텍스트를 snapblocks-source 속성에 저장하도록 수정했습니다.

assets\javascripts\discourse\initializers\snapblocks-discourse.js 파일에서는 인용(quoting)을 처리하는 코드를 추가했습니다.

// assets\javascripts\discourse\initializers\snapblocks-discourse.js

import {
  addBlockDecorateCallback, // 블록 bbcode 태그
  addTagDecorateCallback, // 인라인 bbcode 태그
} from "discourse/lib/to-markdown";

function initializeSnapblocks(api, siteSettings) {
  addTagDecorateCallback(function () {
    // this.element은 HTML 요소가 아닙니다.
    // 하지만 모든 html 속성을 포함하고 있습니다.
    const { attributes } = this.element;

    // 일반적으로 "class"가 자신의 클래스인지 확인해야 하지만,
    // 여기서는 "snapblocks-source"만 필요합니다.
    if (attributes["snapblocks-source"]) {
      let prefix = "[sb";

      // bbcode 태그에 속성을 추가합니다. (이 속성들은 요소의 속성으로도 저장됩니다.)
      const attrs = [
        "blockstyle",
        "wrap",
        "wrapsize",
        "zebra",
        "showspaces",
        "santa",
      ];
      for (const attr of attrs) {
        if (attributes[attr]) {
          prefix += ` ${attr}=${attributes[attr]}`;
        }
      }

      prefix += "]";

      this.prefix = prefix;
      this.suffix = "[/sb]";
      // 텍스트를 반환하면, 선택된 텍스트 대신 이 값이 사용됩니다.
      return attributes["snapblocks-source"];
    }
  });
}

블록 bbcode 태그의 경우에도 동일한 방식이며, addBlockDecorateCallback()을 사용하도록 변경하면 됩니다.

불행히도 이전 snapblocks 스니펫을 인용할 수 있도록 하려면 게시물을 다시 재처리(rebake)해야 합니다. 또한 svg 위의 텍스트를 선택하는 경우는 처리하지 않으며, svg 텍스트 앞뒤로 일부 텍스트를 함께 선택하고 svg 텍스트의 일부도 선택해야만 작동합니다.