|
@@ -8,9 +8,38 @@
|
|
|
<jsp:include page="/WEB-INF/jsp/include/head.jsp"></jsp:include>
|
|
|
<script>
|
|
|
let targetFeed = null;
|
|
|
- const loadingArticles = document.querySelector('#article-loading');
|
|
|
- const articleList = document.querySelector('#article-list');
|
|
|
+ let params = {
|
|
|
+ articleId: -1,
|
|
|
+ feed: 'your-feed'
|
|
|
+ };
|
|
|
+ let query = Object.keys(params)
|
|
|
+ .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
|
|
|
+ .join('&');
|
|
|
+ let url = '/article/page/users/${user.id}?' + query;
|
|
|
+ let loadingArticles = null;
|
|
|
+ let articleList = null;
|
|
|
+ const tagSet = new Set();
|
|
|
+ const options = { method: 'GET' };
|
|
|
+ const follow = 'Follow ' + "${user.username}";
|
|
|
+ const unfollow = 'Unfollow ' + "${user.username}";
|
|
|
|
|
|
+ // feed를 바꾼 경우
|
|
|
+ const filterArticle = (option) => {
|
|
|
+ const { clickedFeed } = option;
|
|
|
+ const articles = document.querySelectorAll('.article-preview');
|
|
|
+
|
|
|
+ articles.forEach(article => {
|
|
|
+ let display = 'none';
|
|
|
+
|
|
|
+ // display 기본 값은 none이고 값이 block으로 변경되는 경우만 조건으로 건다.
|
|
|
+ if(clickedFeed === 'favorite-feed') {
|
|
|
+ display = 'block';
|
|
|
+ } else if(clickedFeed === 'your-feed') {
|
|
|
+ display = 'block';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 게시글 목록 로딩
|
|
|
const setLoading = (type) => {
|
|
|
let loadingDisplay = null;
|
|
|
let contentDisplay = null;
|
|
@@ -22,11 +51,22 @@
|
|
|
loadingDisplay = 'none';
|
|
|
contentDisplay = 'block';
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
loadingArticles.style.display = loadingDisplay;
|
|
|
articleList.style.display = contentDisplay;
|
|
|
}
|
|
|
|
|
|
+ // 자신의 게시글이 없는 경우
|
|
|
+ const noContent = () => {
|
|
|
+ if(articleList.firstChild === null) {
|
|
|
+ const p = document.createElement('p');
|
|
|
+
|
|
|
+ p.classList.add('article-preview');
|
|
|
+ p.textContent = 'No articles are here... yet.';
|
|
|
+ articleList.appendChild(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
const focusFeed = () => {
|
|
|
// 이미 포커싱되었으면 중단
|
|
|
if(event.target.parentNode.classList.contains('active')) {
|
|
@@ -42,8 +82,128 @@
|
|
|
// targetFeed 초기화
|
|
|
targetFeed = currentFeed;
|
|
|
setLoading('on');
|
|
|
+
|
|
|
+ // 게시글 초기화
|
|
|
+ while(articleList.firstChild) {
|
|
|
+ articleList.firstChild.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ params = {
|
|
|
+ articleId: -1,
|
|
|
+ feed: targetFeed.id
|
|
|
+ }
|
|
|
+ query = Object.keys(params)
|
|
|
+ .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
|
|
|
+ .join('&');
|
|
|
+ url = '/article/page/users/${user.id}?' + query;
|
|
|
+
|
|
|
+ fetch(url, options)
|
|
|
+ .then(response => response.json())
|
|
|
+ .then(json => {
|
|
|
+ const { articles, paging } = json;
|
|
|
+ username = "${ssUsername}";
|
|
|
+
|
|
|
+ displayArticles(articles);
|
|
|
+ nextPageLoad(articles, paging);
|
|
|
+ })
|
|
|
+ filterArticle({ 'clickedFeed': currentFeed.id });
|
|
|
+ setTimeout(() => {
|
|
|
+ setLoading('off');
|
|
|
+ noContent();
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 좋아요 버튼
|
|
|
+ const favoriteBtn = (indexNumber) => {
|
|
|
+ const favoriteNum = event.target.querySelector('.count').textContent;
|
|
|
+ const favorite = event.target.querySelector('.favorite-btn');
|
|
|
+ let counts = parseInt(favoriteNum);
|
|
|
+ let newCounts = counts + 1;
|
|
|
+
|
|
|
+ // 로그인한 경우에만 실행
|
|
|
+ if("${ssIsLogin}" === "true") {
|
|
|
+ if(event.target.classList.contains('active')) { // 좋아요 버튼 해지
|
|
|
+ event.target.querySelector('.count').textContent = counts - 1;
|
|
|
+ event.target.classList.remove('active');
|
|
|
+
|
|
|
+ fetch('/article/' + indexNumber + '/favorite', { method: 'DELETE' })
|
|
|
+ .then(response => {
|
|
|
+ if(response.status === 200) {
|
|
|
+ return ;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else { // 좋아요 버튼 활성
|
|
|
+ event.target.querySelector('.count').textContent = newCounts;
|
|
|
+ event.target.classList.add('active');
|
|
|
+
|
|
|
+ fetch('/article/' + indexNumber + '/favorite', {
|
|
|
+ body: JSON.stringify({
|
|
|
+ articleId: "${article.id}",
|
|
|
+ userId: "${ssId}",
|
|
|
+ created: new Date()
|
|
|
+ }),
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json"
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .then(response => {
|
|
|
+ if(response.status === 201) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ location.href = "/user/signin";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // 사용자 팔로우
|
|
|
+ const userFollow = () => {
|
|
|
+ const followBtn = document.querySelector('.follow-btn');
|
|
|
+
|
|
|
+ if("${ssIsLogin}" === "true") { // 로그인한 경우에만 팔로우 기능 사용 가능
|
|
|
+ if(followBtn.classList.contains('active')) { // 팔로우 취소
|
|
|
+ followBtn.querySelector('.follow').textContent = follow;
|
|
|
+ fetch("/user/unfollow", {
|
|
|
+ body: JSON.stringify({
|
|
|
+ fromUser: "${ssId}",
|
|
|
+ toUser: "${user.id}"
|
|
|
+ }),
|
|
|
+ method: 'DELETE',
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json"
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .then(response => {
|
|
|
+ if(response.status === 200) {
|
|
|
+ return followBtn.classList.remove('active');
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else { // 팔로우하기
|
|
|
+ followBtn.querySelector('.follow').textContent = unfollow;
|
|
|
+ fetch("/user/follow", {
|
|
|
+ body: JSON.stringify({
|
|
|
+ fromUser: "${ssId}",
|
|
|
+ toUser: "${user.id}"
|
|
|
+ }),
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ "Content-Type": 'application/json'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .then(response => {
|
|
|
+ if(response.status === 201) {
|
|
|
+ return followBtn.classList.add('active');
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else { // 로그인하지 않은 경우 로그인화면으로 이동
|
|
|
+ location.href = "/user/signin"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 게시글 목록 조회
|
|
|
const displayArticles = (articles) => {
|
|
|
articles.forEach(article => {
|
|
|
const domParser = new DOMParser();
|
|
@@ -53,11 +213,11 @@
|
|
|
`
|
|
|
<div class="article-meta">
|
|
|
<div class="metadata">
|
|
|
- <a href="javascript.void(0);">
|
|
|
- <img src"/resources/images/avatar.png" alt="avatar">
|
|
|
+ <a href="/user/\${article.writerId}">
|
|
|
+ <img src="/resources/images/avatar.png" alt="avatar">
|
|
|
</a>
|
|
|
<div class="article-info">
|
|
|
- <a href="javascript.void(0);" class="name">\${article.writerName}</a>
|
|
|
+ <a href="/user/\${article.writerId}" class="name">\${article.writerName}</a>
|
|
|
<span class="date"></span>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -80,9 +240,9 @@
|
|
|
// 게시글 내용 및 게시글태그
|
|
|
const domStrPreviewLink =
|
|
|
`
|
|
|
- <a href="/article/\${articleId}" class="preview-link">
|
|
|
- <h1 class="preview-title">\${articleTitle}</h1>
|
|
|
- <p>\${articleSubtitle}</p>
|
|
|
+ <a href="/article/\${article.id}" class="preview-link">
|
|
|
+ <h1 class="preview-title">\${article.title}</h1>
|
|
|
+ <p>\${article.subtitle}</p>
|
|
|
<div class="tag-data">
|
|
|
<span>Read more...</span>
|
|
|
<ul class="tag-list">
|
|
@@ -110,12 +270,79 @@
|
|
|
const articlePreview = document.createElement('article');
|
|
|
articlePreview.classList.add('article-preview');
|
|
|
articlePreview.append(divArticleMeta, aPreviewLink);
|
|
|
+ articlePreview.style.display = 'block';
|
|
|
articleList.appendChild(articlePreview);
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ const nextPageLoad = (articles, paging) => {
|
|
|
+ // 더 조회될 수 있는 게시글이 있는지 여부
|
|
|
+ if(paging.isNext) {
|
|
|
+ const moreButton = document.createElement('button');
|
|
|
+ const articleList = document.querySelector('#article-list');
|
|
|
+ const loading = document.createElement('div');
|
|
|
+
|
|
|
+ moreButton.textContent = '더보기';
|
|
|
+ moreButton.classList.add('more-button');
|
|
|
+ loading.setAttribute('id', 'loading');
|
|
|
+ articleList.appendChild(moreButton);
|
|
|
+
|
|
|
+ // more 버튼 클릭 시 다음페이지 게시물을 가져온다
|
|
|
+ moreButton.onclick = () => {
|
|
|
+ articleList.removeChild(moreButton);
|
|
|
+ articleList.appendChild(loading); // 로딩 생성
|
|
|
+
|
|
|
+ // 현재 페이지의 마지막 게시글 id
|
|
|
+ params['articleId'] = articles[articles.length - 1].id;
|
|
|
+ query = Object.keys(params)
|
|
|
+ .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
|
|
|
+ .join('&');
|
|
|
+ url = '/article/page/users/${user.id}?' + query;
|
|
|
+
|
|
|
+ fetch(url, options)
|
|
|
+ .then(response => response.json())
|
|
|
+ .then(json => {
|
|
|
+ const {
|
|
|
+ articles,
|
|
|
+ paging
|
|
|
+ } = json;
|
|
|
+ username = "${ssUsername}";
|
|
|
+ setTimeout(() => {
|
|
|
+ articleList.removeChild(loading);
|
|
|
+ displayArticles(articles);
|
|
|
+ nextPageLoad(articles, paging);
|
|
|
+ }, 1000);
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
window.onload = () => {
|
|
|
targetFeed = document.querySelector('.nav-item.active');
|
|
|
+ loadingArticles = window.document.querySelector('#article-loading');
|
|
|
+ articleList = document.querySelector('#article-list');
|
|
|
+
|
|
|
+ setLoading('on');
|
|
|
+ // fetch api call
|
|
|
+ fetch(url, options)
|
|
|
+ .then(response => response.json())
|
|
|
+ .then(json => {
|
|
|
+ const { articles, paging } = json;
|
|
|
+ username = "${ssUsername}";
|
|
|
+
|
|
|
+ displayArticles(articles);
|
|
|
+ nextPageLoad(articles, paging);
|
|
|
+
|
|
|
+ // hide loadings
|
|
|
+ setTimeout(() => {
|
|
|
+ setLoading('off');
|
|
|
+ noContent();
|
|
|
+ }, 1000);
|
|
|
+ });
|
|
|
+
|
|
|
+ const followString = document.querySelector('.follow-btn');
|
|
|
+ if(followString.classList.contains('active')) {
|
|
|
+ followString.querySelector('.follow').textContent = unfollow;
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
@@ -126,9 +353,8 @@
|
|
|
<jsp:include page="/WEB-INF/jsp/include/header.jsp"></jsp:include>
|
|
|
<!-- user-page content -->
|
|
|
|
|
|
- ${myArticles}
|
|
|
- ${paging}
|
|
|
${user}
|
|
|
+
|
|
|
|
|
|
<div class="user-page">
|
|
|
<!-- User Info -->
|
|
@@ -136,19 +362,22 @@
|
|
|
<div class="container">
|
|
|
<div class="row">
|
|
|
<div class="col-10">
|
|
|
- <img src="img/avatar.jpg" alt="" class="user-img">
|
|
|
- <h4 class="user-name">username</h4>
|
|
|
- <p class="profile-bio">short bio</p>
|
|
|
- <a href="settings.html" class="action-btn btn-sm">
|
|
|
+ <img src="/resources/images/avatar.png" alt="" class="user-img">
|
|
|
+ <h4 class="user-name"><c:out value="${user.username}"></c:out></h4>
|
|
|
+ <p class="profile-bio"><c:out value="${user.shortBio}"></c:out></p>
|
|
|
<c:choose>
|
|
|
- <c:when test="{id eq ssId}">
|
|
|
- <i class="fas fa-cog"></i> Edit Profile Settings
|
|
|
+ <c:when test="${user.id eq ssId}">
|
|
|
+ <a href="/user/settings/${user.id}" class="action-btn btn-sm">
|
|
|
+ <i class="fas fa-cog"></i> Edit Profile Settings
|
|
|
+ </a>
|
|
|
</c:when>
|
|
|
<c:otherwise>
|
|
|
- <i class="fas fa-plus"></i> Follow Gerome
|
|
|
+ <button class="action-btn btn-sm follow-btn" onclick="userFollow()">
|
|
|
+ <i class="fas fa-plus"></i>
|
|
|
+ <span class="follow"> Follow <c:out value="${user.username}"/></span>
|
|
|
+ </button>
|
|
|
</c:otherwise>
|
|
|
</c:choose>
|
|
|
- </a>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -163,7 +392,7 @@
|
|
|
<li id="your-feed" class="nav-item active">
|
|
|
<a href="javascript:void(0);" onclick="focusFeed()">My Articles</a>
|
|
|
</li>
|
|
|
- <li id="favoreted-feed" class="nav-item">
|
|
|
+ <li id="favorite-feed" class="nav-item">
|
|
|
<a href="javascript:void(0);" onclick="focusFeed()">Favorited Articles</a>
|
|
|
</li>
|
|
|
</ul>
|
|
@@ -177,95 +406,7 @@
|
|
|
</article>
|
|
|
|
|
|
<!-- Article List -->
|
|
|
- <div id="article-list" style="display: none;">
|
|
|
- <!-- <article class="article-preview">
|
|
|
- <div class="article-meta">
|
|
|
- <div class="metadata">
|
|
|
- <a href="#" class="profile-link">
|
|
|
- <img src="img/avatar.jpg" alt="">
|
|
|
- </a>
|
|
|
- <div class="article-info">
|
|
|
- <a href="#" class="name">username</a>
|
|
|
- <span class=date>November 24, 2021</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <button class="favorite-btn">
|
|
|
- <i class="fas fa-heart"></i>
|
|
|
- <span class="count">564</span>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <a href="article-my.html" class="preview-link">
|
|
|
- <h1 class="preview-title">Create a new implementation</h1>
|
|
|
- <p>join the community by creating a new implementation</p>
|
|
|
- <div class="tag-data">
|
|
|
- <span>Read more...</span>
|
|
|
- <ul class="tag-list">
|
|
|
- <li class="tag">implementations</li>
|
|
|
- <li class="tag">implementations</li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </a>
|
|
|
- </article>
|
|
|
- <article class="article-preview">
|
|
|
- <div class="article-meta">
|
|
|
- <div class="metadata">
|
|
|
- <a href="#" class="profile-link">
|
|
|
- <img src="img/avatar.jpg" alt="">
|
|
|
- </a>
|
|
|
- <div class="article-info">
|
|
|
- <a href="#" class="name">username</a>
|
|
|
- <span class=date>November 24, 2021</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <button class="favorite-btn">
|
|
|
- <i class="fas fa-heart"></i>
|
|
|
- <span class="count">564</span>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <a href="article-my.html" class="preview-link">
|
|
|
- <h1 class="preview-title">Create a new implementation</h1>
|
|
|
- <p>join the community by creating a new implementation</p>
|
|
|
- <div class="tag-data">
|
|
|
- <span>Read more...</span>
|
|
|
- <ul class="tag-list">
|
|
|
- <li class="tag">implementations</li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </a>
|
|
|
- </article>
|
|
|
- <article class="article-preview">
|
|
|
- <div class="article-meta">
|
|
|
- <div class="metadata">
|
|
|
- <a href="#" class="profile-link">
|
|
|
- <img src="img/avatar.jpg" alt="">
|
|
|
- </a>
|
|
|
- <div class="article-info">
|
|
|
- <a href="#" class="name">username</a>
|
|
|
- <span class=date>November 24, 2021</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <button class="favorite-btn">
|
|
|
- <i class="fas fa-heart"></i>
|
|
|
- <span class="count">564</span>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <a href="article-my.html" class="preview-link">
|
|
|
- <h1 class="preview-title">Create a new implementation</h1>
|
|
|
- <p>join the community by creating a new implementation</p>
|
|
|
- <div class="tag-data">
|
|
|
- <span>Read more...</span>
|
|
|
- <ul class="tag-list">
|
|
|
- <li class="tag">implementations</li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </a>
|
|
|
- </article> -->
|
|
|
+ <div id="article-list" style="display: block;">
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|