Skip to content

@toast‐ui editor 이슈 (with Next.js 14 app router)

DaeSung Oh edited this page Jan 18, 2024 · 5 revisions

설치 이후 @toast-ui/editor를 import 하려고 할 경우 모듈을 찾을 수 없다는 에러

tsconfig.json에 직접 타입을 연결하여 해결

"paths": {
      "@toast-ui/editor": ["./node_modules/@toast-ui/editor/types/index.d.ts"],
      "@toast-ui/editor/types": ["./node_modules/@toast-ui/editor/types"]
    }
  • npm에서는 typescript를 지원하는 것으로 나오고, 실제 node_modules에서 확인해보면 관련 타입들도 있으나 적용은 되지 않음
  • 프로젝트 node_modules 폴더에서 @toast-ui/editor 의 package.json exports 를 확인해보니 import, require는 있는데 types는 작성되있지 않아서 생긴 문제로 보임...

navigator 를 찾을 수 없다는 에러

next/dynamic (ssr: false) 으로 동적으로 import하여 해결

  • 처음 pre-render 할 때 서버 환경이니 클라이언트 환경의 window 객체를 참조할 수 없어서 발생한 에러라고 추측
  • 에러 이후 Next.js 에서 자동으로 클라이언트로 전환되어 결과적으로 렌더링은 되는 것 같지만, 에러 없이 정상적으로 렌더링 해주고 싶어서 적용함
  • 서버-> 클라이언트 환경으로 전환되어 렌더링 되기 이전까지는 설정하지 않을 경우 아무것도 렌더링 하지 않기 때문에, 현재 프로젝트에서는 관련된 loading 컴포넌트를 간단하게 적용함(없다가 갑자기 렌더링되어 나타나는 것으로 인한 Layout Shift는 유저경험에 좋지 않을 것 같아서 적용함) / dynamic import 시 외부 서버와 통신해서 작업하는 부분은 없고, 설치된 라이브러리 모듈을 불러오는 것이기 때문에 에러는 없을 것이라 생각되어 에러일 경우 렌더링은 처리해주지 않음

dynamic 컴포넌트에 ref 전달 되지 않음

감싸는 wrapper 컴포넌트를 만들고 해당 컴포넌트에 forwardRef에 전달된 ref를 전달하여 해결

인덱스 페이지에서 css import 할 경우 아이콘 이미지가 나오지 않는 문제

  • 근본적으로 해결하는 방법을 생각하느라, 다른 이슈들 보다 원인을 분석하고 해결하는데 시간이 더 소모 됨
  • 버튼의 base64이미지를 다운받고 에셋에 추가해서 사용

진입점 파일을 만들고 해당 진입점에서 import 하여 해결 / 각각 사용하는 모듈(파일)에서 import 할 수도 있겠지만 에디터가 렌더링 되기 전에 결과적으로 한번만 css 가 적용되면 되기 때문에 진입점(index.tsx)을 활용함

  • layout.tsx나 global.css 에서 import 할 경우 dev 모드에서는 정상적으로 아이콘이 나오지만, build 후 실행할 때에는 아이콘이 보이지 않음
  • toolbar요소 background 속성에 base64 인코딩된 주소가 있고 개발자 도구에서 해당 주소를 클릭시 확인도 되는데, 화면에는 나오지 않음
  • 정확한 원인은 모르겠지만, 이미지 스플릿팅 기법과 연관이 있는 것 같음
  • tailwindcss의 preflight 에서 button 요소에 background: none 이 적용되어 배경이미지가 적용이 되지 않았으며, 배경이미지가 적용되지 않기 때문에 toast ui에서 적용한 이미지 스플리팅 css 가 정상적으로 동작하지 않아 아이콘 이미지가 나오지 않은 것 => global.css에 css 적용하여 해결
    • 해결하는데 사용된 개념은 css inherit(상속)
    • preflight 이후 toast ui 의 button 기본 background를 상속받도록 override 하고, 툴바의 부모요소의 background를 툴바 아이콘 이미지로 지정하지만 버튼에서만 필요하므로 사이즈를 0으로 하여 버튼에서만 노출되도록 설정
/*toast ui button 속성 재정의*/
.toastui-editor-defaultUI [type="button"],
.toastui-editor-defaultUI [type="reset"],
.toastui-editor-defaultUI [type="submit"],
.toastui-editor-defaultUI button {
  background-image: inherit;
}
/*toast ui button icon 이미지 주소*/
.toastui-editor-toolbar-group {
  background-image: url("../assets/images/toolbarBackgrond.png");
  background-size: 0;
}

toolbar 가 넘칠경우 에디터 rootElement 에 overflow: visible 이 적용된 것 같이 렌더링 되어 화면 넓이를 초과할 경우 스크롤이 되는 현상 (toolbar wrapper를 뚫고 옆에 렌더링)

toolbar css 클래스에 flex-wrap: wrap !important 를 작성하여 해결

.toastui-editor-defaultUI-toolbar {
  flex-wrap: wrap !important;
}
  • 툴바가 flex로 되어 있어서 넘칠 경우 wrap을 해주도록 적용해주니, 넘치는 툴바 요소들에 대해서는 ... 버튼을 클릭하면 볼 수 있도록 렌더링 됨

next/dynamic 과 wrapper, 그리고 index 를 적용한 코드 (댓글 에디터, 질문 에디터 등 사용하는 곳에서는 진입점의 ToastUiEditor 를 확장하여 사용하면 될 것 같음, Viewer도 동일하게 적용하면 될 것 같음)

