Java/Spring

[Spring] summernote ์—๋””ํ„ฐ๋กœ ์ด๋ฏธ์ง€๊ฐ€ ํฌํ•จ๋œ ๊ธ€ ์—…๋กœ๋“œํ•˜๊ธฐ

๋ฒผ๋ฆฌ01 2024. 2. 22. 15:30

๐Ÿ“Œ ํ™˜๊ฒฝ

Spring Tool Suite 3 (STS3)

์Šคํ”„๋ง ๋ ˆ๊ฑฐ์‹œ ํ”„๋กœ์ ํŠธ

 

๐Ÿ“Œ ํ๋ฆ„

1. summernote api๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

1-1. ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๊ณผ ์ถฉ๋Œ์ด ์ผ์–ด๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” summernote lite๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

2. ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด `pom.xml`์— `gson` `common-io` `commons-fileupload` ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. 

3. `textarea` id๋ฅผ summernote๋กœ ์ž‘์„ฑํ•˜๋ฉด summernote ์—๋””ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์–ด๋จธ ๋ฏธ์นœ ํƒœ๊ทธ๊ฐ€ ๋™์ž‘ํ•ด๋ฒ„๋ฆผ

`<textarea id="summernote"></textarea>`

4. ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์œ„์น˜์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด ์—๋””ํ„ฐ์— summernote์—์„œ ์ œ๊ณตํ•˜๋Š” callbakcs ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

5. ajax๋กœ Controller ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•œ๋‹ค. ์ด๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

๐Ÿ“Œ ๊ตฌํ˜„

1. summernote api๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

1-1. summernote๋Š” ํƒœ๊ทธ ์ž์ฒด๋ฅผ ๋ชฝ๋•… ์ €์žฅํ•˜๋Š” ์—๋””ํ„ฐ๋กœ ์ฝ์–ด์˜ฌ ๋•Œ ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๊ณผ ์ถฉ๋Œ์ด ์ผ์–ด๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” summernote lite๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. 

 

 

 

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

 

 

Getting Started๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

 

 

`summernote-lite.css`

`summetnote-lite.js`

`lang` ํด๋” ๋‚ด `summetnote-ko-KR.js`

`font` ํด๋” ์ „์ฒด

 

์š”๋ ‡๊ฒŒ ๋„ค๊ฐ€์ง€๋งŒ ์‚ฌ์šฉํ•  ๊ฑฐ๋‹ค!

 

 

 

 

`resources`์— ๋„ฃ์–ด์ฃผ๊ณ  ์‚ฌ์šฉํ•  ํŽ˜์ด์ง€์—์„œ script ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. 

`css`๋Š” ํ—ค๋”์—, `script`๋Š” `</body>` ํƒœ๊ทธ ๋ฐ”๋กœ ์œ„์— ๋„ฃ์–ด์ค€๋‹น

 

<!-- summernote  -->
<link rel="stylesheet" href="${contextPath }/resources/summernote/summernote-lite.css">

.
.
.

<!--  summernote -->
<script src="${contextPath }/resources/summernote/summernote-lite.js"></script>
<script src="${contextPath }/resources/summernote/lang/summernote-ko-KR.js"></script>

 

 

 

 

2. ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด `pom.xml`์— `gson` `common-io` `commons-fileupload` ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. 

 

<!--  ํŒŒ์ผ ์—…๋กœ๋“œ -->

<!--  json : gson -->
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.9</version>
</dependency>

<!--  fileutils -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

<!-- commons-fileupload -->
<!-- multipart๋“ฑ์„ ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ์Œ -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

 

 

3. `textarea` id๋ฅผ summernote๋กœ ์ž‘์„ฑํ•˜๋ฉด summernote ์—๋””ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

<form method="post" action="${contextPath }/insert">
    <input class="form-control" placeholder="์ œ๋ชฉ" style="width:100%" name="title" required/>
    <textarea id="summernote" name="content" required></textarea>
    <button class="btn btn-primary">์ž‘์„ฑ</button>
</form>

 

 

 

 

 

4. ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์œ„์น˜์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•ด ์—๋””ํ„ฐ์— summernote์—์„œ ์ œ๊ณตํ•˜๋Š” callbacks ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

5. ajax๋กœ Controller ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•œ๋‹ค. ์ด๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. 

onImageUpload๋Š” ์—๋””ํ„ฐ์— ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ๋˜๋ฉด ์‹คํ–‰๋œ๋‹ค.

ajax์˜ insertImage๋Š” ์—๋””ํ„ฐ์— ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€๋ฅผ ๋ถ™์—ฌ์ฃผ๋Š” ํ•จ์ˆ˜.

 

