상세 컨텐츠

본문 제목

React(리액트) 티키타카 17 (react-quill-new 에디터)

React

by e7e 2024. 12. 25. 11:39

본문

React에 쓸 Editor가 필요하단당. 생각해보니 그렇탕.  그냥 Quill을 넣어보장.

 

1. 마음이 가는 어딘가에 reditor 폴더 생성

   ( 2. 번에서 새폴더를 만들어 이름 주고  열어도 된당)

 

2. vscode의 Open Foler로  reditor 폴더 열기 

 

3. 터미널을 열공  vite를 이용 react project 생성

npm create vite@latest .

 

4. 필요 모듈 설치

npm i react-quill-new
npm i  quill-resize-image

 

5. 알아서 파일 대략 정리하고

   (assets 폴더, App.css 파일 삭제, index.css는 모든 내용 지우깅)

   개발 서버 실행 시키면 준비 끝

npm install
npm run dev

 

그라믄 어서 시작해 보아유

App.jsx

import ReactQuill from "react-quill-new";
import "quill/dist/quill.snow.css";

function App() {
  return (
    <>
      <h1>작성 해볼쳐?</h1>
      <ReactQuill style={{ height: "500px" }} theme="snow" />
    </>
  );
}

export default App;

 

아마도 아래와 같은 화면이 나올꼬시당. 심플한 거이 맘에 든당.

 

이미지를 널라카닝,  툴바에 그거이 없당.

툴바를 쪼메 고치야 겄당.

 

아래 코드를 그져 눈으로 보장. 이미지 버튼,링크 버튼 순서가 첫번째 그룹으로

맨 처음에 온당. 다음에는 h1,h2 ,h3 버튼 .....   그냥 그렇당.

