This commit is contained in:
2025-12-26 17:31:21 +09:00
parent 9b29b623c2
commit e8355b3048
46 changed files with 2510 additions and 1765 deletions
+182 -41
View File
@@ -35,49 +35,82 @@
<section class="wrapper style1">
<div class="container">
<div class="row">
<div class="col-12">
<div id="content_inner">
<article>
<section th:each="post , iterStat : ${Posts}">
<div class="box post" th:id="${post.id}" onclick="goToViewer(this)" style="cursor: pointer;">
<span class="image left">
<img th:src="${post.thumb != null and not #strings.isEmpty(post.thumb)} ? ${apiBaseUrl + post.thumb} : @{/images/pic01.jpg}"
alt="Post Thumbnail"
th:onerror="|this.onerror=null; this.src='@{/images/bum.png}';|" />
</span>
<article id="feed-container">
<section th:each="item, iterStat : ${feedItems}" class="feed-item">
<div class="inner">
<h3 style="display: flex; justify-content: space-between; align-items: center;">
<span th:text="${post.title != null and not #strings.isEmpty(post.title)} ? ${post.title} : 'untitled[' + ${#temporals.format(T(java.time.Instant).ofEpochMilli(post.writeTime).atZone(T(java.time.ZoneId).systemDefault()).toLocalDateTime(), 'yyyy-MM-dd HH:mm')} + ']'"></span>
<span style="font-size: 0.75em; color: #888; font-weight: normal; white-space: nowrap; margin-left: 1em;">
(읽음: <span th:text="${post.readCount}">0</span>)
</span>
</h3>
<p style="font-size: 0.9em; color: #555; margin-bottom: 0.5em;" th:text="${'by ' + post.writer}"></p>
<p th:text="${#strings.abbreviate(post.html, 80)}" class="ellipsis"></p>
</div>
</div>
<th:block th:if="${iterStat.count % 3 == 0}">
<section>
<div class="box ad-container" style="padding: 2em; text-align: center;">
<p style="margin-bottom: 1em; color: #888;">- Advertisement -</p>
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-9504446465764716"
data-ad-slot="5334609005"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
</section>
</th:block>
</section>
</article>
<div th:if="${item.type.name() == 'POST'}" class="box post"
th:onclick="|location.href='@{${item.url}}'|" style="cursor: pointer;">
<span class="image left">
<img th:if="${item.thumbnail}" th:src="${apiBaseUrl + item.thumbnail}"
alt="Thumbnail" th:onerror="|this.onerror=null; this.src='@{/images/pic01.jpg}';|" />
<img th:unless="${item.thumbnail}" th:src="@{/images/pic01.jpg}" alt="Default Thumbnail" />
</span>
<div class="inner">
<h3 th:text="${item.title}">제목</h3>
<p style="font-size: 0.9em; color: #555; margin-bottom: 0.5em;"
th:text="${#dates.format(new java.util.Date(item.createdAt), 'yyyy-MM-dd HH:mm')} + ' by ' + ${item.writer}"></p>
<p th:text="${#strings.abbreviate(item.content, 150)}">내용 요약</p>
</div>
</div>
<div th:if="${item.type.name() == 'GIBBERISH'}" class="box post gibberish-card"
th:onclick="|location.href='@{${item.url}}'|"
style="cursor: pointer; background-color: #fff9c4; border-left: 5px solid #fbc02d;">
<div class="inner">
<blockquote>
<i class="icon fa-quote-left" style="color:#fbc02d; margin-right:10px;"></i>
<span th:text="${item.content}" style="font-size: 1.1em; font-weight: bold; color: #333;"></span>
<i class="icon fa-quote-right" style="color:#fbc02d; margin-left:10px;"></i>
</blockquote>
<p style="text-align: right; font-size: 0.8em; color: #777; margin-top: 10px;"
th:text="${#dates.format(new java.util.Date(item.createdAt), 'yyyy-MM-dd HH:mm')}"></p>
</div>
</div>
<div th:if="${item.type.name() == 'BOOKMARK'}" class="box post bookmark-card"
style="border: 1px dashed #3498db;">
<div class="inner" style="display: flex; align-items: center;">
<div style="flex-shrink: 0; margin-right: 20px;" th:if="${item.thumbnail}">
<img th:src="${apiBaseUrl + item.thumbnail}"
style="width: 100px; height: 100px; object-fit: cover; border-radius: 5px;" />
</div>
<div style="flex-grow: 1;">
<h4>
<a th:href="${item.url}" target="_blank" style="text-decoration: none; color: #3498db;">
<i class="icon solid fa-bookmark"></i> <span th:text="${item.title}">북마크 제목</span>
<i class="icon solid fa-external-link-alt" style="font-size: 0.7em;"></i>
</a>
</h4>
<p th:if="${item.content}" th:text="${item.content}" style="font-size: 0.9em; color: #666; margin-bottom: 0.5em;"></p>
<p style="font-size: 0.8em; color: #999;">
Saved on <span th:text="${#dates.format(new java.util.Date(item.createdAt), 'yyyy-MM-dd')}"></span>
</p>
</div>
</div>
</div>
<div th:if="${iterStat.count % 3 == 0}" class="box ad-container" style="padding: 1em; text-align: center; margin-bottom: 2em; background: #f4f4f4;">
<span style="font-size: 0.8em; color: #aaa;">- Advertisement -</span>
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-9504446465764716"
data-ad-slot="5334609005"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
</div>
</section>
</article>
<div id="load-more-container" style="text-align: center; margin-top: 2em; margin-bottom: 2em;">
<button id="btn-load-more" class="button alt"
th:if="${nextCursor != null}"
th:data-cursor="${nextCursor}"
onclick="loadMoreFeed()">
More Stories <i class="icon solid fa-chevron-down"></i>
</button>
<div id="loading-spinner" style="display:none;">
<i class="icon solid fa-spinner fa-spin fa-2x"></i>
</div>
</div>
</div>
@@ -131,5 +164,113 @@
</header>
</div>
</section>
<script th:inline="javascript">
const apiBaseUrl = /*[[${apiBaseUrl}]]*/ ''; // Thymeleaf 변수 바인딩
function loadMoreFeed() {
const btn = document.getElementById('btn-load-more');
const spinner = document.getElementById('loading-spinner');
const container = document.getElementById('feed-container');
// 현재 커서 값 가져오기
const cursor = btn.getAttribute('data-cursor');
if (!cursor) return;
// UI 상태 변경 (로딩 중)
btn.style.display = 'none';
spinner.style.display = 'inline-block';
// API 호출
fetch(`/api/feed?cursor=${cursor}`)
.then(response => response.json())
.then(data => {
// 데이터가 없으면 버튼 숨기고 종료
if (!data.items || data.items.length === 0) {
spinner.style.display = 'none';
return;
}
// 받아온 데이터를 HTML로 변환하여 추가
data.items.forEach(item => {
const html = createFeedItemHtml(item);
// section 태그로 감싸서 추가
const section = document.createElement('section');
section.className = 'feed-item';
section.innerHTML = html;
container.appendChild(section);
});
// 다음 커서 업데이트
if (data.nextCursor) {
btn.setAttribute('data-cursor', data.nextCursor);
btn.style.display = 'inline-block';
} else {
// 더 이상 불러올 게 없으면 버튼 제거
btn.remove();
}
})
.catch(err => {
console.error('Feed load error:', err);
alert('추가 콘텐츠를 불러오는 중 오류가 발생했습니다.');
btn.style.display = 'inline-block';
})
.finally(() => {
spinner.style.display = 'none';
});
}
// JSON 데이터를 HTML 문자열로 변환하는 헬퍼 함수
function createFeedItemHtml(item) {
const dateStr = new Date(item.createdAt).toISOString().split('T')[0];
if (item.type === 'POST') {
const thumbSrc = item.thumbnail ? (apiBaseUrl + item.thumbnail) : '/images/pic01.jpg';
return `
<div class="box post" onclick="location.href='${item.url}'" style="cursor: pointer;">
<span class="image left"><img src="${thumbSrc}" onerror="this.onerror=null; this.src='/images/pic01.jpg';" /></span>
<div class="inner">
<h3>${item.title || 'Untitled'}</h3>
<p style="font-size: 0.9em; color: #555; margin-bottom: 0.5em;">${dateStr} by ${item.writer || 'Bum'}</p>
<p>${item.content ? item.content.substring(0, 150) + '...' : ''}</p>
</div>
</div>`;
}
else if (item.type === 'GIBBERISH') {
return `
<div class="box post gibberish-card" onclick="location.href='${item.url}'"
style="cursor: pointer; background-color: #fff9c4; border-left: 5px solid #fbc02d;">
<div class="inner">
<blockquote>
<i class="icon fa-quote-left" style="color:#fbc02d; margin-right:10px;"></i>
<span style="font-size: 1.1em; font-weight: bold; color: #333;">${item.content}</span>
</blockquote>
<p style="text-align: right; font-size: 0.8em; color: #777;">${dateStr}</p>
</div>
</div>`;
}
else if (item.type === 'BOOKMARK') {
const thumbImg = item.thumbnail ?
`<div style="flex-shrink: 0; margin-right: 20px;"><img src="${apiBaseUrl + item.thumbnail}" style="width: 100px; height: 100px; object-fit: cover; border-radius: 5px;" /></div>` : '';
return `
<div class="box post bookmark-card" style="border: 1px dashed #3498db;">
<div class="inner" style="display: flex; align-items: center;">
${thumbImg}
<div style="flex-grow: 1;">
<h4>
<a href="${item.url}" target="_blank" style="text-decoration: none; color: #3498db;">
<i class="icon solid fa-bookmark"></i> ${item.title}
<i class="icon solid fa-external-link-alt" style="font-size: 0.7em;"></i>
</a>
</h4>
<p style="font-size: 0.9em; color: #666; margin-bottom: 0.5em;">${item.content || ''}</p>
<p style="font-size: 0.8em; color: #999;">Saved on ${dateStr}</p>
</div>
</div>
</div>`;
}
return '';
}
</script>
</th:block>
</html>