[React] Toast UI editor에서 Markdown 커스텀 문법 구현

조수현·2025년 11월 23일

서론

개발하다 마주친 요청사항을 반영하기 위한 여정입니다

참고

Toast UI Editor 기본

국내 회사인 nhn에서 개발한 에디터 라이브러리

  • 우선 제가 구현할 에디터는 아래와 같은 요청사항을 만족해야 했음

요청사항

  • 마크다운 문법 가능
  • 이미지 업로드 가능
  • 본문 글씨를 버튼으로 만드는 문법 추가
  • 해외 라이브러리 선호 (디자인이 국내스럽지 않아야 함)

라이브러리 선택 과정

  • 리액트 에디터를 검색하면 제일 많이 나오는 게 toast ui editor인데 국내 라이브러리라는 이유로 처음엔 제외
  • editorjs로 적용하다가 마크다운이 주로 사용될 것 같아 마크다운 에디터가 잘 되어있는 라이브러리를 고르기로 결정
  • @uiw/react-md-editor, mdeditor 이렇게 후보로 있었다
  • 그러다 피그마에 구현된 디자인 자체가 라이브러리 본래의 디자인을 다 뜯어 고쳐야하기 떄문에 국내 라이브러리인 toast ui editor 사용 결정,,~
  • 라이브러리 마지막 업데이트가 23년도인 것이 조금 걸리지만,,에디터가 만들어 놓고 더 업데이트 할 게 없긴 하겠다 싶어서 선택했다
  • 사용 빈도수가 많아 참고 자료가 많기도 하고 국내 라이브러리라 접근이 쉽다는 장점이 있고
  • 무엇보다 원하는 기능이 다 추가 라이브러리 없이 된다는 점!

커스텀 문법 적용

  • 제일 중요한 기능이면서도 구현을 어떻게 해야하나 고민했던 건 본문 속 글자로 버튼을 만드는 기능
  • 결론적으로 커스텀 문법을 만드는 기능은 따로 없다
  • 다만 커스텀 플러그인을 직접 만들어서 사용해야한다
    참고 링크

요구사항 구체적인 예시

  • 마크다운으로 ${안녕}하세요 라고 쓰면

  • 글에서 안녕하세요로 노출되고

  • 안녕이라는 글자는 버튼이 되어야 한다

  • 커스텀 문법을 어떻게 구현하지 고민하면서 프로젝트 내 다른 기능을 먼저 만들었는데 본격적으로 개발하기 시작하니 생각보단 간단해 보였다 역시 고민보다 고

  • 참고 자료로 올렸던 링크에 이미 예시가 있어서 적용하기 어렵지 않아 보였다...

기본 구조


// 1. 플러그인 함수 생성
function plugin(노드정보){
	// 전처리 코드 작성
  
  return{
  	toHTMLRenderers,
  	toMarkdownRenderers,
  	//...
  }
}

return (
	<Editor
  		//...
  		plugins={[plugin]} // 2. 플러그인 적용
  	/>
)

커스텀 문법 적용 코드

const plugin = () => {
  return {
    // Markdown-it 규칙 추가
    toHTMLRenderer: {
      // HTML 렌더러
      span(node) {
        if (node.__customToken) {
          const value = node.__customToken;
          return {
            type: 'html',
            content: `<button type="button">${value}</button>`,
          };
        }
      },
    },

    // Markdown 파서
    markdownParser:  (markdownIt: any) => {
      // 커스텀 인라인 rule
      markdownIt.inline.ruler.after('escape', 'custom_token', (state, silent) => {
        const src = state.src.slice(state.pos);

        const match = src.match(/^\$\$\{([^}]+)\}/);
        if (!match) return false;
        if (silent) return false;

        const full = match[0];      // $${안녕}
        const content = match[1];   // 안녕

        const token = state.push('span_open', 'span', 1);
        token.__customToken = content;

        const text = state.push('text', '', 0);
        text.content = content;

        state.pos += full.length;

        state.push('span_close', 'span', -1);
        return true;
      });
    },
  };
}

화면

마무리

역시 기본 개념도 공부하기 좋지만 개념 정리 노트를 블로그에 적는 느낌이라면
프로젝트를 진행하면서 쓰는 글이 더 블로그 답고 재밌는 것 같다

profile
프론트엔드 개발 블로그

0개의 댓글