// 에디터 Toolbar 리스트 https://quilljs.com/docs/modules/toolbar 참공
// 낸 중에 별도 파일로 빼면 깔끔해 지겄넹
const toolbarOptions = [
  ["image", "link"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  [{ list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }],
  [{ align: [] }, { color: [] }, { background: [] }], // dropdown with defaults from theme
  ["clean"],
];

 

위에 껄  따로 파일로 빼면 좋지만 지금은 한 눈에 들어오는 거이 좋으니

필요한 모든 걸 App.jsx에 때려 박아서 보장. (modules에 시선 집중)

 

App.jsx

import ReactQuill from "react-quill-new";
import "quill/dist/quill.snow.css";

// 에디터 Toolbar 리스트 https://quilljs.com/docs/modules/toolbar 참공
// 낸 중에 별도 파일로 빼면 깔끔해 지겄넹
const toolbarOptions = [
  ["image", "link"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  [{ list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }],
  [{ align: [] }, { color: [] }, { background: [] }], // dropdown with defaults from theme
  ["clean"],
];

function App() {
  const modules = {
    toolbar: toolbarOptions,
  };

  return (
    <>
      <h1>작성 해볼쳐?</h1>
      <ReactQuill modules={modules} style={{ height: "300px" }} theme="snow" />
    </>
  );
}

export default App;

 

아마도 아래와 같은 화면이 눈 앞에 커튼 칠꺼당.(이미지가 잘 들어간당~ 오호!당)

 

헌디 문제가 있을지어당.  이미지 사이즈 조절이 안된당. 답답하여랑.

그리하여 quill-resize-image 모듈을 아까메로 설치했던 거시였던 거시였당.

https://www.npmjs.com/package/quill-resize-image

위 링크에 설명은 좁고 여백은 넓고 가득한데 그냥 잘 된당. 

그라믄 적용하여 보장.  modules 변수에 resize:{} 만을 추강

 

App.jsx

import ReactQuill, { Quill } from "react-quill-new";
import "quill/dist/quill.snow.css";
import QuillResizeImage from "quill-resize-image";
Quill.register("modules/resize", QuillResizeImage);

// 에디터 Toolbar 리스트 https://quilljs.com/docs/modules/toolbar 참공
// 낸 중에 별도 파일로 빼면 깔끔해 지겄넹
const toolbarOptions = [
  ["image", "link"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  [{ list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }],
  [{ align: [] }, { color: [] }, { background: [] }], // dropdown with defaults from theme
  ["clean"],
];

function App() {
  const modules = {
    toolbar: toolbarOptions,
    resize: {}, // 요거 추강
  };

  return (
    <>
      <h1>작성 해볼쳐?</h1>
      <ReactQuill modules={modules} style={{ height: "300px" }} theme="snow" />
    </>
  );
}

export default App;

 

왠지 결과를 보고프당.

(들어간 이미지를 클릭하면, 모서리를 잡고 사이즈 조정이 가능하당, 보조 메뉴동 화들짝!)

 

이 정도면 다 되었나 싶을 수 있당. 당근 아니당. F12 누르고 마우스로 이미지를 클릭하고

elements 탭을 보면, 문제가 보인당. 

data:image/jpeg;base64,/9j/4AAQSkZJRgA....................... 

BackEnd 구축시 저런 값을 DB에 저장하는 것은  가성비가 많이 안 좋당.

 

파일을 서버에 보내서 저장 시킨 후에, 접근할 수 있는 Web-URL을 받아서 

넣으면 좋겠당. (열심히 공부했다면 분명 알아 들었을꺼이당. OK?)

 

대략 점진적으로 생각이란 걸 해보면 이미지 버튼 클릭시에 ajax로 서버에 파일을 보내고

돌려 받은 값으로 img 태그의 src값을 바꾸어야 할 꺼이당.

이 부분을 구현한 소스는 검색하면 많이 나오는데,  실제 에디터 컴포넌트 자체를 구현하는

사람이 아니라면 잘 쓸 일이 없는 부분이니, 부담없이 가져다  쓰고,

나중에 어디에 필요한 ajax 소스를 넣으면 되는지에만 꽌씸을 가지장.

 

App.jsx

import ReactQuill, { Quill } from "react-quill-new";
import "quill/dist/quill.snow.css";
import QuillResizeImage from "quill-resize-image";
import { useEffect, useRef } from "react";
Quill.register("modules/resize", QuillResizeImage);

// 에디터 Toolbar 리스트 https://quilljs.com/docs/modules/toolbar 참공
// 낸 중에 별도 파일로 빼면 깔끔해 지겄넹
const toolbarOptions = [
  ["image", "link"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  [{ list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }],
  [{ align: [] }, { color: [] }, { background: [] }], // dropdown with defaults from theme
  ["clean"],
];

function App() {
  const quillRef = useRef(null);

  const modules = {
    toolbar: toolbarOptions,
    resize: {}, // 요거 추강
  };

  // 이미지 버튼 클릭 핸들러
  const imageHandler = () => {
    alert("버튼 누르셨어요");
    const inputFile = document.createElement("input");
    inputFile.type = "file";
    inputFile.accept = "image/*";
    inputFile.click(); // 강제 클릭

    inputFile.onchange = async () => {
      const file = inputFile.files[0];

      const Editor = quillRef.current.getEditor();

      const range = Editor.getSelection(true);
      Editor.insertEmbed(range.index, "image", `/vite.svg`);

      try {
        // 요기 url을 ajax로 return 받아야 한당. 지금은 무조건 카리나당!!~~ ㅋㅋ
        const url =
          "https://cdn.seoulwire.com/news/photo/202208/478344_682775_4739.jpg";
        Editor.deleteText(range.index, 1);
        Editor.insertEmbed(range.index, "image", url);

        Editor.setSelection(range.index + 1);
      } catch (e) {
        Editor.deleteText(range.index, 1);
      }
    };
  };

  useEffect(() => {
    // image 버튼에 imageHandler 등록
    if (quillRef.current) {
      quillRef.current
        .getEditor()
        .getModule("toolbar")
        .addHandler("image", imageHandler);
    }
  }, []);

  return (
    <>
      <h1>작성 해볼쳐?</h1>
      <ReactQuill
        ref={quillRef}
        modules={modules}
        style={{ height: "300px" }}
        theme="snow"
      />
    </>
  );
}

export default App;

 

실행하여 결과를 바야바 했당.   이미지 추가 버튼을 누루하치 하고 무얼 골라도 

카리나 이미지가 들어간당. 왱?   imageHandler 함수 안에서 

img src에 들어가는 url 변수 값을 카리나 이미지 주소로 고정해 놓았기 때문이당.

 


에잉~ 이왕 한 김에 스프링부트로 간단히  요 부분을 리액션 할 수 있는 부분도 맹글어서 

대략적 전략적 전술적 대응을 보장.

 

web dependency만 넣은 spring boot 프로젝트를 맹글었다 치장.

 

웹 URL과 실제 물리적 경로를 맵핑시키는 설정 파일을 맹글장.

YouKnowConfig.java

package com.e7e.quill.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
public class YouKnowConfig implements WebMvcConfigurer{
	
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		log.info("요기가 실행되었는지? 요따구 로그는 좋은 디버깅 습관");
		/*
		 * 브라우져 주소줄에 /km/huk/merong.jpg 가 들어가면
		 *        d:/minu/huk/merong.jpg 에 맵핑 된다는 이야깅 
		 */
				
		registry.addResourceHandler("/km/**")             // 웹 접근 URL 경로 
		        .addResourceLocations("file:///d:/minu/");  // 서버내 물리 경로
	}
}

 

 

요청 받는 RestController를 맹글장. @CrossOrigin 사용에 주목

React쪽에서 proxy 설정으로도 할 수 있는데, 귀안탕!

HiphopController.java

package com.e7e.quill.controller;

import java.io.File;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@CrossOrigin("http://localhost:5173") // 요기서 오는 외부서버 요청은 허락
@RestController
@RequestMapping("/hiphop")
public class HipHopController {
	
	@PostMapping("/fileup")
	public String fileup(MultipartFile file) throws Exception {
		log.info("체킁1: {}",file.getOriginalFilename());
		log.info("체킁2: {}",file.getSize());
		
		String fileName = file.getOriginalFilename();
		String saveDir = "d:/minu/";
		file.transferTo(new File(saveDir + fileName));
		
		String webURL = "http://localhost:8080/km/" + fileName;
		return webURL;
		
	}
}

 

Tomcat을 실행 시켜 잘 실행 되는지 확인하장!, 난 포트 8080으로 돌렸당.

 

 

이제  마지막으로 React의 App.jsx를  위 스프링과 힙합댄스를 추도록 고치장.

ajax 사용이 필요한데,  axios가 젤로 편하당.

imageHandler 함수 내용만 바꾸어쓰닝, 그거에 시선을 몰아주장.

아 참고로 난 public 폴더에 loading.gif  파일을 다운받아서 넣었당.

npm i axios

 

App.jsx  완성본

import ReactQuill, { Quill } from "react-quill-new";
import "quill/dist/quill.snow.css";
import QuillResizeImage from "quill-resize-image";
import { useEffect, useRef } from "react";
import axios from "axios";
Quill.register("modules/resize", QuillResizeImage);

// 에디터 Toolbar 리스트 https://quilljs.com/docs/modules/toolbar 참공
// 낸 중에 별도 파일로 빼면 깔끔해 지겄넹
const toolbarOptions = [
  ["image", "link"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  [{ list: "ordered" }, { list: "bullet" }, { indent: "-1" }, { indent: "+1" }],
  [{ align: [] }, { color: [] }, { background: [] }], // dropdown with defaults from theme
  ["clean"],
];

function App() {
  const quillRef = useRef(null);

  const modules = {
    toolbar: toolbarOptions,
    resize: {}, // 요거 추강
  };

  // 이미지 버튼 클릭 핸들러
  const imageHandler = () => {
    alert("버튼 누르셨어요 확인용!");
    const inputFile = document.createElement("input");
    inputFile.type = "file";
    inputFile.accept = "image/*";
    inputFile.click(); // 강제 클릭

    inputFile.onchange = async () => {
      const file = inputFile.files[0];

      const Editor = quillRef.current.getEditor();

      const range = Editor.getSelection(true);
      // 그냥 로딩 이미지 재미롱!....
      Editor.insertEmbed(range.index, "image", `/loading.gif`);

      const formData = new FormData();
      formData.append("file", file);

      // 요건 나중에 환경변수 값으로 빼면 좋을 꼉!, 그라지유
      const springURL = "http://localhost:8080";

      axios
        .post(`${springURL}/hiphop/fileup`, formData)
        .then((resp) => {
          console.log("체킁: ", resp.data);
          if (resp.data) {
            Editor.deleteText(range.index, 1);
            Editor.insertEmbed(range.index, "image", resp.data);
            Editor.setSelection(range.index + 1);
          }
        })
        .catch((e) => {
          alert("미나네 미나네 문제 발생!!!");
          Editor.deleteText(range.index, 1);
        });
    };
  };

  useEffect(() => {
    // image 버튼에 imageHandler 등록
    if (quillRef.current) {
      quillRef.current
        .getEditor()
        .getModule("toolbar")
        .addHandler("image", imageHandler);
    }
  }, []);

  return (
    <>
      <h1>작성 해볼쳐?</h1>
      <ReactQuill
        ref={quillRef}
        modules={modules}
        style={{ height: "300px" }}
        theme="snow"
      />
    </>
  );
}

export default App;

 

해보장!, 호라 잘 된단당!  지치고 말았당. 

 

이제 이해가 되었다묜 구미가 당기도록 구미에 맞게 수정해서 마구 사용해 뽀장!~~


 

하느리 맑지 않당.  

비 아니 아니  눈이 오려나

 

하느리 맑지 않으면 바다도 맑지 않당.

아니 맑지 않다는 화면을 전송하는 거당.

 

바다는 결정권이 없당. 

단지 하늘을 투영할 뿐....., 수동? 불쌍?

 

이런 날은 아파트가 제격이다.

로제는 아파트에 살깡?

아 파트를 나누는 걸 깜빡했당. 

파트가 여러개면 멀티파트!! 

 

 

https://www.youtube.com/watch?v=ekr2nIex040

 

관련글 더보기