본문 바로가기
개발/Spring

[SpringBoot] Summernote Editor(이미지,동영상 삽입) 적용하기 -1

by 코딩하는 흰둥이 2024. 7. 11.
Summernote Editor 다운로드 주소

https://summernote.org/#google_vignette

 

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

 

 

화살표를 클릭하거나

 

 

 

해당 메뉴에 들어가서 다운로드하면 된다

 

 

 

폴더 경로

 

 

현재 Bootstrap을 적용 중이라

' /resources/static/bootstrap/js/summernote ' 위치에 파일을 넣었는데

 

' /resources/static/ ' 경로 안에만 넣으면 된다

 

 

html
<div class="form-group">
	// Summernote 적용시킬 textarea 태그
    <textarea class="form-control" id="summernote" name="content" type="text"></textarea>
</div>


<!-- thymeleaf 용 -->
<!-- summernote -->
<script th:src="@{/bootstrap/js/summernote/summernote-lite.js}"></script>
<script th:src="@{/bootstrap/js/summernote/lang/summernote-ko-KR.js}"></script>
<link rel="stylesheet" th:href="@{/bootstrap/js/summernote/summernote-lite.css}">


<!-- thymeleaf 해제  -->
<!-- summernote -->
<script src="/bootstrap/js/summernote/summernote-lite.js"></script>
<script src="/bootstrap/js/summernote/lang/summernote-ko-KR.js"></script>
<link rel="stylesheet" href="/bootstrap/js/summernote/summernote-lite.css">

 

 

 

javascript
// 이미지 업로드
function imageUpload(file){
    let formData = new FormData();
    formData.append("file", file);

    $.ajax({
       url : "/upload/imageUpload",
       type : "POST",
       data : formData,
        // contentType, processData 설정 안하면 TypeError: Illegal invocation 오류가 발생한다
        contentType: false,
        processData: false,
       encType : "multipart/form-data",
       success : function (data) {
           // 글에 이미지 넣을때 크기 설정
           // $("#summernote").summernote("insertImage", "/uploadPath/image/"+data, function (data){
           //     data.css("width" , "100%");
           // });

           // 글에 이미지 넣기
           $("#summernote").summernote("insertImage", "/uploadPath/image/"+data);
       },
        error(e){
           console.log("error : "+ e);
        }
    });
}

// 이미지 삭제
function deleteFile(fileName) {
    let formData = new FormData();
    formData.append("file", fileName);

    $.ajax({
        url : "/upload/imageDelete",
        type : "POST",
        data : formData,
        // contentType, processData 설정 안하면 TypeError: Illegal invocation 오류가 발생한다
        contentType: false,
        processData: false,
        encType : "multipart/form-data"
    });
}



$(document).ready(function (){
    // textarea summernote 적용하기
    $("#summernote").summernote({
        codeviewFilter: false,                              // 코드 보기 필터 비활성화
        codeviewIframeFilter: false,                        // 코드 보기 iframe 필터 비활성화

        height: 400,                                        // 에디터 높이
        minHeight: null,                                    // 최소 높이
        maxHeight: null,                                    // 최대 높이
        lang: "ko-KR",                                      // 에디터 한글 설정
        focus : true,                                       // 에디터 포커스 설정
        toolbar: [
            ['fontname', ['fontname']],                     // 글꼴 설정
            ['fontsize', ['fontsize']],                     // 글자 크기
            ['style', ['bold', 'italic', 'underline','strikethrough', 'clear']],  // 글자 스타일 설정
            ['color', ['forecolor','color']],               // 글자색
            ['table', ['table']],                           // 표 생성
            ['insert', ['picture', 'link','video']],        // 이미지, 링크 , 동영상
            ['para', ['ul', 'ol', 'paragraph']],            // 문단 스타일 설정
            ['height', ['height']],                         // 줄간격
            ['view', ['codeview','fullscreen', 'help']]     // 코드보기, 전체화면, 도움말
        ],
        fontNames: ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New','맑은 고딕','궁서','굴림체','굴림','돋음체','바탕체'], // 추가한 글꼴
        fontSizes: ['8','9','10','11','12','14','16','18','20','22','24','28','30','36','50','72'], // 추가한 폰트사이즈
        callbacks : {
            // 파일 업로드
            onImageUpload : function (files) {
                for(let i=0; i < files.length; i++){
                    // 이미지가 여러개일 경우
                    imageUpload(files[i]);
                }
            },
            // 파일 삭제
            onMediaDelete: function ($target){
                if(confirm("이미지를 삭제하시겠습니까?")){
                    let fileName = $target.attr('src').split('/').pop();
                    deleteFile(fileName);
                }
            }
        }
    });
});

 

 

