1
0

4 Commits 7f6109e771 ... 0939ad2524

Autor SHA1 Mensagem Data
  Gayeon Park 0939ad2524 Merge branch 'master' of http://wcollector.idatabank.com:5230/dbs333/RealWorld há 3 anos atrás
  Gayeon Park 7d34fce178 기타수정 há 3 anos atrás
  Gayeon Park 24cdd3ed10 게시물 상세 페이지 팔로우, 좋아요 기능 추가 há 3 anos atrás
  sangwonlee 9cbe3636b0 게시물 상세 조회 수정 há 3 anos atrás

+ 10 - 4
realworld/src/main/java/com/dbs/realworld/controller/ArticleController.java

@@ -213,8 +213,8 @@ public class ArticleController {
         data.put("articles", articleDTOs);
 
         final int size = articleDTOs.size();
-        int lastItemArticleId = (size != 0) ? articleDTOs.get(size - 1).getId() : -1;
-        data.put("paging", this.articleService.calculatePagingInfo(articleDTOs, lastItemArticleId, feed, userId));
+        int lastArticleId = (size != 0) ? articleDTOs.get(size - 1).getId() : -1;
+        data.put("paging", this.articleService.calculatePagingInfo(articleDTOs, lastArticleId, feed, userId));
         
         return ResponseEntity.ok().body(data);
     }