EditorWrapper.tsx

"use client"

import { Editor, EditorProps } from "@toast-ui/react-editor"

interface EditorWrapperProps extends EditorProps {
  forwardedRef: React.ForwardedRef<Editor>
}

function EditorWrapper({ forwardedRef, ...props }: EditorWrapperProps) {
  return <Editor ref={forwardedRef} {...props} />
}

export default EditorWrapper

Editor.tsx

"use client"

import { Editor as ToastEditor, EditorProps } from "@toast-ui/react-editor"
import dynamic from "next/dynamic"
import { forwardRef } from "react"
import Skeleton from "react-loading-skeleton"

const EditorWrapper = dynamic(() => import("./EditorWrapper"), {
  ssr: false,
  loading(loadingProps) {
    return (
      <div className="relative h-[300px]">
        <Skeleton height={"100%"} baseColor="#eee" />
        <div className="absolute top-0 left-0 flex justify-center items-center w-full z-[1] h-full">
          에디터 로딩 중
        </div>
      </div>
    )
  },
})

const Editor = forwardRef<ToastEditor, EditorProps>(
  function ToastUiWithRefEditor(props, ref) {
    return <EditorWrapper forwardedRef={ref} {...props} />
  },
)

export default Editor

index.tsx

import "@toast-ui/editor/dist/toastui-editor.css"

import ToastUiEditor from "./editor/Editor"

export { ToastUiEditor }

dropdown toolbar & tooltip

dropdown 툴바 관련된 css 수정하여 해결

.toastui-editor-dropdown-toolbar {
  max-width: 100% !important;
  flex-wrap: wrap;
  height: max-content !important;
}

.toastui-editor-dropdown-toolbar .toastui-editor-toolbar-group {
  flex-wrap: wrap;
}

tailwind css로 필요한 에디터에서만 wrapper에 독립적으로 적용

<div className={"... [&_.toastui-editor-defaultUI-toolbar]:!flex-wrap [&_.toastui-editor-dropdown-toolbar]:!max-w-full [&_.toastui-editor-dropdown-toolbar]:!h-max [&_.toastui-editor-dropdown-toolbar]:flex-wrap [&_.toastui-editor-toolbar-group]:flex-wrap ..."}>
 {...}
</div>
  • wrap을 적용하여 넘칠경우 ... (more) 버튼이 나오게 하는 데 성공은 했으나 이후 기기 width에 따라 more 툴바에서 렌더링이 이상한 것을 발견, 라이브러리의 css는 480px 이하일 경우 max-width: none 으로 작성되어 있고 right를 통해 요소를 배치하다보니 앞의 일부가 잘려서 렌더링됨
  • overflow: auto를 통해 스크롤을 줄 경우 육안으로 보았을 때에는 스크롤해서 볼 수 있는 것 같지만, 오른쪽으로 스크롤 할 경우 tooltip 의 left가 싱크가 맞지 않아서 해당 버튼 보다 더 오른쪽으로 tooltip이 렌더링됨
  • 기존 툴바에 커스텀한 툴바 버튼들을 커스텀하게 렌더링 할 수 있지만, 툴바 자체를 커스텀해서 배치할 수 없음(기기 width에 따라 more버튼으로 툴바가 나오도록 하는 동작을 막을 방법이 없다고 생각됨)
  • 좌우 스크롤을 통해 높이를 많이 차지하지 않는 것을 지향하고 싶으나, 지금 현재는 오른쪽으로 스크롤 시 tootip 의 position을 어떻게 동기화되게 맞춰줄 수 있는 지 생각나지 않음
  • 수정된 css를 적용할 경우 높이가 늘어나며 여러줄을 차지하게 되지만, 현재로서는 라이브러리의 기존 동작을 크게 수정하지 않으면서 대응할 수 있는 최선의 방법이라고 생각됨...

image

initialValue를 주지 않을 경우 Write Preview {placeholder 문자열} 이 설정됨 , spellcheck 가 적용 됨

EditorWrapper 에서 useLayoutEffect를 적용하여 해결

  • <br> 등 태그를 입력해도 빨간 밑줄이 나옴, 적용하지 않는 것이 좋을 것 같다고 판단되어 rootElement의 spellcheck도 false로 설정 함
function EditorWrapper({
  forwardedRef,
  mdTabVisible = true,
  ...props
}: EditorWrapperProps) {
  /* ... */
  useLayoutEffect(() => {
    const editorRef = forwardedRef as MutableRefObject<Editor | null>

    if (editorRef.current) {
      editorRef.current.getRootElement().spellcheck = false

      editorRef.current
        .getInstance()
        .setMarkdown(editorRef.current.props.initialValue ?? "")
    }
  }, []) /* eslint-disable-line */
  /* ... */

autofocus false로 설정해도 자동 focus 되는 현상

  • useEffect를 통해 autofocus가 아닌 경우 에디터를 blur 함
  • initial value를 설정하기 위한 useLayoutEffect 로 인해 setMarkdown으로 에디터 상태가 업데이트되면서 자동으로 포커스가 생겼던 것 같음

EditorWrapper.tsx

 // ...
useEffect(() => {
    if (!autofocus) {
      setTimeout(() => {
        editorRef.current?.getInstance().blur()
      }, 0)
    }
  }, [])