1
0

5 Revīzijas ccb8dba17d ... 88d361044a

Autors SHA1 Ziņojums Datums
  sangwonlee 88d361044a userDTO 수정 3 gadi atpakaļ
  sangwonlee b251d2b6c8 Merge branch 'dbs347' of http://wcollector.idatabank.com:5230/dbs333/RealWorld into dbs333 3 gadi atpakaļ
  Gayeon Park 7f6a350a62 메인화면 좋아요 기능 추가 3 gadi atpakaļ
  Gayeon Park 821bc30cc4 Merge branch 'dbs333' of http://wcollector.idatabank.com:5230/dbs333/RealWorld into dbs347 3 gadi atpakaļ
  Gayeon Park 9b33b61870 메인화면 페이징 기능 추가 3 gadi atpakaļ

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

@@ -26,5 +26,5 @@ public class ArticleDTO {
     private List<CommentDTO> comments;
 
     private int favoriteNum;
-    private boolean isFavorite;
+    private boolean favorite;
 }

+ 1 - 3
realworld/src/main/java/com/dbs/realworld/dto/UserDTO.java

@@ -2,9 +2,7 @@ package com.dbs.realworld.dto;
 
 import lombok.*;
 
-@ToString
-@Getter
-@Setter
+@Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class UserDTO {

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

@@ -18,6 +18,7 @@ public interface ArticleMapper {
     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);
 }

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

@@ -57,7 +57,7 @@
               SELECT COUNT(*) FROM article_like AL 
               WHERE AL.user_id     = #{userId}
               AND   AL.article_id  = AR.id
-          )                   AS isFavorite,
+          )                   AS favorite,
         ]]>
       </if>
       <![CDATA[
@@ -129,7 +129,7 @@
 
     <insert id="insertFavorite" parameterType="hashmap">
         <![CDATA[
-            INSERT INTO article_mst 
+            INSERT IGNORE INTO article_like 
               ( user_id, article_id	)
             VALUES 
               ( #{userId}, #{articleId} )
@@ -138,10 +138,9 @@
 
     <delete id="deleteFavorite" parameterType="hashmap">
         <![CDATA[
-            DELETE FROM article_mst 
-            WHERE 
-              user_id    = #{userId},
-              article_id = #{articleId}
+            DELETE FROM article_like
+            WHERE user_id    = #{userId}
+            AND   article_id = #{articleId}
         ]]>
     </delete>
 

+ 157 - 84
realworld/src/main/webapp/WEB-INF/jsp/main.jsp

@@ -13,8 +13,19 @@
     let ssIsLogin = null;
     let targetFeed = null;
     let username = null;
+    const options = { method: 'GET' };
     const tagSet = new Set();
-    const tagArray = [];
+    let tagArray = [];
+    let params = {
+            articleId: -1,
+            feed: selectFeed()
+        };
+    
+    let query = Object.keys(params)
+            .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+            .join('&');
+
+    let url = '/article/page?' + query;
 
     // 1. feed를 바꾼 경우
     // 2. 태그를 클릭한 경우
@@ -46,10 +57,6 @@
                 }
             }
 
-            // if ((targetFeed.id === 'global-feed' && display === 'none') || (targetFeed.id === 'your-feed' && display === 'none')) {
-            //     articleList.innerHTML = `<article class="noArticle">Feed not here yet.</article>`
-            // } 
-
             article.style.display = display;
         });
     };
@@ -88,8 +95,33 @@
 
         // targetFeed 초기화
         targetFeed = unFocusedFeed;
-
         setLoading('on');
+        tagArray = [];
+        articleList = document.querySelector('#article-list');
+  
+        while(articleList.firstChild) {
+            articleList.firstChild.remove();
+        }
+
+        params['articleId'] = -1;
+        params['feed'] = unFocusedFeed.id;
+
+        query = Object.keys(params)
+            .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+            .join('&');
+
+        let url = '/article/page?' + query;
+
+        fetch(url, options)
+            .then(response => response.json())
+            .then(json => {
+                const { articles, paging } = json;
+                username = "${ssUsername}";
+
+                loadArticle(articles);
+                nextPageLoad(articles, paging);
+            });
+
         filterArticle({ 'clickedFeed': unFocusedFeed.id })
         setTimeout(() => {
             setLoading('off');
@@ -98,13 +130,43 @@
 
     
     function favoriteBtn(indexNumber) {
+        const favoriteNum = event.target.querySelector('.count').textContent;
+        let counts = parseInt(favoriteNum);
+        let newCounts = counts + 1;
+        
         const favBtn = document.querySelectorAll('.favorite-btn');
         if ("${ssIsLogin}" === "true") {
             favBtn.forEach(value => {
                 if (value.classList.contains(indexNumber)) {
-                    value.classList.toggle('active');
+                    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) {
+                                return value.classList.remove('active');
+                                }
+                            });
+                    } else {
+                        event.target.querySelector('.count').textContent = newCounts;
+                        fetch('/article/'+ indexNumber + '/favorite', {
+                            body: JSON.stringify({
+                                articleId: indexNumber,
+                                userId: "${ssId}",
+                                created: new Date()
+                            }),
+                            method: 'POST',
+                            headers: {
+                                "Content-Type": "application/json"
+                            }
+                        })
+                        .then(response => {
+                            if(response.status === 201){
+                                return value.classList.add('active');
+                                }
+                            })
+                    }
                 }
-            })
+            });
         } else {
             location.href = "/user/signin"
         }