$(document).ready(function() {
	
//	summernote ์—๋””ํ„ฐ ์„ค์ • 
	$('#summernote').summernote({
		  height: 300,
		  minHeight: null,
		  maxHeight: null,
		  focus: true,
		  lang: "ko-KR",
		  placeholder: '๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”.',
//		 ์—๋””ํ„ฐ ๋ฆฌ์‚ฌ์ด์ฆˆ ๊ธˆ์ง€ 
		  disableResizeEditor : true,
//		  ์ฝœ๋ฐฑ ํ•จ์ˆ˜
//		  omImageUpload ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ ๋˜์—ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜
		  callbacks : {
			onImageUpload: function (files, editor, welEditable){
				 
				 	// ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
                    for (var i = files.length - 1; i >= 0; i--) {
                        var fileName = files[i].name      

                        uploadSummernoteImageFile(files[i], this, fileName)
                    }// for
                    }// onImageUpload
		}// callbacks 
		 
	
	}); // summernote
}


// ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ajax
function uploadSummernoteImageFile(file, el, caption) {
    data = new FormData()
    data.append('file', file)
    $.ajax({
        data: data,
        type: 'POST',
        url: 'uploadSummernoteImageFile',
        contentType: false,
        enctype: 'multipart/form-data',
        processData: false,
        dataType : 'json',
        success: function (data) {
            console.log(data);
            console.log(data.url);
            console.log(data.responseCode);
            
            $("#summernote").summernote(
                'insertImage', data.url
             );
        },
    })
}

 

 

 

 

`UploadController.java`

 

//	summernote ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
@PostMapping("/uploadSummernoteImageFile")
@ResponseBody
public String uploadSummernoteImageFile(@RequestParam("file")MultipartFile multipartFile, HttpServletRequest request){

//	json ๊ฐ์ฒด ์–ป๊ธฐ
//	JSONObject์™€ ๋‹ค๋ฅธ ํด๋ž˜์Šค์ž„ 
//	JSONObject == org.json.simple
//	JsonObject == com.google.json
	JsonObject jsonObject = new JsonObject();

//	์ €์žฅ ๊ฒฝ๋กœ ์„ค์ •(์ ˆ๋Œ€๊ฒฝ๋กœ)
	String uploadPath = request.getSession().getServletContext().getRealPath("resources"); 
	String fileRoot = uploadPath + "/images";

//	์—…๋กœ๋“œ ํŒŒ์ผ ์›๋ณธ ์ด๋ฆ„, ํ™•์žฅ์ž ์ถ”์ถœ
	String originalFileName = multipartFile.getOriginalFilename();
	String extension = originalFileName.substring(originalFileName.lastIndexOf("."));

//	์ƒˆ๋กœ์šด ํŒŒ์ผ๋ช… ์ƒ์„ฑ(์ค‘๋ณต ๋ฐฉ์ง€) ๋žœ๋ค UUID
//	ํŒŒ์ผ๋ช…์— ํ•œ๊ธ€์ด ๋“ค์–ด๊ฐ€๋ฉด ์˜ค๋ฅ˜, ๊ฐ™์€ ์ด๋ฆ„์˜ ๋‹ค๋ฅธ ํŒŒ์ผ์ด ๋“ค์–ด์™”์„ ๊ฒฝ์šฐ ๋ฎ์–ด์จ์ง€๋Š” ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ
	String saveFileName = UUID.randomUUID() + "";

	File targetFile = new File(fileRoot + saveFileName);

	try {
//	์—…๋กœ๋“œ ํŒŒ์ผ์˜ inputstream ์–ป๊ธฐ
        InputStream fileStream = multipartFile.getInputStream();

//	ํŒŒ์ผ์„ ์ง€์ •๋œ ๊ฒฝ๋กœ(uploadPath)์— ์ €์žฅ
        FileUtils.copyInputStreamToFile(fileStream, targetFile);

//	Json ๊ฐ์ฒด์— ์ด๋ฏธ์ง€ url๊ณผ ์‘๋‹ต ์ฝ”๋“œ ์ถ”๊ฐ€
//	ํ”„๋กœ์ ํŠธ resources์— ๋„ฃ์—ˆ์œผ๋‹ˆ๊นŒ ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ๋ถˆ๋Ÿฌ์˜ด
//	{"url" : ํ”„๋กœ์ ํŠธ๋ช…/resources/images/ํŒŒ์ผ๋ช…}
        jsonObject.addProperty("url", "/ํ”„๋กœ์ ํŠธ๋ช…/resources/images/" + saveFileName);
        jsonObject.addProperty("responseCode", "success");

        } catch (IOException e) {
    //	ํŒŒ์ผ ์ €์žฅ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ ์‚ญ์ œ, ์—๋Ÿฌ ์ฝ”๋“œ ์ถ”๊ฐ€ 
            FileUtils.deleteQuietly(targetFile);
            jsonObject.addProperty("responseCode", "error");
            e.printStackTrace();
        }

    return jsonObject.toString();

}

 

 

request.getSession().getServletContext().getRealPath("resources"); 

์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค. ํ”„๋กœ์ ํŠธ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์—†๊ณ  `.metadata`์— ๋“ค์–ด๊ฐ.

๋งฅ์—์„œ๋Š” command + shift + . ์œผ๋กœ ์ˆจ๊ฒจ์ง„ ํด๋”๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

 

์‹คํ–‰ํ–ˆ์„ ๋•Œ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์ฝ˜์†”์— ์š”๋ ‡๊ฒŒ ๋œจ๋ฉด ์„ฑ๊ณต

 

 

 

@PostMapping("insert")
public String noticeInsert(HttpSession session, Board board) {
    Member member = (Member)session.getAttribute("member");
    board.setId(member.getId());
    board.setNickname(member.getNickname());

    int result = boardService.insert(board);

    return "redirect:/listAll";
}

 

 

์ด๊ฑด form ์•ก์…˜๋˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜(๊ธ€ ์ž‘์„ฑ ํ•จ์ˆ˜)

form ํƒœ๊ทธ์—์„œ input์— ์ž‘์„ฑํ–ˆ๋˜ name์œผ๋กœ getter๊ฐ€ ๋™์ž‘ํ•˜๋ฉด์„œ ๊ฐ์ฒด๋ฅผ ๋งคํ•‘ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ `Board`๋กœ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Œ.

 

 

 

 

 

๋ฏธ๋ฆฌ๋ณด๊ธฐ๋„ ์ž˜ ๊ตฌํ˜„๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹น

 

 

 

 

 

 

๐Ÿ“Œ ์ฐธ๊ณ 

 

[์ธ๋จธ๋…ธํŠธ] ์ธ๋จธ๋…ธํŠธ ์—๋””ํ„ฐ์— ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ์›ํ•˜๋Š” ๋‚ด๋ถ€ ๊ฒฝ๋กœ์— ์ €์žฅํ•˜๊ณ  ๊ฒฝ๋กœ๊ฐ’์„ DB์—

ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์€ ๋ณธ์ธ์˜ ์ดํ•ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฏ€๋กœ ํ‹€๋ฆฐ ๋ถ€๋ถ„์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹ ๋‹ค๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค! ---------------------------------------------------- Spring์ด๋“  summernote๋“  ์ „ํ˜€

mabb.tistory.com

 

Summernote ์‚ฌ์šฉ๋ฒ• , ์ธ๋จธ๋…ธํŠธ ์‚ฌ์šฉ๋ฒ• - ์‚ฝ์งˆ์ค‘์ธ ๊ฐœ๋ฐœ์ž

- ์ธ๋จธ๋…ธํŠธ์‚ฌ์šฉ๋ฒ• ์ •๋ฆฌ - ํ™ˆํŽ˜์ด์ง€์—์„œ ๊ธ€์„ ์“ฐ๋Š” ๋ถ€๋ถ„์— ์ผ๋ฐ˜ ํ…์ŠคํŠธ ํ˜•ํƒœ์˜ ๊ธ€ ์“ฐ๊ธฐ๋งŒ ์ง€์›ํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๊ธฐ ๋ถˆํŽธํ•˜๋‹ค. ์ด๋Ÿฌํ•œ ์  ๋•Œ๋ฌธ์— ๋Œ€๋ถ€๋ถ„์˜ ํ™ˆํŽ˜์ด์ง€์—์„œ๋Š” ์›น์—๋””ํ„ฐ๋ฅผ ์ง€์›

programmer93.tistory.com

 

Summernote[์ธ๋จธ๋…ธํŠธ] (1) : ์›น ์—๋””ํ„ฐ ๊ธฐ๋ณธ ์„ค์ •๊ณผ ์‚ฌ์šฉ ๋ฐฉ๋ฒ• Blog(6)

์ธ๋จธ๋…ธํŠธ[Summernote] ํˆด๋ฐ” ๊ตฌ์„ฑ์š”์†Œ์™€ ์†์„ฑ ์„ค์ •๋ฐฉ๋ฒ• ๊ทธ๋ฆฌ๊ณ  ์ถ”ํ›„ ์žˆ์„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ์— ๋Œ€ํ•œ ์˜ˆ๊ณ  ๋‚ด์šฉ์ด ๋‹ด๊ฒจ ์žˆ์Šต๋‹ˆ๋‹ค.

sirobako.co.kr