본문으로 바로가기

React + ckeditor v5 문서 정리

category React, Next, Redux/⚛ React.JS 2020. 7. 14. 02:36
 

React component - CKEditor 5 Documentation

Learn how to install, integrate and configure CKEditor 5 Builds and how to work with CKEditor 5 Framework, customize it, create your own plugins and custom editors, change the UI or even bring your own UI to the editor. API reference and examples included.

ckeditor.com

 

공식 문서에서 예시로 든 코드.

import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

<CKEditor

  editor={ClassicEditor}
  
  data="<p>Hello from CKEditor 5!</p>"
  
  onInit={(editor) => {
    console.log("Editor is ready to use!", editor);
  }}
  
  onChange={(event, editor) => {
    const data = editor.getData();
    console.log({ event, editor, data });
  }}
  
  onBlur={(event, editor) => {
    console.log("Blur.", editor);
  }}
  
  onFocus={(event, editor) => {
    console.log("Focus.", editor);
  }}
></CKEditor>

 

 

우선 CKEditor가 가장 큰 컴포넌트이고 속성으로 이를 컨트롤 할 수 있겠다.

 

🚨 주의

1. reset.css를 적용하셨나요? 그렇다면 해당 글의 제목, 일반 텍스트 등에 적용되는 css가 먹지 않습니다. 전부 일반 플레인한 텍스트가 되어 버립니다. 예외 처리를 잘 하시던가 reset.css를 포기합시다.

2. react에서는 플러그인을 설치할 경우 충돌 오류가 납니다. 쉽게 말해 underline을 기본 에디터에서 지원하지 않으므로 플러그인을 설치해여 연결해줘야 하는 데 그게 안된다는 것입니다. 이 경우 기존에 있는 빌드를 커스터마이징하는 방식으로 시작해야 합니다. 아래에 정리해두었습니다.

3. 커스터마이징하지 않고도 기능이 충실하게 지원되길 원하시면 v4를 쓰시는 것도 좋은 선택입니다..만 저는 가급적 v5를 권해드리고 싶습니다. 이미지 첨부 등 인터레티브한 류의 작동이 v5가 더 쉬운 편입니다.
https://ckeditor.com/docs/ckeditor4/latest/guide/dev_react.html

 

그 외 CKEditor의 컴포넌트의 속성은 다음과 같다.

 

Component properties

 

The <CKEditor> component supports the following properties:

  • editor (required) – 사용할 에디터 Editor로 대표적으로 ClassicEditor와 InlineEditor, BalloonEditor, DecoupledEditor가 존재 한다고 한다. 살펴보니 기본은 ClassicEditor이고 그 외에 드래그 했을 때 나타나는 BallonEditor 등 완전한 에디터가 아니라 부가 기능 식으로 작동하는 에디터도 존재한다.

