userDetail.jsp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  2. <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
  3. <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
  4. <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
  5. <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
  6. <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
  7. <jsp:include page="/WEB-INF/jsp/include/head.jsp"></jsp:include>
  8. <script>
  9. let targetFeed = null;
  10. let params = {};
  11. let query = null;
  12. let url = null;
  13. let loadingArticles = null;
  14. let articleList = null;
  15. const tagSet = new Set();
  16. const options = { method: 'GET' };
  17. const follow = 'Follow ' + "${user.username}";
  18. const unfollow = 'Unfollow ' + "${user.username}";
  19. // 게시글 목록 로딩
  20. const setLoading = (type) => {
  21. let loadingDisplay = null;
  22. let contentDisplay = null;
  23. if(type === 'on') {
  24. loadingDisplay = 'block';
  25. contentDisplay = 'none';
  26. } else if(type === 'off') {
  27. loadingDisplay = 'none';
  28. contentDisplay = 'block';
  29. }
  30. loadingArticles.style.display = loadingDisplay;
  31. articleList.style.display = contentDisplay;
  32. }
  33. // 게시글이 없는 경우
  34. const noContent = () => {
  35. if(articleList.firstChild === null) {
  36. const p = document.createElement('p');
  37. p.classList.add('article-preview');
  38. p.textContent = 'No articles are here... yet.';
  39. articleList.appendChild(p);
  40. }
  41. }
  42. const focusFeed = () => {
  43. // 이미 포커싱되었으면 중단
  44. if(event.target.parentNode.classList.contains('active')) {
  45. return ;
  46. }
  47. const currentFeed = event.target.parentNode; // 포커싱할 피드
  48. const focusedFeed = (currentFeed.id === 'favorite-feed') ? document.querySelector('#your-feed') : document.querySelector('#favorite-feed'); // 현재 포커싱되어있는 피드
  49. currentFeed.classList.add('active');
  50. focusedFeed.classList.remove('active');
  51. // targetFeed 초기화
  52. targetFeed = currentFeed;
  53. setLoading('on');
  54. console.log(targetFeed)
  55. console.log(articleList.firstChild)
  56. // 게시글 초기화
  57. while(articleList.firstChild) {
  58. articleList.firstChild.remove();
  59. }
  60. params = {
  61. articleId: -1,
  62. feed: targetFeed.id
  63. };
  64. query = Object.keys(params)
  65. .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
  66. .join('&');
  67. url = '/article/page/users/${user.id}?' + query;
  68. fetch(url, options)
  69. .then(response => response.json())
  70. .then(json => {
  71. const { articles, paging } = json;
  72. username = "${ssUsername}";
  73. displayArticles(articles);
  74. nextPageLoad(articles, paging);
  75. console.log(articleList.firstChild)
  76. setTimeout(() => {
  77. setLoading('off');
  78. noContent();
  79. }, 1000)
  80. })
  81. }
  82. // 좋아요 버튼
  83. const favoriteBtn = (indexNumber) => {
  84. const favoriteNum = event.target.querySelector('.count').textContent;
  85. const favorite = event.target.querySelector('.favorite-btn');
  86. let counts = parseInt(favoriteNum);
  87. let newCounts = counts + 1;
  88. // 로그인한 경우에만 실행
  89. if("${ssIsLogin}" === "true") {
  90. if(event.target.classList.contains('active')) { // 좋아요 버튼 해지
  91. event.target.querySelector('.count').textContent = counts - 1;
  92. event.target.classList.remove('active');
  93. fetch('/article/' + indexNumber + '/favorite', { method: 'DELETE' })
  94. .then(response => {
  95. if(response.status === 200) {
  96. return ;
  97. }
  98. });
  99. } else { // 좋아요 버튼 활성
  100. event.target.querySelector('.count').textContent = newCounts;
  101. event.target.classList.add('active');
  102. fetch('/article/' + indexNumber + '/favorite', {
  103. body: JSON.stringify({
  104. articleId: "${article.id}",
  105. userId: "${ssId}",
  106. created: new Date()
  107. }),
  108. method: 'POST',
  109. headers: {
  110. "Content-Type": "application/json"
  111. }
  112. })
  113. .then(response => {
  114. if(response.status === 201) {
  115. return;
  116. }
  117. })
  118. }
  119. } else {
  120. location.href = "/user/signin";
  121. }
  122. }
  123. // 사용자 팔로우
  124. const userFollow = () => {
  125. const followBtn = document.querySelector('.follow-btn');
  126. const req = {
  127. fromUser: "${ssId}",
  128. toUser: "${user.id}"
  129. };
  130. if("${ssIsLogin}" === "true") { // 로그인한 경우에만 팔로우 기능 사용 가능
  131. if(followBtn.classList.contains('active')) { // 팔로우 취소
  132. followBtn.querySelector('.follow').textContent = follow;
  133. fetch("/user/unfollow", {
  134. body: JSON.stringify(req),
  135. method: 'DELETE',
  136. headers: {
  137. "Content-Type": "application/json"
  138. }
  139. })
  140. .then(response => {
  141. if(response.status === 200) {
  142. return followBtn.classList.remove('active');
  143. }
  144. })
  145. } else { // 팔로우하기
  146. followBtn.querySelector('.follow').textContent = unfollow;
  147. fetch("/user/follow", {
  148. body: JSON.stringify(req),
  149. method: 'POST',
  150. headers: {
  151. "Content-Type": 'application/json'
  152. }
  153. })
  154. .then(response => {
  155. if(response.status === 201) {
  156. return followBtn.classList.add('active');
  157. }
  158. })
  159. }
  160. } else { // 로그인하지 않은 경우 로그인화면으로 이동
  161. location.href = "/user/signin"
  162. }
  163. }
  164. // 게시글 목록 조회
  165. const displayArticles = (articles) => {
  166. articles.forEach(article => {
  167. console.log(JSON.stringify(article))
  168. const domParser = new DOMParser();
  169. // 게시글 정보
  170. const domStrArticleMeta =
  171. `
  172. <div class="article-meta">
  173. <div class="metadata">
  174. <a href="/user/\${article.writerId}">
  175. <img src="/resources/images/avatar.png" alt="avatar">
  176. </a>
  177. <div class="article-info">
  178. <a href="/user/\${article.writerId}" class="name">\${article.writerName}</a>
  179. <span class="date"></span>
  180. </div>
  181. </div>
  182. <div>
  183. <button class="favorite-btn" onclick="favoriteBtn(\${article.id})">
  184. <i class="fas fa-heart"></i>
  185. <span class="count">\${article.favoriteNum}</span>
  186. </button>
  187. </div>
  188. </div>
  189. `;
  190. const divArticleMeta = domParser.parseFromString(domStrArticleMeta, 'text/html').body.firstChild;
  191. divArticleMeta.querySelector('.date').textContent = new Date(article.created).toLocaleString();
  192. // 게시글 좋아요한 경우 active
  193. if (article.favorite) {
  194. divArticleMeta.querySelector('.favorite-btn').classList.add('active');
  195. }
  196. // 게시글 내용 및 게시글태그
  197. const domStrPreviewLink =
  198. `
  199. <a href="/article/\${article.id}" class="preview-link">
  200. <h1 class="preview-title">\${article.title}</h1>
  201. <p>\${article.subtitle}</p>
  202. <div class="tag-data">
  203. <span>Read more...</span>
  204. <ul class="tag-list">
  205. </ul>
  206. </div>
  207. </a>
  208. `;
  209. const aPreviewLink = domParser.parseFromString(domStrPreviewLink, 'text/html').body.firstChild;
  210. const ul = aPreviewLink.querySelector('.tag-list');
  211. if (article.tags !== '') {
  212. article.tags.split(',').forEach(tag => {
  213. if(!tagSet.has(tag)) {
  214. tagSet.add(tag);
  215. }
  216. const li = document.createElement('li');
  217. li.classList.add('tag');
  218. li.textContent = tag;
  219. ul.appendChild(li);
  220. });
  221. }
  222. // 게시글 완성
  223. const articlePreview = document.createElement('article');
  224. articlePreview.classList.add('article-preview');
  225. articlePreview.append(divArticleMeta, aPreviewLink);
  226. articleList.appendChild(articlePreview);
  227. })
  228. }
  229. const nextPageLoad = (articles, paging) => {
  230. // 더 조회될 수 있는 게시글이 있는지 여부
  231. if(paging.isNext) {
  232. const moreButton = document.createElement('button');
  233. const loading = document.createElement('div');
  234. moreButton.textContent = '더보기';
  235. moreButton.classList.add('more-button');
  236. loading.setAttribute('id', 'loading');
  237. articleList.appendChild(moreButton);
  238. // more 버튼 클릭 시 다음페이지 게시물을 가져온다
  239. moreButton.onclick = () => {
  240. articleList.removeChild(moreButton);
  241. articleList.appendChild(loading); // 로딩 생성
  242. // 현재 페이지의 마지막 게시글 id
  243. params.articleId = articles[articles.length - 1].id;
  244. query = Object.keys(params)
  245. .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
  246. .join('&');
  247. url = '/article/page/users/${user.id}?' + query;
  248. fetch(url, options)
  249. .then(response => response.json())
  250. .then(json => {
  251. const {
  252. articles,
  253. paging
  254. } = json;
  255. username = "${ssUsername}";
  256. setTimeout(() => {
  257. articleList.removeChild(loading);
  258. displayArticles(articles);
  259. nextPageLoad(articles, paging);
  260. }, 1000);
  261. })
  262. }
  263. }
  264. }
  265. window.onload = () => {
  266. targetFeed = document.querySelector('.nav-item.active');
  267. loadingArticles = window.document.querySelector('#article-loading');
  268. articleList = document.querySelector('#article-list');
  269. params = {
  270. articleId: -1,
  271. feed: 'your-feed'
  272. };
  273. query = Object.keys(params)
  274. .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
  275. .join('&');
  276. url = '/article/page/users/${user.id}?' + query;
  277. // 팔로우 하고 있는 경우
  278. const followString = document.querySelector('.follow-btn');
  279. if("${isFollow}" === "true"){
  280. followString.classList.add('active');
  281. followString.querySelector('.follow').textContent = unfollow;
  282. }
  283. setLoading('on');
  284. // fetch api call
  285. fetch(url, options)
  286. .then(response => response.json())
  287. .then(json => {
  288. const { articles, paging } = json;
  289. username = "${ssUsername}";
  290. displayArticles(articles);
  291. nextPageLoad(articles, paging);
  292. // hide loadings
  293. setTimeout(() => {
  294. setLoading('off');
  295. noContent();
  296. }, 1000);
  297. });
  298. }
  299. </script>
  300. </head>
  301. <body>
  302. <jsp:include page="/WEB-INF/jsp/include/header.jsp"></jsp:include>
  303. <!-- user-page content -->
  304. <div class="user-page">
  305. <!-- User Info -->
  306. <section class="user-info">
  307. <div class="container">
  308. <div class="row">
  309. <div class="col-10">
  310. <img src="/resources/images/avatar.png" alt="" class="user-img">
  311. <h4 class="user-name"><c:out value="${user.username}"></c:out></h4>
  312. <p class="profile-bio">
  313. <pre>
  314. <c:out value="${user.shortBio}"></c:out>
  315. </pre>
  316. </p>
  317. <c:choose>
  318. <c:when test="${user.id eq ssId}">
  319. <a href="/user/settings/${user.id}" class="action-btn btn-sm">
  320. <i class="fas fa-cog"></i>&nbsp;Edit Profile Settings
  321. </a>
  322. </c:when>
  323. <c:otherwise>
  324. <button class="action-btn btn-sm follow-btn" onclick="userFollow()">
  325. <i class="fas fa-plus"></i>
  326. <span class="follow">&nbsp;Follow <c:out value="${user.username}"/></span>
  327. </button>
  328. </c:otherwise>
  329. </c:choose>
  330. </div>
  331. </div>
  332. </div>
  333. </section>
  334. <!-- Body -->
  335. <div class="container main">
  336. <div class="row">
  337. <div class="col-10">
  338. <!-- 토글 버튼으로 피드 내용 보기 -->
  339. <div class="toggle">
  340. <ul class="nav">
  341. <li id="your-feed" class="nav-item active">
  342. <a href="javascript:void(0);" onclick="focusFeed()">My Articles</a>
  343. </li>
  344. <li id="favorite-feed" class="nav-item">
  345. <a href="javascript:void(0);" onclick="focusFeed()">Favorited Articles</a>
  346. </li>
  347. </ul>
  348. </div>
  349. <!-- Content -->
  350. <div class="article">
  351. <!-- Article Loading -->
  352. <article id="article-loading">
  353. Loading articles...
  354. </article>
  355. <!-- Article List -->
  356. <div id="article-list" style="display: block;"></div>
  357. </div>
  358. </div>
  359. </div>
  360. </div>
  361. </div>
  362. </body>
  363. </html>