Controller
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

@RestController
@Tag(name="Swagger upload", description = "upload")
@RequestMapping("/upload/*")
public class ImageUpload {

    // 파일 업로드 경로
    final Path FILE_ROOT = Paths.get("./").toAbsolutePath().normalize();
    private String uploadPath = FILE_ROOT.toString() + "/upload/image/";

    @Operation(summary = "이미지 업로드 ", description = "이미지를 서버에 업로드한다.")
    @PostMapping("/imageUpload")
    public ResponseEntity<?> imageUpload(@RequestParam MultipartFile file) throws Exception{

        System.err.println("이미지 업로드");

        try {
            // 업로드 파일의 이름
            String originalFileName = file.getOriginalFilename();

            // 업로드 파일의 확장자
            String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));

            // 업로드 된 파일이 중복될 수 있어서 파일 이름 재설정
            String reFileName = UUID.randomUUID().toString() + fileExtension;

            // 업로드 경로에 파일명을 변경하여 저장
            file.transferTo(new File(uploadPath, reFileName));

            // 파일이름을 재전송
            return ResponseEntity.ok(reFileName);
        }catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.badRequest().body("업로드 에러");
        }
    }


    @Operation(summary = "이미지 삭제 ", description = "서버에 저장된 이미지를 삭제한다.")
    @PostMapping("/imageDelete")
    public void imageDelete(@RequestParam String file) throws Exception{

        System.err.println("이미지 삭제");

        try {
            Path path = Paths.get(uploadPath, file);
            Files.delete(path);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

}

 

 

파일 업로드 경로 설정에 관한 건 여기를 참고하길 바란다

https://greed-yb.tistory.com/253

 

[SpringBoot] 외부 파일 불러오기

기본적으로 /resources/static/ 폴더 안에 파일을 읽어오지만그 외의 외부폴더에서 파일을 참조할 경우 보안상의 문제로 파일을 불러오지 않는다 파일이 동적으로 생성되거나 관리가 되어야 하는

greed-yb.tistory.com

 

 

 

 

application.properties
# file upload
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

파일 업로드 용량 설정

 

 

 

 

 

TEST

Summernote 가 적용된 페이지

 

 

 

 

 

 

또는

파일 드랍이 가능하다

 

 

 

1. 이미지 업로드 

이미지가 정상적으로 출력 및 업로드가 되었다

 

 

2. 이미지 삭제

사진 삭제 클릭 시 업로드한 이미지도 삭제

 

 

 

3. 서버로 데이터 전송

 

 

 

{title='이것은 제목!!', content='<p>첫줄은 글</p><p><img src="/uploadPath/image/d4eea150-a37f-4f50-a9a3-e9e57afeef23.png" style="font-size: 1rem; width: 142px;"></p><p>장마로 인해서 너무 습해!</p><p><img src="/uploadPath/image/4b0f2e9e-9338-4a9e-be33-b5bade5b8537.png" style="width: 144px;"></p><p>에어컨 때문에 추워!</p>'}

이미지는 업로드된 경로 주소로 들어가 있고

작성한 글도 다 확인이 되고 있다

(서버로 데이터 전송 코드는 올려두지 않았다)

 


처음에 CKEditor 로  설정을 잡다가 계속 오류가 나서 

Summernote 를 처음 사용해 보았는데 매우 만족이었다

 

 

 

https://greed-yb.tistory.com/266

 

[SpringBoot] Summernote Editor(게시판 글 상세보기) 적용하기 - 2

https://greed-yb.tistory.com/265 [SpringBoot] Summernote Editor(이미지,동영상 삽입) 적용하기Summernote Editor 다운로드 주소https://summernote.org/#google_vignette Summernote - Super Simple WYSIWYG editorSuper Simple WYSIWYG Editor on Bo

greed-yb.tistory.com

다음글 - 목록 및 상세보기

댓글