/**
* 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 = ' \
\
';
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.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());
});