@@ -224,9 +224,15 @@ public class ArticleController {
      * 게시물 상세 조회
      */
     @GetMapping("/{articleId}")
-    public String getArticleDetailById(@PathVariable("articleId") int articleId, ModelMap model) {
+    public String getArticleDetail(HttpServletRequest request, @PathVariable("articleId") int articleId, ModelMap model) {
 
-        ArticleDTO articleDTO = this.articleService.findByArticleId(articleId);
+        int userId = -1;
+        HttpSession session = request.getSession();
+        if (session.getAttribute("ssId") != null) {
+            userId = (int) request.getSession().getAttribute("ssId");
+        }
+
+        ArticleDTO articleDTO = this.articleService.findArticleDetail(articleId, userId);
 
         model.put("articleDetail", articleDTO);
         return Views.ARTICLE_DETAIL;

+ 2 - 1
realworld/src/main/java/com/dbs/realworld/dto/ArticleDTO.java

@@ -26,5 +26,6 @@ public class ArticleDTO {
     private List<CommentDTO> comments;
 
     private int favoriteNum;
-    private boolean favorite;
+    private boolean favorite;     // 로그인한 경우
+    private boolean writerFollow; // 로그인한 경우
 }

+ 2 - 1
realworld/src/main/java/com/dbs/realworld/mapper/ArticleMapper.java

@@ -14,11 +14,12 @@ public interface ArticleMapper {
     void insertArticle(ArticleDTO dto);
     List<ArticleDTO> select(int articleId, String feed, int userId);
     Map<String, Object> selectPagingInfo(int articleId, String feed, int userId);
+    ArticleDTO selectArticleDetail(int articleId, int userId);
     ArticleDTO selectByArticleId(int articleId);
     void deleteByArticleId(int articleId);
     int updateArticle(ArticleDTO dto);
+    
     void insertFavorite(int userId, int articleId);
-    // boolean checkExistFavorite()    
     void deleteFavorite(int userId, int articleId);
     void updateFavoriteNum(int articleId);
 }

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

@@ -42,10 +42,10 @@ public class ArticleService {
     }
 
 
-    public Map<String, Object> calculatePagingInfo(List<ArticleDTO> articleDTOs, int articleId, String feed, int userId) {
-        Map<String, Object> paging = this.articleMapper.selectPagingInfo(articleId, feed, userId);
+    public Map<String, Object> calculatePagingInfo(List<ArticleDTO> articleDTOs, int lastArticleId, String feed, int userId) {
+        Map<String, Object> paging = this.articleMapper.selectPagingInfo(lastArticleId, feed, userId);
         paging.put("size", articleDTOs.size());
-        if (articleId == -1) {
+        if (lastArticleId == -1) {
             paging.put("isNext", false);
             paging.put("firstId", null);
             paging.put("lastId", null);
@@ -57,7 +57,6 @@ public class ArticleService {
         return paging;
     }
 
-
     public ArticleDTO findByArticleId(int articleId) {
         ArticleDTO articleDTO = this.articleMapper.selectByArticleId(articleId);
         List<CommentDTO> commentDTOs = this.commentService.findAllByArticleId(articleId);
@@ -66,6 +65,18 @@ public class ArticleService {
     }
 
 
+    public ArticleDTO findArticleDetail(int articleId, int userId) {
+        ArticleDTO articleDTO = this.articleMapper.selectArticleDetail(articleId, userId);
+        
+        List<CommentDTO> commentDTOs = this.commentService.findAllByArticleId(articleId);
+        articleDTO.setComments(commentDTOs);
+
+
+
+        return articleDTO;
+    }
+
+
     public void remove(int articleId) {
         this.articleMapper.deleteByArticleId(articleId);
     }
@@ -76,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);

+ 49 - 5
realworld/src/main/resources/mybatis/mapper/user/ArticleMapper.xml

@@ -17,16 +17,24 @@
             SET	update_datetime = NOW()
       ]]>
       <if test='title != null'>
-        , title = #{title}
+        <![CDATA[
+          , title = #{title}
+        ]]>	
       </if>
       <if test='subtitle != null'>
-        , subtitle = #{subtitle}
+        <![CDATA[
+          , subtitle = #{subtitle}
+        ]]>	
       </if>
       <if test='content != null'>
-        , content = #{content}
+        <![CDATA[
+          , content = #{content}
+        ]]>	
       </if>
       <if test='tags != null'>
-        , tags = #{tags}
+        <![CDATA[
+          , tags = #{tags}
+        ]]>	
       </if>
         <![CDATA[
           WHERE id = #{id}
@@ -80,7 +88,7 @@
         ]]>
       </if>
       <![CDATA[
-        ORDER BY create_datetime DESC
+        ORDER BY AR.create_datetime DESC
         LIMIT 4
       ]]>
     </select>
@@ -102,6 +110,42 @@
      </select>
 
 
+    <select id="selectArticleDetail" parameterType="hashmap" resultType="ArticleDTO">
+      <![CDATA[
+        SELECT
+          USR.name            AS writerName,
+          USR.email           AS writerEmail,
+          AR.user_id          AS writerId,
+          AR.id               AS id,
+          AR.title            AS title,
+          AR.subtitle         AS subtitle,
+          AR.content          AS content,
+          AR.tags             AS tags,
+          AR.create_datetime  AS created,
+          AR.favorite_num     AS favoriteNum,
+      ]]>
+      <if test='userId != -1'>
+        <![CDATA[
+          (
+              SELECT COUNT(*) FROM article_like AL 
+              WHERE AL.user_id     = #{userId}
+              AND   AL.article_id  = AR.id
+          )                   AS favorite,
+          (
+              SELECT COUNT(*) FROM user_follow UF
+              WHERE  UF.from_user = #{userId}
+              AND    UF.to_user   = AR.user_id
+          )                   AS writerFollow
+        ]]>
+      </if>
+      <![CDATA[
+        FROM article_mst AR
+        LEFT JOIN user_mst USR
+        ON AR.user_id = USR.id
+        WHERE AR.id = #{articleId}
+      ]]>
+    </select>
+
     <select id="selectByArticleId" parameterType="int" resultType="ArticleDTO">
       <![CDATA[
         SELECT

+ 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} )
         ]]>

+ 164 - 40
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) {
@@ -107,7 +189,7 @@
                     const divComment = domParser.parseFromString(domStrComment, 'text/html').body.firstChild;
                     divComment.querySelector('.date-posted').textContent = new Date(comment.created).toLocaleString();
                     container.appendChild(divComment);
-                    const icons = document.querySelectorAll('.fa-trash-alt');
+                    const icons = document.querySelectorAll('.comment-footer .fa-trash-alt');
                     icons.forEach(icon => {
                         if (comment.userId == "${ssId}") {
                             icon.style.display = 'block';
@@ -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>

+ 11 - 9
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())
@@ -119,7 +119,8 @@
                 username = "${ssUsername}";
 
                 loadArticle(articles);
-                nextPageLoad(articles, paging);
+                document.addEventListener("scroll", nextPageLoad(articles, paging));
+                // nextPageLoad(articles, paging);
             });
 
         filterArticle({ 'clickedFeed': unFocusedFeed.id })
@@ -140,6 +141,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 +150,7 @@
                             });
                     } else {
                         event.target.querySelector('.count').textContent = newCounts;
+
                         fetch('/article/'+ indexNumber + '/favorite', {
                             body: JSON.stringify({
                                 articleId: indexNumber,
@@ -159,9 +162,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');
                                 }
                             })
                     }
@@ -235,7 +238,7 @@
             const divArticleMeta = domParser.parseFromString(domStrArticleMeta, 'text/html').body.firstChild;
             divArticleMeta.querySelector('.name').textContent = article.writerName;
             divArticleMeta.querySelector('.date').textContent = new Date(article.created).toLocaleString();
-            console.log(article.favorite)
+            
             if(article.favorite) {
                 divArticleMeta.querySelector('.favorite-btn').classList.add('active');
             }
@@ -345,9 +348,8 @@
                     .then(json => {
                         const { articles, paging } = json;
                         username = "${ssUsername}";
-                        loadArticle(articles);
-                        // articleList.removeChild(moreButton);
                         articleList.removeChild(loading);
+                        loadArticle(articles);
                         nextPageLoad(articles, paging);
                         
                     })

+ 13 - 7
realworld/src/main/webapp/resources/css/style.css

@@ -388,14 +388,15 @@ img {
     float: right;
     margin-top: 0.6rem;
     margin-bottom: 1.2rem;
-    border: 1px solid #818a92;
-    padding: 0.25rem 0.5rem;
-    border-radius: 3px;
+    border-bottom: 1px solid #818a92;
+    padding: 0.1rem 0;
+    font-size: 1.2rem;
+    font-weight: 600;
 }
 
-.home-page #article-list .more-button:hover {
+/* .home-page #article-list .more-button:hover {
     background-color: #dddddd;
-}
+} */
 
 #loading {
     display: inline-block;
@@ -574,6 +575,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);
@@ -846,7 +852,7 @@ hr {
 .modal-message {
     padding-bottom: 40px;
     font-size: 2rem;
-    font-family: 'Nanum Gothic', sans-serif;
+    font-family: 'Noto Sans KR', sans-serif;
     color: var(--green-color);
 }
 
@@ -855,7 +861,7 @@ hr {
     background-color: var(--green-color);
     color: var(--white-color);
     font-weight: 700;
-    font-family: 'Nanum Gothic', sans-serif;
+    font-family: 'Noto Sans KR', sans-serif;
     width: 5rem;
     height: 2rem;
     border-radius: 0.3rem;