Skip to content

Toolbar에 연동하기

외부 버튼 활용하기 가이드 절차에 따라 react-quill-new 패키지 3.3.3 버전을 설치해 주세요.

예제 코드 소스

React Quill 패키지의 테마 스타일 파일과 컴포넌트 파일을 함께 import하는 것, 잊지 마세요!

  1. 가이드 - 더 나아가기 - 커스텀 훅 만들기를 참고하여 useEditorHandlers() 훅을 생성합니다.

  2. EditorToolbar.tsx 파일을 생성하고, 원하는 대로 ToolBar를 구성합니다. 단, 사전에 설정되지 않은 ToolBar 항목은 반드시 toolbar 속성에 추가해야 합니다.

tsx
<span className="ql-formats">
  <button className="ql-video-thumbnail !flex !w-auto items-center">
    <AiOutlineVideoCameraAdd />
    <span className="ml-1 text-xs font-bold text-primaryDark underline underline-offset-2 hover:text-indigo-800">
      동영상에서 썸네일 가져오기
    </span>
  </button>
</span>
  1. <ReactQuill /> 컴포넌트와 2번에서 만든 <EditorToolbar /> 컴포넌트를 삽입합니다. formats, modules를 지정하여 React Video Thumbnail Extractor와 연동합니다. useEditorHandlers() 훅의 openModal 메서드는 2번에서 만든 Toolbar 버튼을 클릭했을 때, 실행됩니다.

  2. modules를 useMemo() 훅으로 감싸서 openModal 헨들러로 인해 발생하는 에디터의 불필요한 리렌더링을 방지합니다.

tsx
import { EditorToolbar, formats } from 'EditorToolbar';

// ...중략
const modules = useMemo(
  () => ({
    toolbar: {
      // EditorToolbar.tsx 파일의 root Div id를 삽입
      container: '#quill-toolbar',
      // handler 이름은 EditorToolbar.tsx 파일의 button className에서 ql-을 제외한 값 삽입
      handlers: {
        'video-thumbnail': openModal
      }
    }
  }),
  []
);

// ...중략
<EditorToolbar />
<ReactQuill
  theme="snow"
  value={editorContent}
  formats={formats}
  modules={modules}
  onChange={setEditorContent}
  placeholder="에디터 툴바에서 동영상에서 썸네일 가져오기를 선택해서 이미지를 삽입해 보세요."
/>

/react-quill/EditorToolbar.tsx

tsx
import { AiOutlineVideoCameraAdd } from 'react-icons/ai';

export const formats = [
  'header',
  'font',
  'size',
  'bold',
  'italic',
  'underline',
  'align',
  'strike',
  'script',
  'blockquote',
  'background',
  'list',
  'indent',
  'link',
  'image',
  'color',
  'code-block'
];

export const EditorToolbar = () => (
  <div id="quill-toolbar">
    <span className="ql-formats">
      <select className="ql-size" defaultValue="medium">
        <option value="extra-small">Size 1</option>
        <option value="small">Size 2</option>
        <option value="medium">Size 3</option>
        <option value="large">Size 4</option>
      </select>
      <select className="ql-header" defaultValue="3">
        <option value="1">Heading</option>
        <option value="2">Subheading</option>
        <option value="3">Normal</option>
      </select>
    </span>
    <span className="ql-formats">
      <button className="ql-bold" />
      <button className="ql-italic" />
      <button className="ql-underline" />
      <button className="ql-strike" />
      <button className="ql-list" value="ordered" />
    </span>
    <span className="ql-formats">
      <select className="ql-align" />
      <select className="ql-color" />
      <select className="ql-background" />
    </span>
    {/* Custom Button 추가 */}
    <span className="ql-formats">
      <button className="ql-video-thumbnail !flex !w-auto items-center">
        <AiOutlineVideoCameraAdd />
        <span className="ml-1 text-xs font-bold text-primaryDark underline underline-offset-2 hover:text-indigo-800">
          동영상에서 썸네일 가져오기
        </span>
      </button>
    </span>
  </div>
);

/react-quill/DemoAdvanced.tsx

tsx
import 'react-quill-new/dist/quill.snow.css';
import './style.css';
import '@devskyui/react-video-thumbnail-extractor/style.css';

import type { Thumbnail } from '@devskyui/react-video-thumbnail-extractor';
import { VideoThumbnailExtractorModal } from '@devskyui/react-video-thumbnail-extractor';
import { useMemo } from 'react';
import ReactQuill from 'react-quill-new';

import { useEditorHandlers } from '@/hooks';

import { EditorToolbar, formats } from './';

export const DemoAdvanced = () => {
  const {
    editorContent,
    setEditorContent,
    isOpen,
    openModal,
    closeModal,
    handleAddedThumbnail,
    handleRemovedThumbnail,
    handleError
  } = useEditorHandlers();

  const modules = useMemo(
    () => ({
      toolbar: {
        // EditorToolbar.tsx 파일의 root Div id를 삽입
        container: '#quill-toolbar',
        // handler 이름은 EditorToolbar.tsx 파일의 button className에서 ql-을 제외한 값 삽입
        handlers: {
          'video-thumbnail': openModal
        }
      }
    }),
    []
  );

  const handleInsertThumbnails = (thumbnails: Thumbnail[]) => {
    const thumbnailUrls = thumbnails.map((thumbnail) => thumbnail.base64Url);

    if (thumbnails.length < 1) return;
    const imageTags = thumbnailUrls.map((url) => `<p><img src="${url}" /></p><p></p>`).join('');

    setEditorContent((prevContent) => prevContent + imageTags);
  };

  return (
    <>
      <EditorToolbar />
      <ReactQuill
        theme="snow"
        value={editorContent}
        formats={formats}
        modules={modules}
        onChange={setEditorContent}
        placeholder="에디터 툴바에서 동영상에서 썸네일 가져오기를 선택해서 이미지를 삽입해 보세요."
      />

      <VideoThumbnailExtractorModal
        isOpen={isOpen}
        maxFileSize={300}
        maxThumbnailLength={4}
        confirmButtonLabel="에디터에 썸네일 삽입하기"
        cancelButtonLabel="취소"
        onThumbnailAdded={handleAddedThumbnail}
        onThumbnailRemoved={handleRemovedThumbnail}
        onError={handleError}
        onSuccess={(thumbnails: Thumbnail[]) => {
          handleInsertThumbnails(thumbnails);
          closeModal();
        }}
        onCancel={() => closeModal()}
      />
    </>
  );
};