minlog
article thumbnail

 

무료 에디터 사용하기

네이버 구글 등 api가 존재하지만 간편하고 깔끔하게 사용하기 좋은 것 같아서 summernote를 선택했습니다.

 

 

Summernote - Super Simple WYSIWYG editor

Super Simple WYSIWYG Editor on Bootstrap Summernote is a JavaScript library that helps you create WYSIWYG editors online.

summernote.org

 

 

1. 필요 파일 설치

경로 : hompage > 'get started > 'download compiled'

사용가능한 소스들이 모두 들어있는데 그 중에서 자신의 프로젝트에 맞는 파일들을 선택해서 넣어주면 됩니다.

부트스트랩과 함께 사용할 수도 있지만 저는 기본 라이트한 내용을 적용했습니다.

summernote-lite.css , summernote-lite.js

언어 적용도 필요할 경우, summernote-ko-KR.js 파일도 같이 추가해줍니다.

<link rel="stylesheet" href="${contextPath}/resource/summernote/summernote-lite.css">
<script src="${contextPath}/resource/summernote/summernote-lite.js"></script>
<script src="${contextPath}/resource/summernote/summernote-ko-KR.js"></script>

 


 

2. html 구조

<div id="summernote">Hello Summernote</div>
<form method="post">
  <textarea id="summernote" name="editordata"></textarea>
</form>
summernote 실행

 


 

3. 스크립트로 적용 및 설정

적용한 높이 포커스,언어 이외에도 사이트에서 많은 옵션들을 확인 할 수 있습니다.

$('#summernote').summernote({
  height: 300,                 // set editor height
  minHeight: null,             // set minimum height of editor
  maxHeight: null,             // set maximum height of editor
  focus: true                  // set focus to editable area after initializing summernote
  lang: 'ko-KR' // default: 'en-US'
});

 


 

4. 게시글 이미지 업로드 구현

기존 에디터에서 이미지를 업로드 해보면 base64 텍스트형태로 포함 되는 것을 확인 할 수 있습니다. 

인코딩된 문자열이 이미지로 들어가 문서내에 이미지가 포함되는 것입니다. 

하지만 데이터가  많이 늘어날 뿐만 아니라 이미지 관리를 할 수 없는 문제점이 있습니다.

이미지를 따로 관리하기 위헤 썸머노트에서 콜백함수를 사용할 수 있습니다.

 

 

 

4-1. 화면 콜백함수