@@ -137,59 +199,60 @@
     }
 
     function selectFeed() {
-            if (ssIsLogin === "true") {
+            if ("${ssIsLogin}" === 'true') {
                 return 'your-feed'
             } else {
-                return 'global-feed'
+                return 'global-feed';
             }
     }
 
-    
-    function loadArticle(articles) {
-        
+    //게시글 표시
+    function loadArticle(articles) { 
         articles.forEach(article => {
-
             console.log(JSON.stringify(article))
             const domParser = new DOMParser();
 
             const domStrArticleMeta =
                 `
-                        <div class="article-meta">
-                            <div class="metadata">
-                                <a href="#" class="profile-link">
-                                    <img src="/resources/images/avatar.png" alt="avatar">
-                                </a>
-                                <div class="article-info">
-                                    <a href="#" class="name"></a>
-                                    <span class="date"></span>
-                                </div>
-                            </div>
-                            <div>
-                                <button class="favorite-btn \${article.id}" onclick="favoriteBtn(\${article.id})">
-                                    <i class="fas fa-heart"></i>
-                                    <span class="count">564</span>
-                                </button>
-                            </div>
+                <div class="article-meta">
+                    <div class="metadata">
+                        <a href="#" class="profile-link">
+                            <img src="/resources/images/avatar.png" alt="avatar">
+                        </a>
+                        <div class="article-info">
+                            <a href="#" class="name"></a>
+                            <span class="date"></span>
                         </div>
-                        `;
+                    </div>
+                    <div>
+                        <button class="favorite-btn \${article.id}" onclick="favoriteBtn(\${article.id})">
+                            <i class="fas fa-heart"></i>
+                            <span class="count">\${article.favoriteNum}</span>
+                        </button>
+                    </div>
+                </div>
+                `;
             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');
+            }
 
             const domStrPreviewLink =
                 `
-                        <a href="/article/\${article.id}" class="preview-link">
-                            <h1 class="preview-title"></h1>
-                            <p></p>
-                            <div class="tag-data">
-                                <span>Read more...</span>
-                                <ul class = "tag-list">
-
-                                </ul>
-                            </div>
-                        </a>
-                        `;
+                <a href="/article/\${article.id}" class="preview-link">
+                    <h1 class="preview-title"></h1>
+                    <p></p>
+                    <div class="tag-data">
+                        <span>Read more...</span>
+                        <ul class = "tag-list">
+
+                        </ul>
+                    </div>
+                </a>
+                `;
             const aPreviewLink = domParser.parseFromString(domStrPreviewLink, 'text/html').body.firstChild;
             aPreviewLink.querySelector('.preview-title').textContent = article.title;
             aPreviewLink.querySelector('p').textContent = article.subtitle;
@@ -221,8 +284,16 @@
             articleList.appendChild(articlePreview);
 
             //태그 배열 생성
-            const tagsArray = article.tags.split(',')
+            let tagsArray = article.tags.split(',')
+            
+            const tagList = document.querySelector('#tag-list');
+            
+            while(tagList.firstChild) {
+                tagList.firstChild.remove();
+            }
+
             tagsArray.forEach(tag => {
+                
                 if (tag !== '') {
                     tagArray.push(tag);
                 }
@@ -233,7 +304,6 @@
         const tags = Array.from(tagSet);
         const tagList = window.document.querySelector('#tag-list');
 
-
         getSortedArr(tagArray).forEach(tag => {
             const a = document.createElement('a');
             const tagString = tag.join(',');
@@ -244,17 +314,47 @@
             a.textContent = `\${tagValue} (\${tagCount})`
             a.onclick = (event) => {
                 filterArticle({ 'clickedTag': tagValue });
-
-                // let currentValue = document.querySelector('.tag.active');
-                // if(currentValue !== null) {
-                //     currentValue.classList.remove('active');
-                // }
-                // event.target.classList.add('active');
             }
             tagList.appendChild(a);
         });
     }
 
+    //다음페이지 로드
+    function nextPageLoad(articles, paging) {
+        if (paging.isNext) {
+            const moreButton = document.createElement('button');
+            const articleList = document.querySelector('#article-list');
+            const loading = document.createElement('div');
+
+            moreButton.textContent = 'More';
+            moreButton.classList.add('more-button');
+            loading.setAttribute('id', 'loading');
+            articleList.appendChild(moreButton);
+            moreButton.onclick = () => {
+                articleList.removeChild(moreButton);
+                articleList.appendChild(loading);
+                params['articleId'] = articles[articles.length - 1].id;
+                query = Object.keys(params)
+                        .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+                        .join('&');
+
+                url = '/article/page?' + query;
+
+                fetch(url, options)
+                    .then(response => response.json())
+                    .then(json => {
+                        const { articles, paging } = json;
+                        username = "${ssUsername}";
+                        loadArticle(articles);
+                        // articleList.removeChild(moreButton);
+                        articleList.removeChild(loading);
+                        nextPageLoad(articles, paging);
+                        
+                    })
+            }
+        }
+    }
+
     window.onload = (event) => {
 
         ssIsLogin = "${ssIsLogin}";
@@ -268,21 +368,17 @@
         noTag = window.document.querySelector('#no-tag');
         tagList = window.document.querySelector('#tag-list');
 
-        // fetch api call
-        const options = { method: 'GET' };
-        setLoading('on');
-        
+        //fetch api call       
         let params = {
             articleId: -1,
             feed: selectFeed()
         };
-
         let query = Object.keys(params)
                     .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
                     .join('&');
-
         let url = '/article/page?' + query;
-        console.log(url)
+
+        setLoading('on');
 
         fetch(url, options)
             .then(response => response.json())
@@ -291,35 +387,12 @@
                 username = "${ssUsername}";
 
                 loadArticle(articles);
-
-                if(paging.isNext) {
-                    const moreButton = document.createElement('button');
-                    moreButton.textContent = '더보기';
-                    const articleList = document.querySelector('#article-list');
-                    articleList.appendChild(moreButton);
-                    moreButton.onclick = () => {
-                        params['articleId'] = articles[articles.length - 1].id;
-
-                        query = Object.keys(params)
-                            .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
-                            .join('&');
-
-                        console.log(query);
-                        fetch(url, options)
-                            .then(response => response.json())
-                            .then(json => {
-                                const {articles, paging} = json;
-                                username = "${ssUsername}";
-                                loadArticle(articles);
-                                articleList.removeChild(moreButton);
-                            })
-                    }
-                }
-                console.log(paging.isNext);
-
+                nextPageLoad(articles, paging);
+            
                 // hide loadings
                 setLoading('off');
 
+                
                 // loadingTag.style.display = 'none';
                 // loadingArticle.style.display = 'none';
 

+ 35 - 3
realworld/src/main/webapp/resources/css/style.css

@@ -308,16 +308,17 @@ img {
 
 .favorite-btn i {
     font-size: 0.725rem;
+    pointer-events: none;
 }
 
+.favorite-btn.active,
 .favorite-btn:hover {
     color: var(--white-color);
     background-color: var(--green-color);
 }
 
-.favorite-btn.active {
-    color: var(--white-color);
-    background-color: var(--green-color);
+.favorite-btn span {
+    pointer-events: none;
 }
 
 .preview-link h1{
@@ -383,7 +384,38 @@ img {
     background-color: #646d74;
 }
 
+.home-page #article-list .more-button {
+    float: right;
+    margin-top: 0.6rem;
+    margin-bottom: 1.2rem;
+    border: 1px solid #818a92;
+    padding: 0.25rem 0.5rem;
+    border-radius: 3px;
+}
+
+.home-page #article-list .more-button:hover {
+    background-color: #dddddd;
+}
 
+#loading {
+    display: inline-block;
+    position: absolute;
+    left: 33%;
+    width: 50px;
+    height: 50px;
+    border: 3px solid rgba(255,255,255, 0.1);
+    border-radius: 50%;
+    border-top-color: #aaa;
+    animation: spin 1s ease-in-out infinite;
+    -webkit-animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+  to { -webkit-transform: rotate(360deg); }
+}
+@-webkit-keyframes spin {
+  to { -webkit-transform: rotate(360deg); }
+}
 
 /* Sign in & Sign up */
 .container .col-6 {