228 lines
8.0 KiB
JavaScript
228 lines
8.0 KiB
JavaScript
/**
|
|
* 학습센터 / 뉴스레터 목록: 무한 스크롤·실시간 검색
|
|
* (인라인 스크립트는 HTML 엔티티(&) 처리로 SyntaxError가 날 수 있어 외부 파일로 분리)
|
|
*/
|
|
(function () {
|
|
var cfg = document.getElementById("lecture-page-config");
|
|
var apiPath = (cfg && cfg.getAttribute("data-learning-api")) || "/api/learning/lectures";
|
|
var viewerBasePath = (cfg && cfg.getAttribute("data-viewer-base")) || "/learning";
|
|
|
|
var form = document.querySelector(".filter-panel form");
|
|
var qInput = document.getElementById("learning-filter-q");
|
|
var resultsRoot = document.getElementById("lecture-results-root");
|
|
var countEl = document.getElementById("lecture-total-count");
|
|
|
|
var radios = document.querySelectorAll('#category-filter input[name="category"]');
|
|
if (form && radios.length) {
|
|
radios.forEach(function (radio) {
|
|
radio.addEventListener("change", function () {
|
|
form.submit();
|
|
});
|
|
});
|
|
}
|
|
|
|
var scrollObserver = null;
|
|
var scrollLoading = false;
|
|
var scrollListener = null;
|
|
var scrollRootsBound = [];
|
|
var wheelHandler = null;
|
|
var pollTimer = null;
|
|
|
|
function disconnectInfiniteScroll() {
|
|
if (scrollObserver) {
|
|
scrollObserver.disconnect();
|
|
scrollObserver = null;
|
|
}
|
|
if (scrollListener) {
|
|
scrollRootsBound.forEach(function (t) {
|
|
t.removeEventListener("scroll", scrollListener);
|
|
});
|
|
scrollRootsBound = [];
|
|
window.removeEventListener("resize", scrollListener);
|
|
scrollListener = null;
|
|
}
|
|
if (wheelHandler) {
|
|
document.removeEventListener("wheel", wheelHandler, true);
|
|
wheelHandler = null;
|
|
}
|
|
if (pollTimer) {
|
|
clearInterval(pollTimer);
|
|
pollTimer = null;
|
|
}
|
|
}
|
|
|
|
function setupInfiniteScroll() {
|
|
disconnectInfiniteScroll();
|
|
var loadingEl = document.getElementById("infinite-scroll-loading");
|
|
|
|
function shouldLoadMore() {
|
|
var sentinel = document.getElementById("infinite-scroll-sentinel");
|
|
if (!sentinel || sentinel.getAttribute("data-has-next") !== "true") return false;
|
|
var rect = sentinel.getBoundingClientRect();
|
|
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
var margin = 3200;
|
|
return rect.top <= vh + margin;
|
|
}
|
|
|
|
function loadNextPage() {
|
|
if (scrollLoading) return;
|
|
var sentinel = document.getElementById("infinite-scroll-sentinel");
|
|
var grid = document.getElementById("lecture-grid");
|
|
if (!sentinel || !grid) return;
|
|
if (sentinel.getAttribute("data-has-next") !== "true") return;
|
|
var nextPage = sentinel.getAttribute("data-next-page");
|
|
if (!nextPage) return;
|
|
|
|
scrollLoading = true;
|
|
if (loadingEl) loadingEl.style.display = "block";
|
|
var params = new URLSearchParams(window.location.search);
|
|
params.set("page", nextPage);
|
|
fetch(apiPath + "?" + params.toString(), {
|
|
credentials: "same-origin",
|
|
headers: { Accept: "application/json" },
|
|
})
|
|
.then(function (r) {
|
|
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
if (data.html) {
|
|
grid.insertAdjacentHTML("beforeend", data.html);
|
|
}
|
|
if (data.hasNext) {
|
|
sentinel.setAttribute("data-next-page", String(data.nextPage || parseInt(nextPage, 10) + 1));
|
|
sentinel.setAttribute("data-has-next", "true");
|
|
} else {
|
|
disconnectInfiniteScroll();
|
|
var foot = document.getElementById("lecture-infinite-footer");
|
|
if (foot && foot.parentNode) foot.remove();
|
|
}
|
|
})
|
|
.catch(function (e) {
|
|
var msg = "";
|
|
if (e) {
|
|
if (e.message) msg = e.message;
|
|
else msg = String(e);
|
|
}
|
|
console.warn("[learning] 다음 페이지 로드 실패(재시도 가능):", msg);
|
|
})
|
|
.finally(function () {
|
|
scrollLoading = false;
|
|
if (loadingEl) loadingEl.style.display = "none";
|
|
setTimeout(function () {
|
|
if (shouldLoadMore()) loadNextPage();
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
function loadMoreIfNeeded() {
|
|
if (!shouldLoadMore()) return;
|
|
loadNextPage();
|
|
}
|
|
|
|
var sentinelEl = document.getElementById("infinite-scroll-sentinel");
|
|
var gridEl = document.getElementById("lecture-grid");
|
|
if (!sentinelEl || !gridEl) return;
|
|
|
|
var rafScheduled = false;
|
|
scrollListener = function () {
|
|
if (scrollLoading || rafScheduled) return;
|
|
rafScheduled = true;
|
|
requestAnimationFrame(function () {
|
|
rafScheduled = false;
|
|
loadMoreIfNeeded();
|
|
});
|
|
};
|
|
|
|
function addScrollRoot(r) {
|
|
if (r && scrollRootsBound.indexOf(r) === -1) scrollRootsBound.push(r);
|
|
}
|
|
addScrollRoot(window);
|
|
addScrollRoot(document.scrollingElement || document.documentElement);
|
|
scrollRootsBound.forEach(function (t) {
|
|
t.addEventListener("scroll", scrollListener, { passive: true });
|
|
});
|
|
window.addEventListener("resize", scrollListener, { passive: true });
|
|
|
|
wheelHandler = function () {
|
|
if (scrollLoading) return;
|
|
requestAnimationFrame(loadMoreIfNeeded);
|
|
};
|
|
document.addEventListener("wheel", wheelHandler, { passive: true, capture: true });
|
|
|
|
pollTimer = setInterval(function () {
|
|
if (scrollLoading) return;
|
|
loadMoreIfNeeded();
|
|
}, 400);
|
|
|
|
scrollObserver = new IntersectionObserver(
|
|
function (entries) {
|
|
if (!entries.some(function (e) {
|
|
return e.isIntersecting;
|
|
}))
|
|
return;
|
|
loadNextPage();
|
|
},
|
|
{ root: null, rootMargin: "0px 0px 1600px 0px", threshold: 0 }
|
|
);
|
|
scrollObserver.observe(sentinelEl);
|
|
|
|
setTimeout(loadMoreIfNeeded, 0);
|
|
setTimeout(loadMoreIfNeeded, 400);
|
|
|
|
var loadMoreBtn = document.getElementById("lecture-load-more-btn");
|
|
if (loadMoreBtn) {
|
|
loadMoreBtn.addEventListener("click", function () {
|
|
loadNextPage();
|
|
});
|
|
}
|
|
}
|
|
|
|
function applyLiveSearch() {
|
|
if (!form || !resultsRoot) return;
|
|
var params = new URLSearchParams(new FormData(form));
|
|
params.set("page", "1");
|
|
fetch(apiPath + "?" + params.toString())
|
|
.then(function (r) {
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
if (data.error) return;
|
|
var total = typeof data.totalCount === "number" ? data.totalCount : 0;
|
|
if (countEl) countEl.textContent = total;
|
|
var html = (data.html || "").trim();
|
|
if (total === 0 || !html) {
|
|
disconnectInfiniteScroll();
|
|
resultsRoot.innerHTML = '<p class="empty" id="lecture-empty-msg">등록된 항목이 없습니다.</p>';
|
|
} else {
|
|
var nextP = data.nextPage != null ? data.nextPage : 2;
|
|
var sentinelBlock = data.hasNext
|
|
? '<div id="lecture-infinite-footer"><div id="infinite-scroll-sentinel" class="infinite-scroll-sentinel" data-next-page="' +
|
|
String(nextP) +
|
|
'" data-has-next="true"></div><p class="infinite-scroll-loading" id="infinite-scroll-loading" style="display:none;text-align:center;padding:16px;color:#666;">불러오는 중...</p><div class="lecture-load-more-wrap" id="lecture-load-more-wrap"><button type="button" class="lecture-load-more-btn" id="lecture-load-more-btn">더 불러오기</button></div></div>'
|
|
: "";
|
|
resultsRoot.innerHTML = '<div class="lecture-grid" id="lecture-grid">' + data.html + "</div>" + sentinelBlock;
|
|
setupInfiniteScroll();
|
|
}
|
|
if (window.history && window.history.replaceState) {
|
|
var forUrl = new URLSearchParams(params);
|
|
forUrl.delete("page");
|
|
var u = new URL(viewerBasePath, window.location.origin);
|
|
u.search = forUrl.toString() ? "?" + forUrl.toString() : "";
|
|
window.history.replaceState({}, "", u.pathname + u.search);
|
|
}
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
var searchDebounce;
|
|
if (qInput) {
|
|
qInput.addEventListener("input", function () {
|
|
clearTimeout(searchDebounce);
|
|
searchDebounce = setTimeout(applyLiveSearch, 280);
|
|
});
|
|
}
|
|
|
|
setupInfiniteScroll();
|
|
})();
|