이미지를 업로드해주는 함수를 따로 추가하여 썸머노트의 콜백함수에서 불러와주었습니다. 

  • 비동기 방식으로 POST 타입으로 데이터를 전달하고 있는데, 프로젝트 내에서 스프링 시큐리티를 사용하고 있어서 헤더에 토큰을 함께 전송해주었습니다.
  • FormData 는  폼을 쉽게 보내도록 도와주는 객체입니다.  file 객체를 form 으로 만들어서 전송해줍니다. 

 

 $('#summernote').summernote({
            placeholder: '기억에 남는 여행 순간들을 기록해보세요.',
            tabsize: 2,
            height: 300,
            minHeight: null,             // 최소 높이
            maxHeight: null,             // 최대 높이
            focus: true,                  // 에디터 로딩후 포커스를 맞출지 여부
            lang: "ko-KR",                    // 한글 설정
            toolbar: [
                ['style', ['style']],
                ['font', ['bold', 'underline', 'clear']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph']],
                ['table', ['table']],
                ['insert', ['link', 'picture', 'video']],
                ['view', ['fullscreen', 'codeview', 'help']]
            ],
            callbacks: {
                onImageUpload: function (files, editor, welEditable) {
                    // 파일 업로드 (다중 업로드를 위해 반복문 사용)
                    for (var i = files.length - 1; i >= 0; i--) {
                        var fileName = files[i].name
                        // 이미지 alt 속성 삽일을 위한 설정
                        var caption = prompt('이미지 설명 :', fileName)
                        if (caption == '') {
                            caption = '이미지'
                        }
                        uploadSummernoteImageFile(files[i], this, caption)
                    }
                },
            }

        });

        $('#summernote').summernote('code',boardCont.html());



        /* Post 메서드 시큐리티 추가 */
        let headerName = document.getElementsByClassName("csrf_input")[1].getAttribute("name");
        let token = document.getElementsByClassName("csrf_input")[0].getAttribute("value");

        // 이미지 업로드 함수 ajax 활용
        function uploadSummernoteImageFile(file, el, caption) {
            let formData = new FormData();
            formData.append('file', file)
            console.log(formData);
            $.ajax({
                type: 'post',
                data: formData,
                url: "/fileApi/categoryImgSave",
                dataType: 'json',
                contentType: false,
                enctype: 'multipart/form-data',
                processData: false,
                async: true, //동기, 비동기 여부
                cache :false, // 캐시 여부
                beforeSend : function(xhr)
                {
                    xhr.setRequestHeader(headerName, token);
                },
                focus: true,
                success: function (data) {
                    console.log(data.url)
                    $(el).summernote('insertImage', '/boardApi/boardImg?url='+data.url, function($image) {
                        $image.css('width', "100%");
                        $image.attr('alt', caption)
                    });

                },
                error:function (data){
                    console.log(data)
                }
            });

        }

 

 

4-2. 컨트롤러 

이미지를 실제 저장하는 서비스 로직에 파일을 전달하고 이미지가 저장된 경로를 전달받아 문자열로 변환해 화면에 다시 전송합니다.

 

📑FileApi.java

package com.example.travel.controller.api;
...

@Log4j2
@RestController
@RequiredArgsConstructor
@RequestMapping("/fileApi")
public class FileApi {
    final BoardFileService boardFileService;
    @PostMapping(value = "/categoryImgSave")
    @ResponseBody
    public String categoryImageUpload(@RequestParam("file") MultipartFile files) {
        log.info("컨텐츠 이미지 저장------------------------");
        JsonObject categoryImg = boardFileService.createImage(files, "categoryImg");
        String txt = categoryImg.toString();
        return txt;
    }
}

 

 

4-3. 이미지 저장 서비스 로직 

- 서비스 로직에서 JsonObject 객체를 사용하려면 `build.gradle` 에 아래 라이브러리를 추가해주어야한다. 

자바 오브젝트를 쉽게 JSON으로 변환시켜주는 심플한 라이브러리 입니다.

📑build.gradle

// https://mvnrepository.com/artifact/commons-codec/commons-codec
implementation group: 'commons-codec', name: 'commons-codec', version: '1.9'

 

📑BoardFileService.java

package com.example.travel.service;
...
@RequiredArgsConstructor
@Service
@Log4j2
public class BoardFileService{

    @Value("${spring.servlet.multipart.location}")
    private String uploadPath;

    final CategoryImageRepository categoryImageRepository;

    public JsonObject createImage(MultipartFile uploadFile,String folderPath) {
        log.info("일반 컨텐츠 이미지 저장 ==========================");

        //0. 저장 후 전달할 객체
        JsonObject jsonObject = new JsonObject();

        //1. 파일 경로 폴더를 생성
        String folderPathMake = makeFolder(folderPath);


        //2. 경로와 이름을 나눠야함.
        //실제 파일 이름 ie 나 edge는 전체 경로가 전달된다.
        String uploadFileName = uploadFile.getOriginalFilename();
        String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("\\") + 1 );
        log.info("fileName : " + fileName);

        //UUID
        String uuid = UUID.randomUUID().toString();
        String originalName = uuid + "_"+ fileName;

        //저장할 파일 이름 중간에 _ 를 이용하여 구분
        String saveName = uploadPath + File.separator + folderPathMake + File.separator + originalName;
        String url =  "/"+ folderPathMake  + "/" + originalName;
        Path savePath=  Paths.get(saveName);

        try {
            //실제 이미지 저장
            uploadFile.transferTo(savePath);

            log.info("url: {}",url);
            //화면에 전달할 DTO 생성
            jsonObject.addProperty("url", url);
            jsonObject.addProperty("responseCode", "succcess");

        } catch (IOException e) {
            jsonObject.addProperty("responseCode", "error");
            e.printStackTrace();
            log.warn("업로드 폴더 생성 실패: " + e.getMessage());
        }


        return jsonObject;
    }




    @Transactional
    public JsonObject createImageThumbnail(MultipartFile uploadFile, String folderPath, CategoryBoard board) {
        log.info("썸네일 이미지 저장 ==========================");
		...

        return jsonObject;
    }




    protected String makeFolder(String folder) {
        String folderPath = folder.replace("/", File.separator);
        File uploadPathFolder = new File(uploadPath,folderPath);
        if (uploadPathFolder.exists() == false){
            uploadPathFolder.mkdirs();
        }
        return folderPath;
    }




    //파일 제거
    public ResponseEntity<Boolean> removeFile(String fileName,String folderPath){
        log.info("이미지 제거 로직");
        ...
    }

}

 

 

profile

minlog

@jimin-log

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!