on
[NEXT] TOAST Editor 사용하기
버전 정리
Next.js: 13.4.10pages Router를 사용하였다.
@toast-ui/editor- 3.2.2@toast-ui/react-editor- 3.2.3
대략적인 폴더 구조
src
┣ components
┃ ┣ newpost
┃ ┃ ┗ PostEditor.tsx
┣ pages
┃ ┣ newpost
┃ ┃ ┗ index.tsx
┃ ┗ index.tsx
사용법 익히기
Toast Editor는 NHN이 만든 오픈소스 에디터이다.
그래서 한국에서 많이 사용하고, 검색 시에도 한국어 결과가 많이 나오는 편이었다.
나는 Next.js를 사용하고 있기 때문에 React 버전의 사용법을 찾아보았다.
위의 링크에서 소개한 사용법은 아래와 같았다.
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}
/>
);
위와 같이 Editor를 import하고 컴포넌트에 필요한 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>타입을 상속받고 getInstance와 getRootElement라는 메서드를 가지고 있었다.
props를 알아보기 위해서 이번엔 상속받은 EditorProps를 살펴보았다.
// node_modules/@toast-ui/react-editor/index.d.ts
export type EditorProps = Omit<EditorOptions, 'el'> & Partial<EventMapping>;
-
Omit<EditorOptions, 'el'>는EditorOptions에서el을 제외한 나머지 속성들을 상속받는다는 뜻이다. -
Partial<EventMapping>는EventMapping의 속성들을 선택적으로 상속받는다는 뜻이다. -
그리고 위의 두 타입을 합쳐서
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;
}
엄청나게 많은 옵션들이 있다.
하지만 다행히 문서로 잘 정리가 되어 있어서 각 옵션들이 어떤 역할을 하는지 확인할 수 있었다.
위의 링크를 보면서 나에게 필요한 옵션들이 무엇인지 알 수 있었다.
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,
});
정상적으로 잘 나오는 것을 확인할 수 있었다.