21.11.22
#### 요구사항 - 기능 요구사항 __기본기능__ v 권한 구분 관리자 권한 구현 v 깃헙아이디, 구글아이디로 로그인 가능 v 블로그 기본 CRUD v 연관글 등록 v 이미지 삽입시 이미지서버에 선업로드 후 URL반환 v 댓글과 대댓글 구현 v 랜드화면은 무한스크롤 구현 v 카테고리 화면에서는 일반 페이징 v 토스트 에디터 사용 v 게시물 조회수 순위별 v 최근 게시물 v 최근 코멘트 노출 v 블로그 태그별 검색과 태그 보이기 v 일반 검색기능 v 썸네일 링크로 추가 -----11.18 - 카테고리 목록 설정 __추가기능__ - TOC - 글 1분단위 자동저장 - 새로운글 알람 보내기 - RSS피드 - 공유하기 기능 - 글 포스팅시 자동 커밋 푸시
This commit is contained in:
@@ -8,8 +8,6 @@ import myblog.blog.article.service.ArticleService;
|
|||||||
import myblog.blog.category.dto.CategoryForView;
|
import myblog.blog.category.dto.CategoryForView;
|
||||||
import myblog.blog.category.dto.CategoryNormalDto;
|
import myblog.blog.category.dto.CategoryNormalDto;
|
||||||
import myblog.blog.category.service.CategoryService;
|
import myblog.blog.category.service.CategoryService;
|
||||||
import myblog.blog.comment.domain.Comment;
|
|
||||||
import myblog.blog.comment.dto.CommentDto;
|
|
||||||
import myblog.blog.comment.dto.CommentDtoForSide;
|
import myblog.blog.comment.dto.CommentDtoForSide;
|
||||||
import myblog.blog.comment.service.CommentService;
|
import myblog.blog.comment.service.CommentService;
|
||||||
import myblog.blog.member.auth.PrincipalDetails;
|
import myblog.blog.member.auth.PrincipalDetails;
|
||||||
|
|||||||
@@ -80,8 +80,4 @@ public class Article extends BasicEntity {
|
|||||||
this.category = category;
|
this.category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteArticle(){
|
|
||||||
this.articleTagLists = null;
|
|
||||||
this.parentCommentList = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
|
|||||||
@@ -7,24 +7,27 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class UserInfoFactory {
|
public class UserInfoFactory {
|
||||||
|
|
||||||
|
private final static Map<String, Function<OAuth2User, Oauth2UserInfo>> userInfoFactoryMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
userInfoFactoryMap = new HashMap<>();
|
||||||
|
userInfoFactoryMap.put("google", GoogleUserInfo::new);
|
||||||
|
userInfoFactoryMap.put("facebook", FacebookUserInfo::new);
|
||||||
|
userInfoFactoryMap.put("kakao", FacebookUserInfo::new);
|
||||||
|
userInfoFactoryMap.put("naver", FacebookUserInfo::new);
|
||||||
|
}
|
||||||
|
|
||||||
public Oauth2UserInfo makeOauth2UserinfoOf(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
|
public Oauth2UserInfo makeOauth2UserinfoOf(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
|
||||||
|
|
||||||
if (oAuth2UserRequest.getClientRegistration().getRegistrationId().equals("google")) {
|
return userInfoFactoryMap
|
||||||
return new GoogleUserInfo(oAuth2User.getAttributes());
|
.get(oAuth2UserRequest.getClientRegistration().getRegistrationId())
|
||||||
} else if (oAuth2UserRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
|
.apply(oAuth2User);
|
||||||
return new FacebookUserInfo(oAuth2User.getAttributes());
|
|
||||||
} else if (oAuth2UserRequest.getClientRegistration().getRegistrationId().equals("kakao")) {
|
|
||||||
return new KakaoUserInfo(oAuth2User.getAttributes());
|
|
||||||
} else if (oAuth2UserRequest.getClientRegistration().getRegistrationId().equals("naver")) {
|
|
||||||
return new NaverUserInfo(oAuth2User.getAttribute("response"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("지원하지 않는 Oauth 인증 시도입니다");}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package myblog.blog.member.auth.userinfo;
|
package myblog.blog.member.auth.userinfo;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -9,8 +10,8 @@ public class FacebookUserInfo implements Oauth2UserInfo {
|
|||||||
|
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
public FacebookUserInfo(Map<String, Object> attributes) {
|
public FacebookUserInfo(OAuth2User oAuth2User) {
|
||||||
this.attributes = attributes;
|
this.attributes = oAuth2User.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package myblog.blog.member.auth.userinfo;
|
package myblog.blog.member.auth.userinfo;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -9,8 +10,8 @@ public class GoogleUserInfo implements Oauth2UserInfo{
|
|||||||
|
|
||||||
private Map<String,Object> attributes;
|
private Map<String,Object> attributes;
|
||||||
|
|
||||||
public GoogleUserInfo(Map<String, Object> attributes) {
|
public GoogleUserInfo(OAuth2User oAuth2User) {
|
||||||
this.attributes = attributes;
|
this.attributes = oAuth2User.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package myblog.blog.member.auth.userinfo;
|
package myblog.blog.member.auth.userinfo;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -9,8 +10,8 @@ public class KakaoUserInfo implements Oauth2UserInfo {
|
|||||||
|
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
public KakaoUserInfo(Map<String, Object> attributes) {
|
public KakaoUserInfo(OAuth2User oAuth2User) {
|
||||||
this.attributes = attributes;
|
this.attributes = oAuth2User.getAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package myblog.blog.member.auth.userinfo;
|
package myblog.blog.member.auth.userinfo;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -9,8 +10,8 @@ public class NaverUserInfo implements Oauth2UserInfo {
|
|||||||
|
|
||||||
private Map<String, Object> attributes;
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
public NaverUserInfo(Map<String, Object> attributes) {
|
public NaverUserInfo(OAuth2User oAuth2User) {
|
||||||
this.attributes = attributes;
|
this.attributes = oAuth2User.getAttribute("response");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ public interface Oauth2UserInfo {
|
|||||||
|
|
||||||
Map<String, Object> getAttributes();
|
Map<String, Object> getAttributes();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package myblog.blog.member.doamin;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Table(name = "persistent_logins")
|
|
||||||
@Entity
|
|
||||||
@Getter
|
|
||||||
public class PersistentLogins {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Column(length = 64)
|
|
||||||
private String series;
|
|
||||||
|
|
||||||
@Column(nullable = false, length = 64)
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@Column(nullable = false, length = 64)
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@Column(name = "last_used", nullable = false, length = 64)
|
|
||||||
private LocalDateTime lastUsed;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const myModal = new bootstrap.Modal(document.getElementById('thumbnailModal'), {keyboard: false});
|
||||||
|
|
||||||
const thumbBox = document.getElementById("thumbBox");
|
const thumbBox = document.getElementById("thumbBox");
|
||||||
const uploadThumbBtn = document.getElementById("thumbnail");
|
const uploadThumbBtn = document.getElementById("thumbnail");
|
||||||
const thumbDel = document.getElementById("thumbDelBtn");
|
const thumbDel = document.getElementById("thumbDelBtn");
|
||||||
const previewThumb = document.getElementById("thumbnailPreView");
|
const previewThumb = document.getElementById("thumbnailPreView");
|
||||||
const thumbUrl = document.getElementById("thumbnailUrl")
|
const thumbUrl = document.getElementById("thumbnailUrl");
|
||||||
|
const thumbUrlUploadBtn = document.getElementById("thumbnail-url-upload-btn");
|
||||||
|
|
||||||
function uploadImg(input) {
|
function uploadImg(input) {
|
||||||
|
|
||||||
@@ -21,13 +27,17 @@ function uploadImg(input) {
|
|||||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||||
|
|
||||||
thumbUrl.value = xhr.response;
|
thumbUrl.value = xhr.response;
|
||||||
|
previewThumb.src = thumbUrl.value;
|
||||||
|
|
||||||
|
// 썸네일 등록은 서버에서 하도록 리팩토링할것
|
||||||
|
// const reader = new FileReader();
|
||||||
|
// reader.onload = e => {
|
||||||
|
// previewThumb.src = e.target.result;
|
||||||
|
// }
|
||||||
|
// reader.readAsDataURL(input.files[0])
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = e => {
|
|
||||||
previewThumb.src = e.target.result;
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(input.files[0])
|
|
||||||
thumbBox.style.display = ''
|
thumbBox.style.display = ''
|
||||||
|
myModal.hide();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert("이미지가 정상적으로 업로드되지 못했습니다.")
|
alert("이미지가 정상적으로 업로드되지 못했습니다.")
|
||||||
@@ -37,6 +47,16 @@ function uploadImg(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
thumbUrlUploadBtn.addEventListener("click", () =>{
|
||||||
|
const thumbUrlUploadInput = document.getElementById("thumbnail-url-upload-input");
|
||||||
|
const url = thumbUrlUploadInput.value;
|
||||||
|
previewThumb.src = url;
|
||||||
|
thumbUrl.value = url;
|
||||||
|
thumbBox.style.display = ''
|
||||||
|
myModal.hide();
|
||||||
|
})
|
||||||
|
|
||||||
uploadThumbBtn.addEventListener("change", e => {
|
uploadThumbBtn.addEventListener("change", e => {
|
||||||
uploadImg(e.target);
|
uploadImg(e.target);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -62,15 +62,35 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center mt-5 mb-3 g-0">
|
<div class="row justify-content-center mt-5 mb-3 g-0">
|
||||||
|
|
||||||
<div class="d-flex">
|
<div class="d-flex flex-row-reverse">
|
||||||
<label class="btn btn-secondary me-auto" for="thumbnail">썸네일 파일</label>
|
|
||||||
<input type="file" id="thumbnail" name="thumbnail" accept="image/*" class="d-none">
|
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#thumbnailModal">
|
||||||
<!-- <div>-->
|
썸네일 등록
|
||||||
<!-- <input type="text" id="thumbnailUrlSource" name="thumbnail" accept="text">-->
|
</button>
|
||||||
<!-- <label class="btn btn-secondary" for="thumbnailUrlSource">썸네일 URL</label>-->
|
|
||||||
<!-- </div>-->
|
<div class="modal fade" id="thumbnailModal" tabindex="-1" aria-labelledby="thumbnailModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="thumbnailModalLabel">썸네일 등록</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
|
||||||
|
<input type="text" id="thumbnail-url-upload-input" class="form-control">
|
||||||
|
<button class="btn btn-secondary" id="thumbnail-url-upload-btn">URL 업로드</button>
|
||||||
|
<label class="btn btn-secondary" for="thumbnail">파일 업로드</label>
|
||||||
|
<input type="file" id="thumbnail" name="thumbnail" accept="image/*" class="d-none">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<form class="" method="post" enctype="multipart/form-data" th:object="${articleDto}"
|
<form class="" method="post" enctype="multipart/form-data" th:object="${articleDto}"
|
||||||
th:action="|@{/article/edit(articleId=${articleDto.getId()})}|" id="writeArticleForm">
|
th:action="|@{/article/edit(articleId=${articleDto.getId()})}|" id="writeArticleForm">
|
||||||
|
|
||||||
@@ -119,6 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--scripts-->
|
<!--scripts-->
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
|
<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
|
||||||
<script th:replace="layout/fragments.html :: tag"></script>
|
<script th:replace="layout/fragments.html :: tag"></script>
|
||||||
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
||||||
|
|||||||
@@ -134,6 +134,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--page e-->
|
<!--page e-->
|
||||||
|
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -134,6 +134,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--page e-->
|
<!--page e-->
|
||||||
|
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -134,6 +134,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--page e-->
|
<!--page e-->
|
||||||
|
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<div class="main ">
|
<div class="main ">
|
||||||
<div class="carousel-inner ">
|
<div class="carousel-inner ">
|
||||||
<div class="carousel-item active">
|
<div class="carousel-item active" style="cursor: default">
|
||||||
<img th:src="${article.getThumbnailUrl()}" class="w-100 vh-100 cover"
|
<img th:src="${article.getThumbnailUrl()}" class="w-100 vh-100 cover"
|
||||||
alt="...">
|
alt="...">
|
||||||
<div class="card-img-overlay text-shadow text-white text-center row justify-content-center align-content-center p-0">
|
<div class="card-img-overlay text-shadow text-white text-center row justify-content-center align-content-center p-0">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea name="contents" id="contents" th:text="${article.getContent()}" hidden></textarea>
|
<textarea name="contents" id="contents" hidden></textarea>
|
||||||
|
|
||||||
<div class="mt-5 ms-2 me-2 ms-sm-5 me-sm-5 mt-sm-5 d-flex justify-content-center">
|
<div class="mt-5 ms-2 me-2 ms-sm-5 me-sm-5 mt-sm-5 d-flex justify-content-center">
|
||||||
|
|
||||||
@@ -140,11 +140,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- comment e -->
|
<!-- comment e -->
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
||||||
<script src="/js/getCsrf.js"></script>
|
<script src="/js/getCsrf.js"></script>
|
||||||
<script src="/js/articleView.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script>
|
||||||
|
<script th:replace="layout/fragments.html :: view"></script>
|
||||||
<script th:replace="layout/fragments.html :: comment"></script>
|
<script th:replace="layout/fragments.html :: comment"></script>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -62,10 +62,32 @@
|
|||||||
<div class="row justify-content-center mt-5 mb-3 g-0">
|
<div class="row justify-content-center mt-5 mb-3 g-0">
|
||||||
|
|
||||||
<div class="d-flex flex-row-reverse">
|
<div class="d-flex flex-row-reverse">
|
||||||
<label class="btn btn-secondary" for="thumbnail">썸네일 등록</label>
|
|
||||||
<input type="file" id="thumbnail" name="thumbnail" accept="image/*" class="d-none">
|
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#thumbnailModal">
|
||||||
|
썸네일 등록
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="modal fade" id="thumbnailModal" tabindex="-1" aria-labelledby="thumbnailModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="thumbnailModalLabel">썸네일 등록</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
|
||||||
|
<input type="text" id="thumbnail-url-upload-input" class="form-control">
|
||||||
|
<button class="btn btn-secondary" id="thumbnail-url-upload-btn">URL 업로드</button>
|
||||||
|
<label class="btn btn-secondary" for="thumbnail">파일 업로드</label>
|
||||||
|
<input type="file" id="thumbnail" name="thumbnail" accept="image/*" class="d-none">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<form class="" method="post" enctype="multipart/form-data" th:object="${articleDto}"
|
<form class="" method="post" enctype="multipart/form-data" th:object="${articleDto}"
|
||||||
th:action="@{/article/write}" id="writeArticleForm">
|
th:action="@{/article/write}" id="writeArticleForm">
|
||||||
|
|
||||||
@@ -115,6 +137,7 @@
|
|||||||
whitelist.push(tag.name)
|
whitelist.push(tag.name)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
|
<script src="/node_modules/@yaireo/tagify/dist/tagify.min.js"></script>
|
||||||
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
<script src="https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js"></script>
|
||||||
<script src="/js/getCsrf.js"></script>
|
<script src="/js/getCsrf.js"></script>
|
||||||
|
|||||||
@@ -129,6 +129,7 @@
|
|||||||
<script src="/node_modules/wow.js/dist/wow.js"></script>
|
<script src="/node_modules/wow.js/dist/wow.js"></script>
|
||||||
<script src="/js/infinityScroll.js"></script>
|
<script src="/js/infinityScroll.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script>
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -3,6 +3,36 @@
|
|||||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script th:fragment="view" th:inline="javascript" type="application/javascript">
|
||||||
|
const viewer = toastui.Editor.factory({
|
||||||
|
el: document.querySelector('#viewer'),
|
||||||
|
viewer: true,
|
||||||
|
height: '500px',
|
||||||
|
initialValue: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
viewer.setMarkdown([[${article.getContent()}]]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function deleteArticle() {
|
||||||
|
|
||||||
|
|
||||||
|
if (confirm("글을 정말 삭제하시겠습니까?") == true) {
|
||||||
|
document.getElementById("deleteArticle").submit();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script th:fragment="comment" th:inline="javascript" type="application/javascript">
|
<script th:fragment="comment" th:inline="javascript" type="application/javascript">
|
||||||
|
|
||||||
const replyBox = document.getElementById("commentBox");
|
const replyBox = document.getElementById("commentBox");
|
||||||
|
|||||||
@@ -67,7 +67,6 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<section th:replace="${content}"></section>
|
<section th:replace="${content}"></section>
|
||||||
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script src="/js/arrow.js"></script>
|
<script src="/js/arrow.js"></script>
|
||||||
<script src="/js/search.js"></script>
|
<script src="/js/search.js"></script>
|
||||||
<!-- scripts e -->
|
<!-- scripts e -->
|
||||||
|
|||||||
@@ -84,6 +84,8 @@
|
|||||||
<div style="margin-bottom: 100px"></div>
|
<div style="margin-bottom: 100px"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user