블로그 프로젝트를 진행하던 중, 포스팅을 위해서 Textarea만으로는 부족할거야...를 일찍이 깨닫고 Markdown Editor 혹은 위지윅 에디터를 알아봤다.
하지만 나는 생활코딩 백엔드만 수강한 늅늅이 웹 개발자... React나 Vue같은 화려한(?) 프론트엔드 개발은 할 줄 모른다. 간단한 HTML과 JS 지식밖에 없는걸..
당신도 같은 고민을 하고 있으면 Summernote가 제격이다!
Summernote의 홈페이지. 나도 놀러가고 싶다..
https://summernote.org/
Summernote는 기본적으로 위지윅 (WYSIWYG) 에디터다. 뭔가 말이 어려워서 거창해 보이지만, 우리가 흔히 네이버 블로그, 카페 등에서 보는 텍스트 에디터가 위지윅 에디터다. 그럼 Summernote의 장/단점은 뭐가 있을까?
https://summernote.org/getting-started/ 이 곳을 참고하도록 하자. CDN을 적용시켜 간단히 적용이 가능하다.
개요
summernote로 작성하는 글의 본문에 이미지를 넣으려면, insertImage 아이콘을 눌러 적용하면 된다. 다만 기본 설정대로 이용하면 이미지를 Base64로 바꾼 값을 저장해야 하는데, 이게 용량이 꽤 크기 때문에 DB에 부담이 된다. 따라서 클라우드에 저장 후 URL을 받아 본문에 img태그를 삽입한 형태로 저장하는 방법이 바람직하다.
multer, multer-s3
multer와 multer-s3 패키지를 통해 파일 업로드 방법을 구성해야 한다. 해당 내용은 너무나 관련 포스팅이 많기 때문에... 정말 좋은 예시 링크를 첨부하도록 하겠다. https://velog.io/@neity16/NodeJS-S3-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0-multer
router.js
const { postWriteView, insertPost, insertImage } = requires('./postController');
const upload = require('./multer');
router.get('/write', postWriteView); // summernote editor
router.post('/insertImage', upload.single('img'), insertImage); // file upload 처리
router.post('/insertPost', insertPost) // 글을 처리해줄 경로
module.exports = router;
main.js
...
app.use('view engine, 'ejs');
app.use('/', require('./router'));
const PORT = process.env.PORT || 3000;
app.listen(PORT, console.log("Server start at port : "+PORT));
...
const postWriteView = (req, res) => {
res.render("postWrite", {});
}
const insertImage = async(req, res) => {
imgurl = '';
if(req.file !== undefined) {
var imgurl = req.file.location; // router에서 붙인 multer가 반환한 url (aws s3 object url)
}
res.json(imgurl); // json 형태로 반환해주어야 View에서 처리가 가능하다.
}
const insertPost = (req, res) => {
console.log(req.body);
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>summernote</title>
<!-- CDN 추가 시작 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<!-- CDN 추가 부분 끝 -->
</head>
<body>
<form method="post" action="/insertPost">
<textarea id="summernote" name="editordata"></textarea>
<button type="submit">등록</button>
</form>
</body>
<script>
// summernote init
$(function(){
$('#summernote').summernote({
placeholder: '내용',
tabsize: 2,
focus: true,
callbacks:{
// onImageUpload를 통해 이미지 업로드시 동작 개조 가능
// 개조하지 않으면 Base64로 이미지가 전환돼서 img태그로 바뀐뒤 본문에 추가된다.
onImageUpload: function(files){
sendFile(files[0], this);
}
}
});
});
function sendFile(file, editor){
data = new FormData()
data.append("img", file)
// id 'img'로 file form 데이터 추가
$.ajax({
data: data,
type: "POST",
// 이미지 처리를 할 url
url: "/insertImage",
cache: false,
contentType: false,
// multer-s3를 활용하므로 multipart/form-data형태로 넘겨줘야 한다.
enctype: "multipart/form-data",
processData: false,
success: function (response) {
var imgurl = $('<img>').attr({
'src': response,
// json형태로 반환되는 주소.
'crossorigin': 'anonymous',
// crossorigin attr을 삽입하지 않으면 CORS에러가 난다!
});
$("#summernote").summernote("insertNode", imgurl[0]);
// insertNode는 html tag를 summernote 내부에 삽입해주는 기능.
},
})
}
</script>
</html>
summernote에 이미지가 삽입된 모습.
mysql DB에 저장된 모습이다. 필자의 DB 결과의 세 번째 항목이 위 코드상으론 콘솔에 찍힐 것이다.