@3/15/2023
에디터 적용
•
마크다운을 지원하는 TOAST UI Editor를 사용하기로 함
적용 방법
•
CDN 적용
<!-- TOAST UI Editor -->
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
Java
복사
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
<!-- 한국어 패치 -->
<script src="https://uicdn.toast.com/editor/latest/i18n/ko-kr.min.js"></script>
Java
복사
데이터 얻기
•
다음처럼 마크다운 또는 HTML형태로 데이터를 얻을 수 있음
editor.getMarkdown();
editor.getHTML();
Java
복사
•
예시
에디터 내용을 form에 담아 보내기
•
공식 문서도 그렇고 생각보다 에디터 내용을 form에 담아 보내는 방법이 잘 안 보임
•
에디터 작성 폼 html
<!-- 글 등록 -->
<div class="container">
<form th:action="@{/posts}" method="post">
<!-- 제목 -->
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">제목</span>
<input name="title" type="text" class="form-control" aria-label="title input" />
</div>
<!-- 본문 -->
<div id="editor"></div>
<div class="d-flex">
<button type="button" class="btn btn-md btn-primary ms-auto my-3" onclick="submitPost(this)">글 등록</button>
<a class="btn btn-md btn-outline-secondary ms-2 my-3" href="/posts" value="취소">취소</a>
</div>
<input type="hidden" name="content">
<input type="hidden" name="userId" id="userId" value="1"/> <!-- TODO - 유저 권한 기능 추가 후 변경 -->
</form>
</div>
Java
복사
•
Javascript(vanila)
function submitPost(button) {
console.log("submit");
let form = button.closest('form');
let inputs = form.getElementsByTagName('input');
for (let input of inputs) {
if(input.name === 'content') {
input.value = editor.getHTML();
}
}
form.submit();
}
Java
복사
•
에디터 내용을 뽑아서 hidden input에 넣은 후 제출하는 방식으로 구현함
출력
•
출력단에서 HTML이 텍스트 그대로 출력됨
•
기존 타임리프 본문 출력부분의 text를 unexcaped text(utext)로 바꾸면 렌더링이 가능함
<div th:utext="${post.content}" class="row px-3" style="white-space: pre-wrap">
Lorem ipsum pariatur dolor impedit libero minima eaque velit aperiam molestiae numquam doloribus
eveniet et aspernatur placeat.
</div>
Java
복사
◦
하지만 이는 게시글 작성자가 HTML, JS 코드 등을 주입해 XSS(cross site scripting) 공격이 가능하게 하는 보안 취약점을 유발함
◦
그래서 이런 HTML 데이터를 사용할 때는 소독(sanitizing)을 해줘야 하는데, 대표적인 라이브러리가 DOMPurify
◦
Toast UI view는 이 DOMPurify를 이용해 보안 취약점을 해결해주면서 출력물을 예쁘게 꾸며줌
Toast UI Editor Viewer 적용 방법(CDN)
•
CDN - body 태그 안에
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-viewer.js"></script>
HTML
복사
•
CSS - head 태그 안에
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor-viewer.min.css" />
HTML
복사
•
공식 튜토리얼에 나온 방식 → 순수(바닐라)JS는 이대로 하면 오류남(react, vue 등은 안 해봄)
const viewer = new Viewer({
el: document.querySelector('#viewer'),
height: '600px',
initialValue: '# hello'
});
Java
복사
•
CDN + 바닐라JS는 공식 튜토리얼에 있는대로 하면 안 되고, 아래 예제에 있는 대로 적용해야 함
•
html
<div class="viewer"></div>
HTML
복사
•
JS
//Toast UI Editor viewer
const viewer3 = new toastui.Editor({
el: document.querySelector('#viewer'),
initialValue: 'hi'
});
JavaScript
복사
•
thymeleaf까지 적용하면
<script th:inline="javascript">
// Toast UI Editor viewer
const viewer = new toastui.Editor({
el: document.querySelector('#viewer'),
initialValue: [[${post.content}]]
});
</script>
HTML
복사
샘플 - Toast UI Editor
<!DOCTYPE html>
<html>
<head xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- TOAST UI Editor CSS -->
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor.min.css" />
</head>
<body>
<!-- 글 등록 -->
<div class="container">
<form th:action="@{/posts}" method="post">
<!-- 제목 -->
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">제목</span>
<input name="title" type="text" class="form-control" aria-label="title input"/>
</div>
<!-- 본문 -->
<div id="editor"></div>
<div class="d-flex">
<button type="button" class="btn btn-md btn-primary ms-auto my-3" onclick="submitPost(this)">글 등록
</button>
<a class="btn btn-md btn-outline-secondary ms-2 my-3" href="/posts" value="취소">취소</a>
</div>
<input type="hidden" name="content">
<input type="hidden" name="userId" id="userId" th:value="${user.id}"/>
</form>
</div>
<!-- TOAST UI Editor CDN -->
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
<!-- 한국어 패치 -->
<script src="https://uicdn.toast.com/editor/latest/i18n/ko-kr.min.js"></script>
<script>
const Editor = toastui.Editor;
const editor = new Editor({
el: document.querySelector('#editor'),
height: '500px',
initialEditType: 'WYSIWYG',
previewStyle: 'vertical',
language: "ko-KR"
});
function submitPost(button) {
console.log("submit");
let form = button.closest('form');
let inputs = form.getElementsByTagName('input');
for (let input of inputs) {
if(input.name === 'content') {
input.value = editor.getHTML();
}
}
form.submit();
}
</script>
</body>
</html>
HTML
복사
샘플 - Toast UI Viewer
•
Thymeleaf, Javascript 사용 시
<!DOCTYPE html>
<html>
<head xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- TOAST UI Editor Viewer CSS -->
<link rel="stylesheet" href="https://uicdn.toast.com/editor/latest/toastui-editor-viewer.min.css" />
</head>
<body>
<!-- Toast Editor Viewer-->
<div class="viewer"></div>
<!-- Toast Editor Viewer CDN -->
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-viewer.js"></script>
<script th:inline="javascript">
// Toast UI Editor viewer
const viewer = new toastui.Editor({
el: document.querySelector('#viewer'),
height: 'auto',
initialValue: [[${post.content}]]
});
</script>
</body>
</html>
HTML
복사
결과화면
•
에디터
•
뷰어
•
이미지까지 base64로 인코딩되어 잘 올라가는 것을 볼 수 있음
◦
다만 이미지는 DB에 큰 부담을 줄 수 있기 때문에 별도 업로드 처리를 해줘야함
아무것도 없는 버튼만 눌러도 form이 제출되는 상황
<form th:action="@{/posts}" method="post">
<!-- 제목 -->
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">제목</span>
<input name="title" type="text" class="form-control" aria-label="title input" />
</div>
<!-- 본문 -->
<div id="editor"></div>
<div class="d-flex">
<button class="btn btn-md btn-primary ms-auto my-3">글 등록</button>
<a class="btn btn-md btn-outline-secondary ms-2 my-3" href="/posts" value="취소">취소</a>
</div>
<input type="hidden" name="content">
<input type="hidden" name="userId" id="userId" value="1"/> <!-- TODO - 유저 권한 기능 추가 후 변경 -->
</form>
Java
복사
찾아보니 HTML5부터는 버튼이 기본 submit을 내장하고 있다고 함
편집 시 에디터 적용
initialValue에 값을 넣어도 딱히 에디터 창에 넣어주질 않아서 다음 방법으로 설정해줌
editor.setHTML([[${post.content}]]);
SQL
복사
•
postUdateForm.html
<!-- 글 수정 -->
<div class="container">
<form th:action="@{/posts/{postId}/edit(postId = ${post.id})}" method="post">
<!-- 제목 -->
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">제목</span>
<input th:value="${post.title}" name="title" type="text" class="form-control" aria-label="title input"/>
</div>
<!-- 본문 -->
<div id="editor"></div>
<div class="d-flex">
<input type="hidden" name="content">
<button class="btn btn-md btn-primary ms-auto my-3" type="button" onclick="submitPost(this)">글 수정
</button>
<a class="btn btn-md btn-outline-secondary ms-2 my-3" href="/posts">취소</a>
</div>
<input type="hidden" name="userId" id="userId" value="1"/> <!-- TODO - 유저 권한 기능 추가 후 변경 -->
<input type="hidden" name="id" id="postId" th:value="${post.id}"/>
</form>
</div>
<!-- TOAST UI Editor CDN -->
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
<!-- 한국어 패치 -->
<script src="https://uicdn.toast.com/editor/latest/i18n/ko-kr.min.js"></script>
<script th:inline="javascript">
const Editor = toastui.Editor;
const editor = new Editor({
el: document.querySelector('#editor'),
height: '500px',
initialEditType: 'WYSIWYG',
previewStyle: 'vertical',
//initialValue: [[${post.content}]]; 적용이 안됨. 원인 불명
language: "ko-KR"
});
editor.setHTML([[${post.content}]]);
function submitPost(button) {
console.log("submit");
let form = button.closest('form');
let inputs = form.getElementsByTagName('input');
for (let input of inputs) {
if(input.name === 'content') {
input.value = editor.getHTML();
}
}
form.submit();
}
</script>
SQL
복사