(https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#available-builds)

 

npm i @ckeditor/ckeditor5-build-classic
npm i @ckeditor/ckeditor5-editor-inline

 

  • data – 최초 실행시 보여줄 문구로, html을 따옴표로 감싼 문자열로 전달합니다. See the Basic API guide. 개인적으로 data보다 config의 placeholder를 사용하는 것이 더 나아 보입니다. data면 실제로 입력된 값을 지워야 하기 때문입니다.
  • config – 에디터 설정입니다. See the Configuration guide. 뒤에 커스터마이징 하는 부분에서 살펴보겠습니다.
  • onInit – 데이터가 최초 실행되면 트리거 됩니다. editor를 파라미터로 받습니다.
onInit={(editor) => {
  console.log("Editor is ready to use!", editor);
}}

 

여기서 editor가 뭐가 있는 지 한 번 살펴볼까요?

해당 속성들의 자세한 설명은 아래 링크에 있습니다.

https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html

 

여러 중요한 값이 있겠지만 아무래도 getData 메서드가 제일 중요하겠지요?

 

 

  • disabled – A Boolean value. true로 해놓으면 수정 자체가 금지 됩니다. 설정해보니 아무 것도 누를 수가 없더군요
  • onChange – 내용이 입력되면 트리거 됩니다. See the editor.model.document#change:data event.
onChange={(event, editor) => {
  const data = editor.getData();
  console.log({ event, editor, data });
}}

살펴보니 event와 editor를 파라미터로 받고, 데이터의 getData를 통해서 입력한 값을 추출할 수 있겠군요.

getData는 editor에 입력된 글을 가져옵니다. 그러니까, 딱히 onChange에 국한된 메서드는 아닙니다.

 

 

  • onBlur – blurred 되었을 때 트리거 됩니다. blurred라는 것은 쉽게 말해서 데이터에서 focus 상태를 벗어나는 것입니다. 에디터가 아닌 다른 곳을 클릭하는 등의 이유로 발생할 수 있겠네요. 
onBlur={(event, editor) => {
  console.log("Blur.", editor);
}}

 

 

  • onFocus – onBlur와는 반대입니다. 에디터를 클릭해서 데이터가 활성화 되었을 때 트리거 됩니다. 
onFocus={(event, editor) => {
  //   console.log("Focus.", editor);
  console.log(editor.getData());
}}

 

  • onError – 에디터가 시작될 때, 에디터 자체를 불러오는 데 실패했을 경우 트리거 됩니다. It receives the error object as a parameter.

 

공식 문서에는 onChange, onBlur, onFocus의 콜백이 event와 editor 파라미터를 받는다고 명시하면서 안내하고 있습니다.

The editor events callbacks (onChange, onBlur, onFocus) receive two parameters:

  1. An EventInfo object.
  2. An Editor instance.

 

 

기본 CKEditor config 

 

CKEditor 5 builds 는 곧바로 사용할 수 있도록 빌트인 플러그인과 기본 설정 등이 이미 세팅된 상태입니다. 여러분들은 CKEditor 컴포넌트의 config 속성을 통해서 toolbar  원하는 에디터를 사용하기 위해 기존에 설치된 플러그인을 삭제, 할 수도 있습니다.

 

 

공시 문서에는 없지만 config 속성을 살펴보도록하겠습니다.

(https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/configuration.html)

(https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorconfig-EditorConfig.html)

 

우선 기본적으로 제공되는 툴바를 확인해보면 글자 크기 지원도 안하고 미리보기도 없습니다. 폰트 설정도 할 수 없군요

ckeditor에서 제공하는 툴바, 기능을 config에 조립하듯 만들어 놓으면 됩니다.

문제는 넣어도 아무 반응이 없는, 즉, 기본 빌드에서 지원하지 않는 속성이 많다는 것입니다.

<CKEditor
  editor={ClassicEditor}
  config={{
    // 여기에 config 입력
    toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList']
    placeholder: "글을 입력해보세요!",
  }}
></CKEditor>

 

 

기본적으로 제공하는 toolbar 외에 플러그인을 사용하는 것은 불가능합니다. 플러그인을 사용하고 싶다면 커스터마이징해야 합니다. (사실상 커스터마이징이 필수입니다.)

 

 

CKEditor를 커스터마이징하기 (소스 단계부터 editor를 구성하기)

 

툴바에 이것저것 넣어봤는데 Strikethough나 underline이 먹지 않더군요. 패키지를 설치해서 얹어봤지만 그러면 duplicate라며 에러가 납니다. 바닥부터 빌드할까 생각하던 중 The components (buttons, dropdowns, etc.) which can be used as toolbar items are defined in `editor.ui.componentFactory` 라고 하길래 확인해보기로 했습니다.

 

onInit에서 콘솔로 찍어보니 사용할 수 있는 toolbar는 다음과 같았습니다.

아, Strike가 없군요...

 

 

 

 

 

결국 플러그인을 설치하여 사용하고 싶다면 코더가 직접 에디터를 빌드해야 합니다.

새로운 플러그인을 더하여 바닥부터 새롭게 Editor를 빌드하는 방식에는 크게 2가지 방법이 있습니다.

 

  • 기존에 있는 빌드를 커스터마이징하기

    This option does not require any changes in your project’s configuration. You will create a new build somewhere next to your project and include it like you included one of the existing builds. Therefore, it is the easiest way to add missing features. Read more about this method in Installing plugins.

  • 소스 단계부터 editor를 구성하기

    In this approach you will include CKEditor 5 built from source — so you will choose the editor creator you want and the list of plugins, etc. It is more powerful and creates a tighter integration between your application and the WYSIWYG editor, however, it requires adjusting your webpack.config.js to CKEditor 5 needs.

    Read more about this option in Integrating CKEditor 5 from source.

 

저는 기존에 있는 빌드를 커스터마이징하는 방향으로 먼저 시도해보았습니다. 왜냐면 CKEditor 쪽에서 커스터마이징을 쉽게 할 수 있도록 웹 서비스를 제공하고 있기 때문입니다!

 

 

CKEditor 5 online builder | Rich text editor of tomorrow

Create your own CKEditor 5 build with customized plugins, toolbar and language in 5 simple steps. Available rich text editor types are classic, inline, balloon, balloon block and decoupled document.

ckeditor.com

 

 

그러나 react로의 이식이 불편하여 결국 두번 째 방법인 소스 단계부터 editor를 구성하기(Building from source)

를 사용할 수밖에 없었습니다. 이번 삽질로 ckeditor에서는 eject를 통한 웹팩 수정과 소스 단계에서의 editor 빌드가 불가피하다는 것을 알게 되었습니다.

 

우리는 공식 문서의 케이스 2번을 참고할 것입니다. 1번은 일반 빌드이고, 3번은 여러 에디터를 사용하는 경우를 다루고 있기 때문입니다.

 

 

https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/advanced-setup.html#scenario-2-building-from-source

 

https://geniyong.github.io/2019/03/14/CKEditor5-+-React-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0.html

 

 

1️⃣ 우선 관련된 패키지를 설치합시다.

// 에디터 관련 모듈
npm i @ckeditor/ckeditor5-dev-webpack-plugin
npm i @ckeditor/ckeditor5-dev-utils
npm i @ckeditor/ckeditor5-theme-lark

// CRA에 없는 모듈
npm i raw-loader webpack-cli style-loader@1

 

raw-loader는 svg 해석을 위해 넣습니다.

그 외 공식 문서에서 설치하라고 권고한 css-loader 등은 현재 CRA에서 확인해보니 이미 포함된 내용들입니다.

 

 

2️⃣ 웹팩 설정

 

eject로 생성된 webpack.config.js에서 다음과 같이 작성해줍시다.

잘 넣어야 합니다. 상당히 긴 문서이기 때문에 실수로 잘 못 넣으면 골치아파집니다.

 

언어 세팅을 굳이 하지 않겠다면 CKEditorWebpackPlugin 설정은 하지 않아도 됩니다. 통째로 지워도 작동하는데에는 문제 없습니다.

const CKEditorWebpackPlugin = require( '@ckeditor/ckeditor5-dev-webpack-plugin' );
const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );


