| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- 'use client';
- /**
- * @author: Kim Jino
- * @since: 2025.03.31
- * @description: CKEditor component for Next.js using Script component for loading CKEditor script
- */
- // @/app/(forum)/post/_component/Editor.tsx
- import '@/styles/editor.scss';
- import Script from 'next/script';
- import React, { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
- import { CKEditor } from '@ckeditor/ckeditor5-react';
- import type { Editor as CKEditorInstance } from '@ckeditor/ckeditor5-core';
- import Loading from '@/app/component/Loading';
- import BoardMeta from '@/types/forum/boardMeta';
- import { PostConst } from '@/constants/forum';
- export interface Handle {
- editorInstance: CKEditorInstance|null;
- getFileStore(): UploadedFile[];
- getImageStore(): UploadedImage[];
- getMediaStore(): UploadedMedia[];
- }
- interface Props {
- key: string;
- data: string;
- onChange: (data: string) => void;
- boardMeta?: BoardMeta|null;
- }
- const Editor = forwardRef<Handle, Props>(({ key = '', data = '', onChange, boardMeta }: Props, ref) =>
- {
- const [ready, setReady] = useState<boolean>(false); // CKEditor가 준비되었는지 여부
- const [instanceLoaded, setInstanceLoaded] = useState<boolean>(false);
- const editorRef = useRef<typeof window.Editor|null>(null); // CKEditor 객체
- const editorInstanceRef = useRef<CKEditorInstance|null>(null);
- useEffect(() => {
- if (typeof window !== 'undefined' && window.Editor) {
- editorRef.current = window.Editor;
- setReady(true);
- }
- }, [key]); // 게시판 주소가 바뀌면 다시 ready 확인
- const handleScriptLoad = () => {
- if (typeof window !== 'undefined' && window.Editor) {
- editorRef.current = window.Editor;
- setReady(true);
- } else {
- console.error('CKEditor script not loaded properly.');
- }
- };
- // 외부에 접근 가능한 명령어
- useImperativeHandle(ref, () => ({
- editorInstance: editorInstanceRef.current,
- getFileStore: () => editorInstanceRef.current?._fileStore || [],
- getImageStore: () => editorInstanceRef.current?._imageStore || [],
- getMediaStore: () => editorInstanceRef.current?._mediaStore || []
- }), [instanceLoaded]);
- return (
- <>
- <Script src="/editor/editor.min.js" strategy="afterInteractive" onLoad={handleScriptLoad}/>
- {ready && editorRef.current ? (
- <CKEditor
- editor={editorRef.current}
- data={data}
- config={{
- placeholder: '내용을 입력하세요.',
- maxWordCount: PostConst.maxAllowedContentLength, // 최대 본문 길이
- // 이미지 설정
- allowImage: boardMeta?.write.allowImage ?? false,
- imageUploadLimit: boardMeta?.write.imageUploadLimit,
- imageUploadMaxSize: boardMeta?.write.imageUploadMaxSize,
- // 동영상 설정
- allowMedia: boardMeta?.write.allowMedia ?? false,
- mediaUploadLimit: boardMeta?.write.mediaUploadLimit,
- // 파일 설정
- allowFile: boardMeta?.write.allowFile ?? false,
- fileUploadLimit: boardMeta?.write.fileUploadLimit,
- fileUploadMaxSize: boardMeta?.write.fileUploadMaxSize,
- fileUploadExtension: boardMeta?.write.fileUploadExtension
- /*
- // 글자수 세기
- wordCount: {
- onUpdate: (stats: { characters: number; words: number }) => {
- document.getElementById('editorTxtLength')!.innerText = stats.characters.toString();
- }
- }
- */
- }}
- onReady={(editor) => {
- console.log('Editor was initialized');
- // 최소 높이 설정
- editor.editing.view.change(writer => {
- const root = editor.editing.view.document.getRoot();
- if (root) {
- writer.setStyle('min-height', '300px', root);
- }
- });
- editorInstanceRef.current = editor;
- setInstanceLoaded(true);
- // 외부 내용을 복사 붙여넣기 가능하도록 처리
- editor.editing.view.document.on('paste', (_, data) => {
- const html = data.dataTransfer.getData('text/html');
- const text = data.dataTransfer.getData('text/plain');
- const content = (html || text);
- if (content) {
- const viewFragment = editor.data.processor.toView(content);
- const modelFragment = editor.data.toModel(viewFragment);
- editor.model.change(() => {
- editor.model.insertContent(modelFragment);
- });
- }
- });
- }}
- onChange={(_, editor) => {
- onChange?.(editor.getData());
- }}
- />
- ) : (
- <Loading/>
- )}
- </>
- );
- });
- // 컴포넌트 이름 지정
- Editor.displayName = 'Editor';
- export default Editor;
|