/** * 2026.01.07 작성 * 태그별 게시물 조회 JS */ class ProcessTags { constructor() { this.loading = false; this.tags = document.getElementById('tags'); this.posts = document.getElementById('posts'); this.pagination = document.getElementById('pagination'); } setActiveTag(target) { const links = this.tags.querySelectorAll('.tag-nav-link'); links.forEach(link => link.classList.remove('active')); target.classList.add('active'); } updateQueryString(tag, page) { const url = new URL(window.location.href); if (tag) { url.searchParams.set('name', tag); } else { url.searchParams.delete('name'); } if (page > 1) { url.searchParams.set('page', page.toString()); } else { url.searchParams.delete('page'); } window.history.pushState({}, '', url.toString()); } getTagFromQuery() { return new URLSearchParams(window.location.search).get('name')?.trim(); } getPageFromQuery() { return parseInt(new URLSearchParams(window.location.search).get('page') || '1', 10) || 1; } findLinkByTag(tag) { if (!tag) { return null; } const links = this.tags.querySelectorAll('.tag-nav-link'); for (const link of links) { const name = (link.dataset.name || link.textContent || '').trim(); if (name === tag) { return link; } } return null; } async loadPosts(tag, page) { const params = new URLSearchParams({ name: tag, page: page }); this.updateQueryString(tag, page); const nav = this.findLinkByTag(tag); if (nav) { this.setActiveTag(nav); } this.posts.innerHTML = ' \
\
\ Loading... \
\
\ '; try { if (this.loading) { return; } this.loading = true; const res = await fetch(`${BASE_URL}/tag/posts?${params.toString()}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json', } }); if (!res.ok) { throw new Error(); } const data = await res.json().then(r => r.data); this.renderPosts(data); this.renderPagination(data); } catch (error) { this.posts.innerHTML = `

조회 중 오류가 발생했습니다.
잠시 후 다시 시도해주세요.

`; } finally { this.loading = false; } } async fetchPosts(e) { const target = e.target.closest?.('.tag-nav-link, .tag-sub-link, .page-link'); if (!target) { return; } if (target.classList.contains('active') && target.classList.contains('tag-nav-link')) { this.posts.innerHTML = '
태그를 선택하면 게시글이 조회됩니다.
'; this.pagination.innerHTML = ''; target.classList.remove('active'); this.updateQueryString('', 0); return; } let tag = (target.dataset.name || target.textContent || '').trim(); let page = 1; if (target.classList.contains('page-link')) { e.preventDefault(); tag = this.getTagFromQuery(); page = parseInt(target.dataset.page || '1', 10) || 1; } return this.loadPosts(tag, page); } renderPosts(res) { if (!res || res.rows <= 0) { this.posts.innerHTML = '

관련 게시물이 없습니다.

'; return; } let html = ""; res.list.forEach(function(item) { html += `
  • ${item.thumbnail ? ` ` : ''}
    ${item.createdAt}
    ${item.content}
    ${item.userName}
    ${item.tags.map(tag => ` #${tag} `).join(',')}
  • `; }); this.posts.innerHTML = html; } renderPagination(res) { const total = parseInt(res.total || '0', 0) || 0; const page = parseInt(res.page || '1', 10) || 1; const last = Math.ceil(total / 10); if (last <= 1) { return; } const make = (p, label, disabled = false, active = false) => `
  • ${label}
  • `; let html = `
    `; this.pagination.innerHTML = html; } initFromUrl() { const tag = this.getTagFromQuery(); if (!tag) { return; } const link = this.findLinkByTag(tag); if (!link) { return; } // 새로고침 시 active 유지 this.setActiveTag(link); // 새로고침 시 자동 조회 this.fetchPosts({ target: link }); } } document.addEventListener('DOMContentLoaded', function() { let processTags = new ProcessTags(); if (processTags.tags) { processTags.tags.addEventListener('click', (e) => processTags.fetchPosts(e)); } if (processTags.posts) { processTags.posts.addEventListener('click', (e) => processTags.fetchPosts(e)); } if (processTags.pagination) { processTags.pagination.addEventListener('click', (e) => processTags.fetchPosts(e)); } // 새로고침/직접접속 시 URL의 tag 반영 processTags.initFromUrl(); // 뒤로가기/앞으로가기 대응 window.addEventListener('popstate', () => processTags.initFromUrl()); });