[NEXT] TOAST Editor 사용하기

버전 정리

대략적인 폴더 구조

src
 ┣ components
 ┃ ┣ newpost
 ┃ ┃ ┗ PostEditor.tsx
 ┣ pages
 ┃ ┣ newpost
 ┃ ┃ ┗ index.tsx
 ┃ ┗ index.tsx

사용법 익히기

🔗 Toast Editor 소개 바로가기 🔗

Toast EditorNHN이 만든 오픈소스 에디터이다.

그래서 한국에서 많이 사용하고, 검색 시에도 한국어 결과가 많이 나오는 편이었다.

나는 Next.js를 사용하고 있기 때문에 React 버전의 사용법을 찾아보았다.

🔗 @toast-ui/react-editor 깃허브 바로가기 🔗

위의 링크에서 소개한 사용법은 아래와 같았다.

import '@toast-ui/editor/dist/toastui-editor.css';

import { Editor } from '@toast-ui/react-editor';

const MyComponent = () => (
  <Editor
    initialValue='hello react editor world!'
    previewStyle='vertical'
    height='600px'
    initialEditType='markdown'
    useCommandShortcut={true}
  />
);

위와 같이 Editorimport하고 컴포넌트에 필요한 props를 전달해 주면 된다.

그렇다면 Editor컴포넌트는 어떤 props를 가지고 있을까?

Editor 컴포넌트 살펴보기

// node_modules/@toast-ui/react-editor/index.d.ts
export class Editor extends Component<EditorProps> {
  getInstance(): ToastuiEditor;

  getRootElement(): HTMLElement;
}

Editor 컴포넌트는 Component<EditorProps>타입을 상속받고 getInstancegetRootElement라는 메서드를 가지고 있었다.

props를 알아보기 위해서 이번엔 상속받은 EditorProps를 살펴보았다.

// node_modules/@toast-ui/react-editor/index.d.ts
export type EditorProps = Omit<EditorOptions, 'el'> & Partial<EventMapping>;
  1. Omit<EditorOptions, 'el'>EditorOptions에서 el을 제외한 나머지 속성들을 상속받는다는 뜻이다.

  2. Partial<EventMapping>EventMapping의 속성들을 선택적으로 상속받는다는 뜻이다.

  3. 그리고 위의 두 타입을 합쳐서 EditorProps를 만들었다.

이중에 EditorOptions를 먼저 살펴보았다.

EditorOptions

// node_modules/@toast-ui/editor/types/editor.d.ts
export interface EditorOptions {
  el: HTMLElement; // 이건 제외하고 상속을 받는다.
  height?: string;
  minHeight?: string;
  initialValue?: string;
  previewStyle?: PreviewStyle;
  initialEditType?: EditorType;
  events?: EventMap;
  hooks?: HookMap;
  language?: string;
  useCommandShortcut?: boolean;
  usageStatistics?: boolean;
  toolbarItems?: (string | ToolbarItemOptions)[][];
  hideModeSwitch?: boolean;
  plugins?: EditorPlugin[];
  extendedAutolinks?: ExtendedAutolinks;
  placeholder?: string;
  linkAttributes?: LinkAttributes;
  customHTMLRenderer?: CustomHTMLRenderer;
  customMarkdownRenderer?: ToMdConvertorMap;
  referenceDefinition?: boolean;
  customHTMLSanitizer?: Sanitizer;
  previewHighlight?: boolean;
  frontMatter?: boolean;
  widgetRules?: WidgetRule[];
  theme?: string;
  autofocus?: boolean;
  viewer?: boolean;
}

엄청나게 많은 옵션들이 있다.

하지만 다행히 문서로 잘 정리가 되어 있어서 각 옵션들이 어떤 역할을 하는지 확인할 수 있었다.

🔗 EditorOptions 문서 바로가기 🔗

위의 링크를 보면서 나에게 필요한 옵션들이 무엇인지 알 수 있었다.

EventMapping

// node_modules/@toast-ui/react-editor/index.d.ts
export interface EventMapping {
  onLoad: EventMap['load'];
  onChange: EventMap['change'];
  onCaretChange: EventMap['caretChange'];
  onFocus: EventMap['focus'];
  onBlur: EventMap['blur'];
  onKeydown: EventMap['keydown'];
  onKeyup: EventMap['keyup'];
  onBeforePreviewRender: EventMap['beforePreviewRender'];
  onBeforeConvertWysiwygToMarkdown: EventMap['beforeConvertWysiwygToMarkdown'];
}

export interface EventMap {
  load?: (param: Editor) => void;
  change?: (editorType: EditorType) => void;
  caretChange?: (editorType: EditorType) => void;
  focus?: (editorType: EditorType) => void;
  blur?: (editorType: EditorType) => void;
  keydown?: (editorType: EditorType, ev: KeyboardEvent) => void;
  keyup?: (editorType: EditorType, ev: KeyboardEvent) => void;
  beforePreviewRender?: (html: string) => string;
  beforeConvertWysiwygToMarkdown?: (markdownText: string) => string;
}

EventMapping이라는 단어 그대로 사용 가능한 이벤트 함수와 이벤트 함수의 파라미터 타입을 정의하고 맵핑한 것 이었다.

컴포넌트 만들기

글작성 페이지는 pages/newpost/index.tsx 경로에 만들려고 한다.

하지만 index.tsx 파일이 너무 커질것 같아 components/newpost/PostEditor.tsx파일에 PostEditor라는 컴포넌트를 별도로 만들고, index.tsx에서 import 하여 사용하려고 한다.

PostEditor 컴포넌트는 아래와 같이 작성했다.

// components/newpost/PostEditor.tsx
import { Editor } from '@toast-ui/react-editor';
import '@toast-ui/editor/dist/toastui-editor.css';
import '@toast-ui/editor/dist/i18n/ko-kr';

export default function PostEditor() {
  return (
    <Editor
      previewStyle='vertical'
      height='800px'
      initialEditType='markdown'
      placeholder='Write Something'
      hideModeSwitch={true}
      language='ko-KR'
    />
  );
}

그리고 pages/newpost/index.tsx에 import해서 사용하였다.

📌 Toast에디터는 ssr을 지원하지 않기 때문에 next/dynamic을 사용하여 ssr을 하지 않도록 설정해주었다. 아마 React로 작업하는 경우에는 사용할 필요가 없을 것 같다.

import dynamic from 'next/dynamic';

const PostEditor = dynamic(() => import('@/components/newpost/PostEditor'), {
  ssr: false,
});

스크린샷 2023-08-26 오전 2 56 31

정상적으로 잘 나오는 것을 확인할 수 있었다.