Browse Source

게시물 상세 페이지 팔로우, 좋아요 기능 추가

Gayeon Park 3 years ago
parent
commit
24cdd3ed10

+ 2 - 2
realworld/src/main/java/com/dbs/realworld/service/ArticleService.java

@@ -87,14 +87,14 @@ public class ArticleService {
     }
 
 
-    @Transactional(rollbackFor = Exception.class)
+    // @Transactional(rollbackFor = Exception.class)
     public void favorite(int userId, int articleId) {
         this.articleMapper.insertFavorite(userId, articleId);
         this.articleMapper.updateFavoriteNum(articleId);
     }
 
 
-    @Transactional(rollbackFor = Exception.class)
+    // @Transactional(rollbackFor = Exception.class)
     public void unfavorite(int userId, int articleId) {
         this.articleMapper.deleteFavorite(userId, articleId);
         this.articleMapper.updateFavoriteNum(articleId);

+ 1 - 2
realworld/src/main/resources/mybatis/mapper/user/UserMapper.xml

@@ -26,8 +26,7 @@
 
     <insert id="insertFollow" parameterType="FollowDTO">
         <![CDATA[
-            INSERT 
-              INTO user_follow
+            INSERT IGNORE INTO user_follow
                    ( from_user,   to_user	)
             VALUES ( #{fromUser}, #{toUser} )
         ]]>

+ 163 - 39
realworld/src/main/webapp/WEB-INF/jsp/article/articleDetails.jsp

@@ -2,18 +2,100 @@
 <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
 <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
-<%@ taglib uri = "http://java.sun.com/jsp/jstl/functions" prefix = "fn" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
 <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
 
 
 <jsp:include page="/WEB-INF/jsp/include/head.jsp"></jsp:include>
 <script>
-    function filter(classname) {
-        const buttons = document.querySelectorAll('button');
-        if("${ssIsLogin}" === "true") {
-            buttons.forEach(button => {
-                if(button.classList.contains(classname)) {
-                    button.classList.toggle('active');
+    const follow = 'Follow ' + "${articleDetail.writerName}";
+    const unfollow = 'Unfollow ' + "${articleDetail.writerName}";
+    const favorite = 'Favorite Article';
+    const unfavorite = 'Unfavorite Article';
+
+    function userFollow() {
+        const followBtn = document.querySelectorAll('.follow-btn');
+
+        if ("${ssIsLogin}" === "true") {
+            followBtn.forEach(button => {
+                if (button.classList.contains('active')) {
+                    button.querySelector('.follow').textContent = follow;
+                    fetch("/user/unfollow", {
+                        method: 'DELETE',
+                        body: JSON.stringify({
+                            fromUser: "${ssId}",
+                            toUser: "${articleDetail.writerId}"
+                        }),
+                        headers: {
+                            "Content-Type": "application/json"
+                        }
+                    })
+                        .then(response => {
+                            if (response.status === 200) {
+                                return button.classList.remove('active');
+                            }
+                        })
+                } else {
+                    button.querySelector('.follow').textContent = unfollow;
+                    fetch("/user/follow", {
+                        body: JSON.stringify({
+                            fromUser: "${ssId}",
+                            toUser: "${articleDetail.writerId}"
+                        }),
+                        method: 'POST',
+                        headers: {
+                            "Content-Type": "application/json"
+                        }
+                    })
+                        .then(response => {
+                            if (response.status === 201) {
+                                return button.classList.add('active');
+                            }
+                        })
+                }
+            })
+        } else {
+            location.href = "/user/signin"
+        }
+    }
+
+    function articleFavorite() {
+        const favoriteBtn = document.querySelectorAll('.favorite-btn');
+        let counts = parseInt(event.target.querySelector('.count').textContent);
+        let newCounts = counts + 1;
+
+        if ("${ssIsLogin}" === "true") {
+            favoriteBtn.forEach(button => {
+                if (button.classList.contains('active')) {
+                    button.querySelector('.favorite').textContent = favorite;
+                    button.classList.remove('active');
+
+                    fetch("/article/${articleDetail.id}/favorite", { method: 'DELETE' })
+                        .then(response => {
+                            if (response.status === 200) {
+                                return button.querySelector('.count').textContent = counts - 1;
+                            }
+                        })
+                } else {
+                    button.querySelector('.favorite').textContent = unfavorite;
+                    button.classList.add('active');
+
+                    fetch("/article/${articleDetail.id}/favorite", {
+                        body: JSON.stringify({
+                            artilceId: "${articleDetail.id}",
+                            userId: "${ssId}",
+                            created: new Date()
+                        }),
+                        method: 'POST',
+                        headers: {
+                            "Content-Type": "application/json"
+                        }
+                    })
+                        .then(response => {
+                            if (response.status === 201) {
+                                return button.querySelector('.count').textContent = newCounts;
+                            }
+                        })
                 }
             })
         } else {
@@ -47,11 +129,11 @@
                 "Content-Type": "application/json"
             }
         };
-        
-        if(commentInputArea.value !== ''){
+
+        if (commentInputArea.value !== '') {
             fetch('/article/${articleDetail.id}/comment', postOptions)
                 .then(response => {
-                    if(response.status === 201) {
+                    if (response.status === 201) {
                         return displayComments();
                     }
                 })
@@ -62,7 +144,7 @@
     function deleteComment(targetCommentId) {
         fetch("/article/${articleDetail.id}/comment/" + targetCommentId, { method: 'DELETE' })
             .then(response => {
-                if(response.status === 200) {
+                if (response.status === 200) {
                     return displayComments();
                 }
             })
@@ -76,7 +158,7 @@
                 }
             })
             .then(json => {
-                const { comments } = json;           
+                const { comments } = json;
                 const container = document.querySelector('.comments');
 
                 while (container.firstChild) {
@@ -115,29 +197,49 @@
                     })
                 })
             })
-        
+
     }
 
     window.onload = () => {
         const tags = document.querySelector('.tag-list');
         const tagString = "${articleDetail.tags}"
         const tagList = tagString.split(',');
-        if(tagString !== ''){
-            tags.innerHTML = tagList.map(tag => 
+        if (tagString !== '') {
+            tags.innerHTML = tagList.map(tag =>
                 `<li class="tag">\${tag}</li>`
             ).join('');
         }
 
         const date = "${articleDetail.created}"
-        document.querySelectorAll('.date').forEach(value => 
+        document.querySelectorAll('.date').forEach(value =>
             value.textContent = new Date(date).toLocaleString()
         )
 
+        const count = "${articleDetail.favoriteNum}";
+        document.querySelectorAll('.count').forEach(value => {
+            value.textContent = count;
+        })
+
+        const followString = document.querySelectorAll('.follow-btn');
+        followString.forEach(value => {
+            if(value.classList.contains('active')) {
+                value.querySelector('.follow').textContent = unfollow;
+            }
+        })
+
+        const favoriteString = document.querySelectorAll('.favorite-btn');
+        favoriteString.forEach(value => {
+            if (value.classList.contains('active')) {
+                value.querySelector('.favorite').textContent = unfavorite;
+            }
+        })
+
         displayComments();
     }
-    
+
 </script>
 </head>
+
 <body>
     <jsp:include page="/WEB-INF/jsp/include/header.jsp"></jsp:include>
 
@@ -163,23 +265,33 @@
                         <c:choose>
                             <c:when test="${articleDetail.writerId eq ssId}">
                                 <div class="buttons">
-                                    <a href="/article/${articleDetail.id}/edit" class="edit-article btn-sm">
+                                    <a href="/article/${articleDetail.id}/edit"
+                                        class="edit-article btn-sm">
                                         <i class="fas fa-pencil-alt"> Edit Article</i>
                                     </a>
-                                    <button class="delete-article btn-sm" onclick="deleteArticle('${articleDetail.id}')">
+                                    <button class="delete-article btn-sm"
+                                        onclick="deleteArticle('${articleDetail.id}')">
                                         <i class="fas fa-trash-alt"> Delete Article</i>
                                     </button>
                                 </div>
                             </c:when>
                             <c:otherwise>
                                 <div class="buttons">
-                                    <button class="follow-btn btn-sm btn-meta" onclick="filter('follow-btn')">
-                                        <i class="fas fa-plus"></i>&nbsp;Follow <c:out value="${articleDetail.writerName}"></c:out>
+                                    <button class="follow-btn btn-sm btn-meta <c:if test="${articleDetail.writerFollow eq true}">active</c:if>"
+                                        onclick="userFollow()"
+                                        >
+                                        <i class="fas fa-plus"></i>
+                                        <span class="follow">Follow <c:out
+                                                value="${articleDetail.writerName}"></c:out>
+                                            </span>
                                     </button>
-                                    <button class="favorite-btn btn-sm btn-meta" onclick="filter('favorite-btn')">
+                                    <button class="favorite-btn btn-sm btn-meta <c:if test="${articleDetail.favorite eq true}">active</c:if>"
+                                        onclick="articleFavorite()">
                                         <i class="fas fa-heart"></i>
-                                        <span> Favorite Article </span> 
-                                        <span class="count">(564)</span>
+                                        <span class="favorite"> Favorite Article </span> (
+                                        <span class="count">
+                                        </span>
+                                        )
                                     </button>
                                 </div>
                             </c:otherwise>
@@ -188,13 +300,13 @@
                 </div>
             </div>
         </section>
-    
+
         <div class="container main">
             <div class="article-content">
                 <div class="col-12">
                     <pre>
-                        <c:out value="${articleDetail.content}"></c:out>
-                    </pre>
+<c:out value="${articleDetail.content}"></c:out>
+</pre>
                     <ul class="tag-list">
                     </ul>
                 </div>
@@ -211,31 +323,40 @@
                                 <c:out value="${articleDetail.writerName}"></c:out>
                             </a>
                             <span class="date">
-                                
+
                             </span>
                         </div>
                         <div class="buttons">
-                            </div>
-                            <c:choose>
+                        </div>
+                        <c:choose>
                             <c:when test="${articleDetail.writerId eq ssId}">
                                 <div class="buttons">
-                                    <a href="/article/${articleDetail.id}/edit" class="edit-article btn-sm">
+                                    <a href="/article/${articleDetail.id}/edit"
+                                        class="edit-article btn-sm">
                                         <i class="fas fa-pencil-alt"> Edit Article</i>
                                     </a>
-                                    <button class="delete-article btn-sm" onclick="deleteArticle('${articleDetail.id}')">
+                                    <button class="delete-article btn-sm"
+                                        onclick="deleteArticle('${articleDetail.id}')">
                                         <i class="fas fa-trash-alt"> Delete Article</i>
                                     </button>
                                 </div>
                             </c:when>
                             <c:otherwise>
                                 <div class="buttons">
-                                    <button class="follow-btn btn-sm btn-meta" onclick="filter('follow-btn')">
-                                        <i class="fas fa-plus"></i>&nbsp;Follow <c:out value="${articleDetail.writerName}"></c:out>
+                                    <button class="follow-btn btn-sm btn-meta <c:if test="${articleDetail.writerFollow eq true}">active</c:if>" onclick="userFollow()"
+                                        >
+                                        <i class="fas fa-plus"></i>
+                                        <span class="follow">Follow <c:out
+                                                value="${articleDetail.writerName}"></c:out>
+                                            </span>
                                     </button>
-                                    <button class="favorite-btn btn-sm btn-meta" onclick="filter('favorite-btn')">
+                                    <button class="favorite-btn btn-sm btn-meta <c:if test="${articleDetail.favorite eq true}">active</c:if>"
+                                        onclick="articleFavorite()">
                                         <i class="fas fa-heart"></i>
-                                        <span> Favorite Article </span> 
-                                        <span class="count">(564)</span>
+                                        <span class="favorite"> Favorite Article </span> (
+                                        <span class="count">
+                                        </span>
+                                        )
                                     </button>
                                 </div>
                             </c:otherwise>
@@ -248,11 +369,13 @@
                 <div class="col-8">
                     <div class="card comment-form">
                         <div class="card-block">
-                            <textarea id="comment-input" class="form-control" placeholder="Write a comment..." rows="3"></textarea>
+                            <textarea id="comment-input" class="form-control"
+                                placeholder="Write a comment..." rows="3"></textarea>
                         </div>
                         <div class="card-footer">
                             <img class="comment-img" src="/resources/images/avatar.png" alt="">
-                            <button class="btn btn-card btn-sm" onclick="postComments()">Post Comment</button>
+                            <button class="btn btn-card btn-sm" onclick="postComments()">Post
+                                Comment</button>
                         </div>
                     </div>
                     <div class="comments">
@@ -263,4 +386,5 @@
         </div>
     </div>
 </body>
+
 </html>

+ 7 - 5
realworld/src/main/webapp/WEB-INF/jsp/main.jsp

@@ -96,9 +96,9 @@
         // targetFeed 초기화
         targetFeed = unFocusedFeed;
         setLoading('on');
+
         tagArray = [];
         articleList = document.querySelector('#article-list');
-  
         while(articleList.firstChild) {
             articleList.firstChild.remove();
         }
@@ -110,7 +110,7 @@
             .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
             .join('&');
 
-        let url = '/article/page?' + query;
+        url = '/article/page?' + query;
 
         fetch(url, options)
             .then(response => response.json())
@@ -140,6 +140,7 @@
                 if (value.classList.contains(indexNumber)) {
                     if(event.target.classList.contains('active')){
                         event.target.querySelector('.count').textContent = counts - 1;
+
                         fetch('/article/' + indexNumber + '/favorite', { method: 'DELETE' })
                             .then(response => {
                                 if(response.status === 200) {
@@ -148,6 +149,7 @@
                             });
                     } else {
                         event.target.querySelector('.count').textContent = newCounts;
+
                         fetch('/article/'+ indexNumber + '/favorite', {
                             body: JSON.stringify({
                                 articleId: indexNumber,
@@ -159,9 +161,9 @@
                                 "Content-Type": "application/json"
                             }
                         })
-                        .then(response => {
-                            if(response.status === 201){
-                                return value.classList.add('active');
+                            .then(response => {
+                                if(response.status === 201){
+                                    return value.classList.add('active');
                                 }
                             })
                     }

+ 5 - 0
realworld/src/main/webapp/resources/css/style.css

@@ -574,6 +574,11 @@ textarea {
     filter: none;
 }
 
+.article-page button i, 
+.article-page button span {
+    pointer-events: none;
+}
+
 .article-page .edit-article,
 .article-page .follow-btn {
     color: var(--light-grey-color);