plugins: [
  new CKEditorWebpackPlugin({
    // UI 호버 시 설명해주는 언어를 세팅합니다. 일단 영어 en로 설정합니다
    language: "en",
  }),
  ...
]

module: {
  rules: [
    {
      test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
      use: ["raw-loader"],
    },
    {
      test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
      use: [
        {
          loader: "style-loader",
          options: {
            injectType: "singletonStyleTag",
            attributes: {
              "data-cke": true,
            },
          },
        },
        {
          loader: "postcss-loader",
          options: styles.getPostCssConfig({
            themeImporter: {
              themePath: require.resolve("@ckeditor/ckeditor5-theme-lark"),
            },
            minify: true,
          }),
        },
      ],
    },

 

 

3️⃣ 이제 본격적으로 CKEditor를 사용하기 위한 패키지를 설치해봅시다.

원하는 내용을 설치해 넣으면 됩니다. React에서 사용하는 거니 당연히 @ckeditor/ckeditor5-react는 필수로 설치해야 겠죠?

 

저는 아래를 전부 다 설치했습니다. 이 것 외에도 내용이 많은데 공식 홈페이지에서 찾아보시면 됩니다. 

// 사실상 필수인 것들, classic editor 말고 걸 사용하고 싶다면 그걸 설치하면 된다.
npm i @ckeditor/ckeditor5-react @ckeditor/ckeditor5-editor-classic @ckeditor/ckeditor5-essentials

// 선택~
npm i @ckeditor/ckeditor5-adapter-ckfinder
npm i @ckeditor/ckeditor5-alignment
npm i @ckeditor/ckeditor5-autoformat
npm i @ckeditor/ckeditor5-autosave
npm i @ckeditor/ckeditor5-basic-styles
npm i @ckeditor/ckeditor5-block-quote
npm i @ckeditor/ckeditor5-ckfinder
npm i @ckeditor/ckeditor5-dev-utils
npm i @ckeditor/ckeditor5-editor-classic
npm i @ckeditor/ckeditor5-essentials
npm i @ckeditor/ckeditor5-export-pd
npm i @ckeditor/ckeditor5-export-pdf
npm i @ckeditor/ckeditor5-font
npm i @ckeditor/ckeditor5-heading
npm i @ckeditor/ckeditor5-horizontal-line
npm i @ckeditor/ckeditor5-image
npm i @ckeditor/ckeditor5-indent
npm i @ckeditor/ckeditor5-link
npm i @ckeditor/ckeditor5-list
npm i @ckeditor/ckeditor5-media-embed
npm i @ckeditor/ckeditor5-paragraph
npm i @ckeditor/ckeditor5-paste-from-office
npm i @ckeditor/ckeditor5-special-characters
npm i @ckeditor/ckeditor5-table
npm i @ckeditor/ckeditor5-typing

 

 

 

4️⃣ 이제 그냥 사용만 하면 됩니다.

 

원하는 기능은 api 문서에서 찾아봅시다.

https://ckeditor.com/docs/ckeditor5/latest/api/index.html

 

import React from "react";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-editor-classic/src/classiceditor.js";

import Bold from "@ckeditor/ckeditor5-basic-styles/src/bold.js";
import Essentials from "@ckeditor/ckeditor5-essentials/src/essentials.js";
import Heading from "@ckeditor/ckeditor5-heading/src/heading.js";
import Paragraph from "@ckeditor/ckeditor5-paragraph/src/paragraph.js";
import Italic from "@ckeditor/ckeditor5-basic-styles/src/italic.js";
import Alignment from "@ckeditor/ckeditor5-alignment/src/alignment.js";
import BlockQuote from "@ckeditor/ckeditor5-block-quote/src/blockquote.js";
import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat.js";
import Autosave from "@ckeditor/ckeditor5-autosave/src/autosave.js";
import CKFinder from "@ckeditor/ckeditor5-ckfinder/src/ckfinder.js";
import CKFinderUploadAdapter from "@ckeditor/ckeditor5-adapter-ckfinder/src/uploadadapter.js";
import ExportToPDF from "@ckeditor/ckeditor5-export-pdf/src/exportpdf.js";
import FontColor from "@ckeditor/ckeditor5-font/src/fontcolor.js";
import FontFamily from "@ckeditor/ckeditor5-font/src/fontfamily.js";
import FontSize from "@ckeditor/ckeditor5-font/src/fontsize.js";
import HorizontalLine from "@ckeditor/ckeditor5-horizontal-line/src/horizontalline.js";
import Image from "@ckeditor/ckeditor5-image/src/image.js";
import ImageCaption from "@ckeditor/ckeditor5-image/src/imagecaption.js";
import ImageResize from "@ckeditor/ckeditor5-image/src/imageresize.js";
import ImageStyle from "@ckeditor/ckeditor5-image/src/imagestyle.js";
import ImageToolbar from "@ckeditor/ckeditor5-image/src/imagetoolbar.js";
import ImageUpload from "@ckeditor/ckeditor5-image/src/imageupload.js";
import Indent from "@ckeditor/ckeditor5-indent/src/indent.js";
import IndentBlock from "@ckeditor/ckeditor5-indent/src/indentblock.js";
import Link from "@ckeditor/ckeditor5-link/src/link.js";
import List from "@ckeditor/ckeditor5-list/src/list.js";
import MediaEmbed from "@ckeditor/ckeditor5-media-embed/src/mediaembed.js";
import PasteFromOffice from "@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice";
import SpecialCharacters from "@ckeditor/ckeditor5-special-characters/src/specialcharacters.js";
import SpecialCharactersEssentials from "@ckeditor/ckeditor5-special-characters/src/specialcharactersessentials.js";
import Strikethrough from "@ckeditor/ckeditor5-basic-styles/src/strikethrough.js";
import Table from "@ckeditor/ckeditor5-table/src/table.js";
import TableToolbar from "@ckeditor/ckeditor5-table/src/tabletoolbar.js";
import TextTransformation from "@ckeditor/ckeditor5-typing/src/texttransformation.js";
import Underline from "@ckeditor/ckeditor5-basic-styles/src/underline.js";

const installedPlugins = [
  Alignment,
  Autoformat,
  Autosave,
  BlockQuote,
  Bold,
  CKFinder,
  CKFinderUploadAdapter,
  Essentials,
  ExportToPDF,
  FontColor,
  FontFamily,
  FontSize,
  Heading,
  HorizontalLine,
  Image,
  ImageCaption,
  ImageResize,
  ImageStyle,
  ImageToolbar,
  ImageUpload,
  Indent,
  IndentBlock,
  Italic,
  Link,
  List,
  MediaEmbed,
  Paragraph,
  PasteFromOffice,
  SpecialCharacters,
  SpecialCharactersEssentials,
  Strikethrough,
  Table,
  TableToolbar,
  TextTransformation,
  Underline,
];

function App() {
  return (
    <div>
      <CKEditor
        editor={ClassicEditor}
        config={{
          plugins: [...installedPlugins],
          toolbar: [
            "exportPdf",
            "|",
            "heading",
            "|",
            "fontFamily",
            "fontSize",
            "fontColor",
            "alignment",
            "|",
            "bold",
            "italic",
            "strikethrough",
            "underline",
            "specialCharacters",
            "horizontalLine",
            "|",
            "bulletedList",
            "numberedList",
            "|",
            "indent",
            "outdent",
            "|",
            "link",
            "blockQuote",
            "CKFinder",
            "imageUpload",
            "insertTable",
            "mediaEmbed",
            "|",
            "undo",
            "redo",
          ],
        }}
        data="<p>Hello from CKEditor 5!</p>"
      />
    </div>
  );
}

export default App;

 

 

5️⃣ ValidationError: Style Loader Invalid Options, options should NOT have additional properties 오류 해결

 

공식 문서가 하란대로 했는데 문제가 발생한다. 찾아보니 style-loader의 문제였다.

"style-loader""0.23.1" 현재 사용한 버전은 0.23.1이다.

style-loader@1 로 업데이트해준다음, loader가 중복되어 발생하지 않게

웹팩 설정을 다음과 같이 수정한다.

 

exclude 부분 추가

{
  test: cssRegex,
  exclude: {
    or: [cssModuleRegex, /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/],
  },

file-loader 부분 수정

{
  loader: require.resolve("file-loader"),
  exclude: [
    /\.(js|mjs|jsx|ts|tsx)$/,
    /\.html$/,
    /\.json$/,
    /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
    /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
  ],
  options: {
    name: "static/media/[name].[hash:8].[ext]",
  },
},

 

 

스타일링

 

기본적인 디자인도 괜찮지만, css를 통해 해당 디자인을 수정할 수도 있습니다.

https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/ui/document-editor.html

https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html

 

순정 ckeditor에서 개발자 도구를 열어 클래스를 확인하고 이를 조작해봅시다.

저는 기본 높이만 설정했습니다.

.ck-editor__editable:not(.ck-editor__nested-editable) {
    min-height: 400px;
}

 

CKFinder를 이용한 이미지 업로드 

 

이미지 업로드는 서버 사이드가 필요한 작업입니다. express로 진행해보겠습니다.

https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/image-upload.html#official-upload-adapters

 

 

The software that makes the image upload possible is called an upload adapter. It is a callback that tells the WYSIWYG editor how to send the file to the server. There are two main strategies of getting the image upload to work that you can adopt in your project:

 

  • Official upload adapters – There are several features providing upload adapters developed and maintained by the CKEditor team. Pick the best one for your integration and let it handle the image upload in your project.
  • Custom upload adapters – Create your own upload adapter from scratch using the open API architecture of CKEditor 5.

CKEditor 쪽에서는 Easy Image라는 CKEditor Cloud Services를 사용하라고 유도하고 있지만 어림도 없지~ 우리는 CKEditor를 사용해보도록 하겠습니다.

 

Image upload only와 Full integration이 있는데 Image upload only로 진행하겠습니다.

 

import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder';

plugins: [ CKFinder, ... ],
toolbar: [ 'imageUpload', ... ],
ckfinder: {
   uploadUrl: '서버쪽 주소'
}

 

이제 이미지를 서버단에서 받아서 처리하면 됩니다. multer-s3로 처리하고 이미지 프로세싱도 처리해줍시다.

CORS문제도 있었는데 cors 패키지로 해결하시면 됩니다.

 

다음 예시를 참고하면 좋습니다.

(https://jasonwatmore.com/post/2017/03/23/mean-stack-image-upload-from-ckeditor)

 

const multer = require("multer");

const multerUpload = multer({ dest: "upload/" });
const uploadImage = multerUpload.single("upload");

module.exports = { uploadImage };
apiRouter.post("/image/upload", uploadImage, (req, res, next) => {
  console.log(req.file)
  res.json({uploaded: true, url: `업로드된 주소`});
});

 

multe-s3든 sharp든 이용해서 이미지 사이징을 하고 적당한 공간에 저장합시다.

 

클라이언트 단에 존재하는 에디터에 이미지를 보내주기 위해서 반드시 리턴하는 값은 다음과 같아야 합니다.

// 업로드 성공시
res.status(200).json({uploaded: true, url: `업로드된 주소`});

// 오류 발생시
res.status(500).json({uploaded: false, error: error});

 

 

 

 

 

 

 

Building for production

If you still work with create-react-app@1 or use a custom configuration for you application that still uses webpack@3, you will need to adjust the UglifyJsPlugin option to make CKEditor 5 compatible with this setup. CKEditor 5 builds use ES6 so the default JavaScript minifier of webpack@3 and create-react-app@1 is not able to digest them.

To do that, you need to first eject the configuration from the setup created via create-react-app (assuming that you use it):

 

npm run eject

 

Then, you can customize UglifyJsPlugin options in the webpack configuration. Read how to do this here.

 

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체