
Markdown 에디터: Gravity UI로 구축한 WYSIWYG 및 마크업 에디터
Markdown 에디터: Gravity UI로 구축한 WYSIWYG 및 마크업 에디터

TL;DR
- WYSIWYG 모드와 markdown 마크업 모드를 동시에 사용할 수 있습니다(프리뷰 및 분할 화면 지원).
- 기본 제공(out of the box) 블록을 많이 지원합니다.
- 기능 확장이 가능합니다 — WYSIWYG 모드에 확장(extension) 시스템이 있습니다.
- React 애플리케이션에서 사용하도록 설계되었습니다.
- Gravity UI의 테마와 컴포넌트를 사용합니다.
- 완전히 오픈소스 기술로 구축되었습니다(ProseMirror, CodeMirror, markdown‑it, Diplodoc, Gravity UI).
- CommonMark 표준을 준수하며, 표준 markdown과 Yandex Flavored Markdown (YFM)을 지원합니다.
안녕하세요! 저는 세르게이 마흐나트킨이고, Yandex Cloud의 User Experience 부서에서 개발자로 일하고 있습니다. 작년에 우리는 Gravity UI 디자인 시스템과 컴포넌트 라이브러리에 대해 소개한 바 있습니다. 그 이후 시스템은 여러 차례 업데이트되며 새로운 기능들이 추가되었고, 오늘은 문서 작업 과정을 크게 단순화해 주는 새로운 도구 — Markdown Editor에 대해 이야기해 보려 합니다.
UI가 만들어진 배경, 아키텍처적 특징, 통합 및 자체 확장 개발의 기술적 디테일을 다룬 뒤, 왜 이 모든 것이 오픈소스로 공개되어 있는지도 설명하겠습니다.
참고로, 아래에서 도구를 직접 써볼 수 있습니다:
왜 자체 Markdown Editor가 필요한가
사내 정보를 편리하게 저장하고 구조화할 수 있도록, 우리는 지식 베이스를 만들 수 있는 플랫폼 Wiki를 개발했습니다. 지식 베이스뿐만 아니라, 문서와 코드가 파일 저장소에서 (.md 파일로) 나란히 존재하는 Docs as Code 같은 문서화 방식도 발전시켜 왔습니다. 이렇게 해서 Diplodoс 플랫폼이 등장했습니다.
Wiki와 Diplodoc의 공통점은 두 플랫폼 모두 markdown의 방언인 Yandex Flavored Markdown (YFM)으로 동작한다는 점입니다. YFM은 Nebius, Bitrix, DoubleCloud, Mappable, Meteum에서 사용됩니다.
시간이 지나며 우리는 사용자가 텍스트를 작성·편집하는 과정을 서로 다르게 상상하는 두 그룹이 있다는 것을 알게 되었습니다. 한쪽은 MS Word, Confluence, Notion처럼 텍스트를 작성하면서 최종 결과를 바로 보는 것을 선호합니다. 다른 쪽은 마크업만 신뢰하며 markdown으로 페이지를 꾸미는 것을 선호합니다. WYSIWYG/markdown 모드에서 동시에 동작하는 유명한 라이브러리는 찾지 못했습니다. 예를 들어 Notion은 WYSIWYG만 제공하고, 코드 에디터에는 markdown과 프리뷰 모드만 있습니다.
우리는 두 모드(시각적 WYSIWYG 모드와 마크업 markdown 모드)를 동시에 지원하는 markdown 에디터를 개발했습니다. 첫 번째 모드에서는 툴바 아이콘으로 텍스트에 마크업을 적용할 수 있고, 두 번째 모드에서는 사용자가 markdown 코드를 직접 편집할 수 있습니다. 또한 어떤 모드로 작성했든, 문서는 md 파일로 저장됩니다.
아래는 버튼으로 텍스트를 서식 지정할 수 있는 시각적 에디터 화면입니다:

그리고 아래는 특수 기호로 서식 요소가 표현되는 마크업 모드입니다:

Gravity UI의 Markdown Editor 기능
에디터는 CommonMark 표준을 준수하며, 표준 markdown과 YFM을 지원합니다. 또한 GitHub Flavored Markdown 같은 다른 markdown 방언으로도 문법을 확장할 수 있도록 했습니다. 이때 에디터는 마크업 모드에서 WYSIWYG 모드로 전환할 수 있고, 문서는 md 마크업 또는 확장 md (예: YFM 사용 시) 형태로 저장됩니다.
확장(Extensions)
에디터에는 처음부터 많은 확장과 설정이 내장되어 있습니다. 예를 들어 Mermaid 다이어그램과 HTML 블록이 있습니다:


우리는 에디터의 코어가 쉽게 확장 가능하도록 만들려고 했습니다. 개발자는 다음과 같은 목적을 위해 자체 확장 또는 추가 기능을 만들 수 있습니다:
- 새 엔티티(블록 또는 텍스트 모디파이어) 추가;
- markdown 파서를 추가로 구성;
- 외부에서 에디터를 제어할 수 있는 action 추가;
- 예: 슬래시 입력 시 사용 가능한 명령 메뉴 표시 등 UI 기능 강화;
- 예: 이미지/파일을 삽입하고 스토리지로 업로드하는 등 현재 동작 수정.
다음은 우리가 Wiki를 위해 개발한 확장의 몇 가지 예입니다:
- 협업 편집 모드;

- draw.io 다이어그램 블록;

- YandexGPT 플러그인;


- 인클루드(includes);

- 섹션 구조;

- 편리한 그리드를 만들기 위한 섹션;

- 프리뷰가 있는 markdown 모드;

- 그리고 그 외에도 다양합니다.

마크업은 자동으로 변환될 수 있습니다. 마우스 없이 작업하는 것을 선호한다면, 시각적 에디터 모드에서는 텍스트 안에서 바로 마크업을 적용할 수 있는 특수 기호가 제공됩니다. 예를 들어 **는 WYSIWYG 모드에서 텍스트를 굵게 전환합니다. 이러한 기호로 텍스트를 서식 지정하고, 인라인/블록 코드를 만들 수 있습니다.
또한 / 기호를 입력해 확장 메뉴를 호출할 수도 있습니다.

프리셋(Presets)
에디터는 프로젝트마다 툴바를 개별적으로 구성할 수 있지만, 여러 가지 готовые 구성(프리셋)도 함께 제공합니다.
프리셋이 없는 에디터:

CommonMark 프리셋은 굵게, 기울임, 제목, 목록, 링크, 인용, 코드 블록 등 표준 markdown 요소를 지원합니다.

기본 프리셋에서는 취소선 텍스트가 추가되고, 셀에 텍스트만 들어갈 수 있는 표도 제공됩니다. 이 프리셋은 표준 markdown-it에 해당합니다.

앞서 말했듯 에디터는 YFM도 지원하므로 Diplodoc과도 매우 잘 통합됩니다. YFM 프리셋에서는 더 많은 요소가 추가됩니다: 강화된 표, 파일/이미지 삽입, 체크박스, 컷(cut), 탭, 고정폭 글꼴.

전체 프리셋에는 더 많은 요소가 포함됩니다.

아키텍처
WYSIWYG 모드의 기반은 잘 알려진 ProseMirror 라이브러리이며, 마크업 편집에는 CodeMirror를 사용합니다. ProseMirror는 서식이 있는 편집을 지원하고, CodeMirror는 마크업되지 않은 텍스트를 다뤄야 하는 상황에 적합합니다.
우리는 이 라이브러리들을 선택했는데, 동일한 저자가 개발했고 아키텍처와 구현 방식이 일관되며, 큰 커뮤니티의 지원을 받고, 다양한 에디터에서 사용되고, 텍스트 처리에 최적화되어 있기 때문입니다. 예를 들면 문서 변경을 적용하는 트랜잭션 시스템, 뷰를 위한 데코레이션, DOM 가상화, 여러 프로그래밍 언어 문법 지원 등이 있습니다.
통합
우리 에디터는 React 훅으로 쉽게 연결할 수 있습니다:
import React from 'react';
import {useMarkdownEditor, MarkdownEditorView} from '@gravity-ui/markdown-editor';
import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';
function Editor({onSubmit}) {
const editor = useMarkdownEditor({allowHTML: false});
React.useEffect(() => {
function submitHandler() {
// Serialize current content to markdown markup
const value = editor.getValue();
onSubmit(value);
}
editor.on('submit', submitHandler);
return () => {
editor.off('submit', submitHandler);
};
}, [onSubmit]);
return <MarkdownEditorView stickyToolbar autofocus toaster={toaster} editor={editor} />;
}
우리는 GravityUI의 uikit 라이브러리 컴포넌트를 사용합니다. 이는 전체 UI가 일관되고 단일 스타일 가이드를 준수하도록 보장합니다. 또한 이러한 컴포넌트 사용은 사용자에게 높은 수준의 일관성과 친숙함(인지 가능성)을 제공해 에디터 사용을 더욱 편리하게 만들어 줍니다.
React 애플리케이션에 에디터를 연결하는 방법과, 예를 들어 YandexGPT, Mermaid, LaTeX 같은 다양한 확장을 연결하는 방법에 대한 자세한 가이드도 준비되어 있습니다.
자체(커스텀) 확장 통합
에디터에는 이미 여러 추가 확장이 통합되어 있습니다. 하지만 이것만으로 부족하다면, 개발자는 WYSIWYG 모드에 자신만의 확장을 추가할 수 있습니다. 확장이 제공할 수 있는 것에 대해서는 위에서 이야기했습니다.
새 블록이나 텍스트 모디파이어를 추가하려면 먼저 configureMd 메서드로 내부 markdown-it 인스턴스를 구성해야 합니다. 그런 다음 addNode 또는 addMark 메서드로 새 엔티티를 등록해야 하며, 엔티티 이름과 함께 콜백 함수를 전달합니다. 콜백은 반드시 3개의 필수 필드를 가진 객체를 반환해야 합니다:
import insPlugin from 'markdown-it-ins';
export const underlineMarkName = 'ins';
export const UnderlineSpecs: ExtensionAuto = (builder) => {
builder
.configureMd((md) => md.use(insPlugin))
.addMark(underlineMarkName, () => ({
spec: {
parseDOM: [{tag: 'ins'}, {tag: 'u'}],
toDOM() {
return ['ins'];
},
},
toMd: {open: '++', close: '++', mixable: true, expelEnclosingWhitespace: true},
fromMd: {tokenSpec: {name: underlineMarkName, type: 'mark'}},
}));
};
spec— ProseMirror용 스펙(specification);fromMd— markdown 마크업을 ProseMirror 내부 표현으로 파싱하기 위한 설정;toMd— 엔티티를 markdown 마크업으로 직렬화(serialize)하기 위한 설정.
예를 들어 아래는 밑줄 텍스트 확장의 설정입니다. addAction 메서드로 action을 추가해 확장할 수 있습니다:
import {toggleMark} from 'prosemirror-commands';
const undAction = 'underline';
builder
.addAction(undAction, ({schema}) => ({
isActive: (state) => Boolean(isMarkActive(state, markType)),
isEnable: toggleMark(underlineType(schema)),
run: toggleMark(underlineType(schema)),
})
)
이런 action은 다음과 같이 코드에서 호출할 수 있습니다:
// editor – useMarkdownEditor 호출 결과로 얻은 에디터 인스턴스
editor.actions.underline.run(),
문서에서 새 확장을 만드는 전체 가이드를 확인할 수 있습니다.
우리는 에디터의 활용 범위를 지속적으로 넓혀 가고 있습니다. 현재는 VS Code용 플러그인을 개발 중인데, 이를 통해 VS Code 안에서 md 파일을 편리한 WYSIWYG 모드로 편집할 수 있게 됩니다. 또한 완전한 모바일 모드도 추가할 계획입니다. 이렇게 되면 사용자는 휴대폰만으로도 우리 에디터에서 작업할 수 있습니다.
우리 에디터는 단번에 만들어진 것이 아닙니다. 경험과 지식이 축적된 결과입니다. 우리는 에디터가 ProseMirror, CodeMirror, markdown-it 같은 신뢰할 수 있고 검증된 도구를 포함해, 오픈소스 제품을 기반으로 완전히 구축되었다는 점을 자랑스럽게 생각합니다. 또한 Diplodoc과 Gravity UI 같은 우리의 자체 개발도 포함됩니다.
여러분도 에디터 발전에 언제든 기여할 수 있습니다: Pull Request 만들기 또는 Issues 섹션에 나열된 현재 문제 해결을 도와주세요. 여러분의 지원과 새로운 관점이 에디터를 더 좋게 만드는 데 큰 도움이 됩니다. 그리고 프로젝트가 유용하다고 생각하신다면, GitHub 저장소에 별(star)도 부탁드립니다. 큰 힘이 됩니다 :)

세르게이 마흐나트킨
인터페이스 개발자