Eroscript 무한 스크롤 방식을 페이지 방식으로 전환하는 확장프로그램 스크립트. 뒤로가기 시 DOM 교체로 페이지네이션이 사라지는 문제 수정 (자가 치유 재초기화).
// ==UserScript==
// @name Eroscript scroll Pagination conversion
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Eroscript 무한 스크롤 방식을 페이지 방식으로 전환하는 확장프로그램 스크립트. 뒤로가기 시 DOM 교체로 페이지네이션이 사라지는 문제 수정 (자가 치유 재초기화).
// @author Irukai
// @match https://discuss.eroscripts.com/*
// @grant none
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 환경 설정
const itemsPerPage = 15;
const maxPages = 10;
const checkInterval = 1000;
const safeMode = true;
const refreshTimeout = 8000; // 갱신 시 새 아이템 대기 최대 시간(ms)
const restoreTimeout = 4000; // 복원 시 배치당 대기 최대 시간(ms)
const refreshPollMs = 250; // 폴링 간격(ms)
const settleWaitMs = 400; // 아이템 증가 확인 후 정착 대기(ms)
const maxRestoreAttempts = 15; // 복원 시 최대 배치 로드 횟수
const multiRefreshBatches = 5; // 연속 갱신 시 로드할 배치 수
const spacerViewportRatio = 1.5; // 스페이서 고정 높이 (뷰포트 대비 배율)
const stateKeyPrefix = 'epc_state:'; // sessionStorage 키 접두사
let currentPage = 1;
let contentContainer = null;
let allItems = [];
let knownIds = new Set(); // 페이지네이션에 반영된 topic ID 집합
let paginationContainer = null;
let spacerEl = null; // 연쇄 로딩 차단용 고정 높이 스페이서
let refreshBtn = null; // 갱신 버튼 (뱃지 갱신용 참조)
let multiRefreshBtn = null; // 연속 갱신 버튼
let isInitialized = false;
let observer = null;
let lastUrl = location.href;
let isLoading = false;
// 페이지네이션 업데이트 중복방지 플래그
let isUpdatingPagination = false;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startScriptSafely);
} else {
startScriptSafely();
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ===== 상태 저장/복원 (뒤로가기 대응, 같은 탭 세션 내 유지) =====
function stateKey() {
return stateKeyPrefix + location.pathname;
}
function saveState() {
try {
sessionStorage.setItem(stateKey(), JSON.stringify({
itemCount: allItems.length,
currentPage: currentPage
}));
} catch (e) {
// sessionStorage 사용 불가 환경이면 조용히 무시
}
}
function loadSavedState() {
try {
const raw = sessionStorage.getItem(stateKey());
if (!raw) return null;
const state = JSON.parse(raw);
if (typeof state.itemCount !== 'number' || typeof state.currentPage !== 'number') return null;
return state;
} catch (e) {
return null;
}
}
function startScriptSafely() {
console.log("Discourse 페이지네이션: 시작 준비 중...");
setupKeyboardShortcuts();
checkPageAndInitialize();
setInterval(checkPageAndInitialize, checkInterval);
function onUrlChange() {
if (location.href !== lastUrl) {
lastUrl = location.href;
console.log("URL 변경 감지됨, 초기화 상태 리셋 및 재초기화 시작");
resetPaginationState();
checkPageAndInitialize();
}
}
window.addEventListener('popstate', onUrlChange);
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
setTimeout(onUrlChange, 500);
};
}
// 초기화 상태 리셋 (URL 변경 또는 DOM 교체 감지 시)
function resetPaginationState() {
isInitialized = false;
knownIds = new Set();
allItems = [];
if (paginationContainer) {
paginationContainer.remove();
paginationContainer = null;
}
refreshBtn = null;
multiRefreshBtn = null;
if (spacerEl) {
spacerEl.remove();
spacerEl = null;
}
if (observer) {
observer.disconnect();
observer = null;
}
contentContainer = null;
}
// ===== 키보드 단축키: ←/→ 페이지 이동 =====
function setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (!isInitialized || isLoading) return;
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return;
// 입력창/에디터에 포커스가 있으면 무시
const t = e.target;
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
if (e.key === 'ArrowLeft') {
if (currentPage > 1) {
e.preventDefault();
showPage(currentPage - 1);
}
} else if (e.key === 'ArrowRight') {
const totalPages = Math.ceil(allItems.length / itemsPerPage);
if (currentPage < totalPages) {
e.preventDefault();
showPage(currentPage + 1);
}
}
});
}
function checkPageAndInitialize() {
if (location.pathname.startsWith('/t/')) {
if (isInitialized) {
resetPaginationState();
}
return;
}
// 자가 치유: 뒤로가기 등으로 Discourse가 DOM을 통째로 교체하면
// 기존 컨테이너/페이지네이션 바가 문서에서 분리(detached)됨 → 리셋 후 재초기화
if (isInitialized) {
const containerDetached = contentContainer && !contentContainer.isConnected;
const paginationDetached = paginationContainer && !paginationContainer.isConnected;
if (containerDetached || paginationDetached) {
console.log("DOM 교체 감지 (detached), 재초기화 시작");
resetPaginationState();
}
}
if ((document.body) && !isInitialized) {
try {
console.log("페이지네이션 초기화 시작 (/c/ 경로)");
initialize();
} catch (error) {
console.error("페이지네이션 초기화 오류:", error);
}
} else if (isInitialized) {
hideNewItemsOnly(); // 자동 갱신 대신 새 아이템 숨김 + 스페이서 갱신만 수행
}
}
// ===== 아이템 식별 (Ember 재렌더링에 안전하도록 topic ID 기반) =====
function itemKey(item) {
if (!item || !item.getAttribute) return null;
return item.getAttribute('data-topic-id');
}
function isKnownItem(item) {
const key = itemKey(item);
if (key !== null) return knownIds.has(key);
// topic ID가 없는 레이아웃이면 마킹 방식으로 대체
return item.dataset && item.dataset.customKnown === '1';
}
// DOM 순서를 유지하면서 knownIds에 등록된 아이템만 allItems로 재구성
// (재렌더링으로 노드가 교체돼도 항상 살아있는 노드를 참조하게 됨)
function syncAllItemsFromDom() {
const domItems = getDiscourseItems();
allItems = domItems.filter(isKnownItem);
}
// 아직 페이지네이션에 반영되지 않은(숨겨진 채 대기 중인) 아이템 수
function getPendingCount() {
return getDiscourseItems().filter(item => !isKnownItem(item)).length;
}
// ===== 새 아이템 처리 =====
// 무한 스크롤로 새로 로드된 아이템은 숨기기만 하고 페이지네이션에 반영하지 않음
// (갱신 버튼을 눌렀을 때만 collectAllItems()로 반영됨)
function hideNewItemsOnly() {
if (!contentContainer) return;
const domItems = getDiscourseItems();
let hiddenNew = 0;
domItems.forEach(item => {
if (!isKnownItem(item)) {
if (item.style.display !== 'none') {
item.style.display = 'none';
hiddenNew++;
}
}
});
if (hiddenNew > 0) {
console.log(`새 아이템 ${hiddenNew}개 숨김 처리 (수동 갱신 대기)`);
}
syncAllItemsFromDom();
updateSpacer();
updateRefreshBadge();
}
// 현재 DOM의 모든 아이템(숨겨져 있던 새 아이템 포함)을 페이지네이션에 흡수
function collectAllItems() {
const domItems = getDiscourseItems();
domItems.forEach(item => {
const key = itemKey(item);
if (key !== null) {
knownIds.add(key);
} else if (item.dataset) {
item.dataset.customKnown = '1';
}
});
allItems = domItems;
console.log(`${allItems.length}개의 아이템을 찾았습니다.`);
if (isInitialized) {
allItems.forEach(item => item.style.display = 'none');
}
}
// ===== 스페이서: 고정 높이(뷰포트 150%)로 연쇄 로딩 차단 + 스크롤바 길이 유지 =====
function getSpacerHost() {
if (!contentContainer) return null;
if (contentContainer.tagName === 'TBODY') return contentContainer;
const tbody = contentContainer.querySelector('tbody');
return tbody || contentContainer;
}
function updateSpacer() {
const host = getSpacerHost();
if (!host) return;
// 스페이서 생성 (테이블이면 tr, 아니면 div)
if (!spacerEl || !spacerEl.isConnected) {
if (spacerEl) spacerEl.remove();
if (host.tagName === 'TBODY') {
spacerEl = document.createElement('tr');
spacerEl.className = 'custom-pagination-spacer';
const td = document.createElement('td');
td.colSpan = 99;
td.style.padding = '0';
td.style.border = 'none';
const inner = document.createElement('div');
inner.style.height = '0px';
td.appendChild(inner);
spacerEl.appendChild(td);
} else {
spacerEl = document.createElement('div');
spacerEl.className = 'custom-pagination-spacer';
spacerEl.style.height = '0px';
}
host.appendChild(spacerEl);
}
// 항상 리스트 맨 끝에 위치하도록 유지 (새 행이 뒤에 붙었을 경우)
if (host.lastElementChild !== spacerEl) {
host.appendChild(spacerEl);
}
// 고정 높이: 숨겨진 아이템이 하나라도 있으면 뷰포트의 150%, 없으면 0
const domItems = getDiscourseItems();
const hiddenCount = domItems.filter(i => i.style.display === 'none').length;
const h = hiddenCount > 0 ? Math.round(window.innerHeight * spacerViewportRatio) : 0;
const target = spacerEl.tagName === 'TR' ? spacerEl.querySelector('div') : spacerEl;
if (target) target.style.height = h + 'px';
}
// ===== 폴링 대기 =====
// 고정 대기 대신 아이템 증가를 감시해서 오판 방지
async function waitForNewItems(prevCount, timeoutMs) {
const timeout = timeoutMs || refreshTimeout;
const start = Date.now();
while (Date.now() - start < timeout) {
await sleep(refreshPollMs);
if (getDiscourseItems().length > prevCount) {
await sleep(settleWaitMs); // 같은 배치의 나머지 행 도착 대기
return true;
}
}
return false;
}
// ===== 바닥 로딩 트리거 =====
// 이미 바닥에 있으면 scrollTo가 no-op이라 scroll 이벤트가 발생하지 않고,
// Discourse가 바닥 체크를 다시 하지 않아 로딩이 멈춘다.
// 위로 살짝 올렸다가 다시 바닥으로 이동해 이벤트를 강제로 발생시킨다.
async function triggerBottomLoad() {
const upTarget = Math.max(0, document.body.scrollHeight - window.innerHeight * 2);
window.scrollTo(0, upTarget);
await sleep(60);
window.scrollTo(0, document.body.scrollHeight);
// 보조: 위치 변화가 없었을 경우를 대비한 합성 scroll 이벤트
window.dispatchEvent(new Event('scroll'));
}
// ===== 뒤로가기 복원: 저장된 아이템 수까지 배치를 자동 재로드 =====
async function restoreLoadedItems(targetCount) {
if (isLoading) return;
isLoading = true;
console.log(`복원 시작: 목표 ${targetCount}개 (현재 ${getDiscourseItems().length}개)`);
try {
let attempts = 0;
let cur = getDiscourseItems().length;
while (cur < targetCount && attempts < maxRestoreAttempts) {
await triggerBottomLoad();
const gotNew = await waitForNewItems(cur, restoreTimeout);
const next = getDiscourseItems().length;
if (!gotNew || next === cur) {
console.log("더 이상 로드되지 않음, 복원 중단");
break;
}
cur = next;
attempts++;
console.log(`복원 진행: ${cur}/${targetCount} (시도 ${attempts})`);
}
window.scrollTo(0, 0);
console.log(`복원 완료: ${cur}개 로드됨`);
} finally {
isLoading = false;
}
}
// ===== 초기화 =====
async function initialize() {
if (isInitialized) return;
console.log("Discourse 페이지네이션: 초기화 중...");
try {
contentContainer = findDiscourseContentContainer();
if (!contentContainer) {
console.log("콘텐츠 컨테이너를 아직 찾을 수 없습니다. 나중에 다시 시도합니다.");
return;
}
console.log("콘텐츠 컨테이너 찾음:", contentContainer);
if (!safeMode) {
disableInfiniteScrollSafely();
}
collectAllItems();
if (allItems.length === 0) {
console.warn("표시할 아이템을 찾을 수 없습니다.");
return;
}
createPaginationInterface();
// 복원 중 인터벌/옵저버 재진입을 막기 위해 먼저 초기화 완료로 표시
isInitialized = true;
setupContentObserver();
// 저장된 기록이 있고 DOM 아이템이 부족하면 자동 재로드
const saved = loadSavedState();
if (saved && saved.itemCount > getDiscourseItems().length) {
await restoreLoadedItems(saved.itemCount);
collectAllItems();
}
checkUrlForPageNumber(saved ? saved.currentPage : null);
console.log("페이지네이션 초기화 완료!");
} catch (error) {
console.error("초기화 중 오류 발생:", error);
}
}
function disableInfiniteScrollSafely() {
if (window._discourse_scroll_trackers) {
try {
window._discourse_scroll_trackers.forEach((tracker, i) => {
if (tracker && typeof tracker === 'function') {
window._discourse_scroll_trackers[i] = function() {};
}
});
} catch (e) {
console.error("스크롤 트래커 비활성화 오류:", e);
}
}
if (window.MessageBus && safeMode === false) {
try {
if (typeof window.MessageBus.baseInterval === 'number') {
window.MessageBus.baseInterval = 120000;
}
if (typeof window.MessageBus.unsubscribe === 'function') {
window.MessageBus.unsubscribe('/latest');
window.MessageBus.unsubscribe('/new');
}
console.log("MessageBus 설정 조정됨");
} catch (e) {
console.error("MessageBus 조정 오류:", e);
}
}
}
function findDiscourseContentContainer() {
const candidates = [
document.querySelector('.topic-list tbody'),
document.querySelector('table.topic-list'),
document.querySelector('.topic-list'),
document.querySelector('.latest-topic-list'),
document.querySelector('.category-list'),
document.querySelector('#list-area')
];
for (const c of candidates) {
if (!c) continue;
// 토픽 페이지 하단의 추천/관련 토픽 목록은 콘텐츠 컨테이너로 오인하지 않음
if (c.closest('.suggested-topics, #suggested-topics, .more-topics__list, .related-topics')) continue;
return c;
}
return null;
}
function getDiscourseItems() {
if (!contentContainer) return [];
const rows = contentContainer.querySelectorAll('tr.topic-list-item');
if (rows.length > 0) return Array.from(rows);
const topicItems = contentContainer.querySelectorAll('.latest-topic-list-item');
if (topicItems.length > 0) return Array.from(topicItems);
const alternateItems = contentContainer.querySelectorAll('.topic-list-item');
if (alternateItems.length > 0) return Array.from(alternateItems);
if (contentContainer.children.length > 0) {
// 스페이서는 아이템에서 제외
return Array.from(contentContainer.children)
.filter(el => !el.classList.contains('custom-pagination-spacer'));
}
return [];
}
// ===== 페이지네이션 UI =====
function createPaginationInterface() {
if (paginationContainer) paginationContainer.remove();
paginationContainer = document.createElement('div');
paginationContainer.className = 'custom-discourse-pagination';
paginationContainer.style.cssText = `
display: flex;
justify-content: center;
align-items: center;
margin: 20px 0;
padding: 10px 15px;
background-color: #ffffff;
border-radius: 5px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
position: sticky;
bottom: 10px;
z-index: 1000;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
width: 100%;
max-width: none;
font-size: 12px;
overflow-x: auto;
white-space: nowrap;
flex-wrap: nowrap;
`;
updatePaginationInterface();
let inserted = false;
const candidates = [
document.querySelector('.topic-list'),
document.querySelector('table.topic-list'),
contentContainer,
document.querySelector('.list-controls'),
document.querySelector('#main-outlet'),
document.querySelector('main .container'),
document.body
];
for (const c of candidates) {
if (c && !inserted) {
if (c === contentContainer) {
c.parentNode.insertBefore(paginationContainer, c.nextSibling);
} else {
c.appendChild(paginationContainer);
}
inserted = true;
break;
}
}
}
function updatePaginationInterface() {
if (!paginationContainer) return;
const totalItems = allItems.length;
const totalPages = Math.ceil(totalItems / itemsPerPage);
paginationContainer.innerHTML = '';
refreshBtn = null;
multiRefreshBtn = null;
if (currentPage > 1) addPageButton('<<', 1);
if (currentPage > 1) addPageButton('<', currentPage - 1);
const half = Math.floor(maxPages / 2);
let startPage = currentPage - half;
let endPage = currentPage + half;
if (maxPages % 2 === 0) endPage -= 1;
if (startPage < 1) {
endPage += 1 - startPage;
startPage = 1;
}
if (endPage > totalPages) {
startPage -= (endPage - totalPages);
endPage = totalPages;
if (startPage < 1) startPage = 1;
}
let displayedPages = endPage - startPage + 1;
if (displayedPages > maxPages) {
endPage = startPage + maxPages - 1;
if (endPage > totalPages) endPage = totalPages;
}
for (let i = startPage; i <= endPage; i++) {
addPageButton(i.toString(), i, i === currentPage);
}
if (currentPage < totalPages) addPageButton('>', currentPage + 1);
if (currentPage < totalPages) addPageButton('>>', totalPages);
addRefreshButtons();
updateRefreshBadge();
}
// 갱신 버튼 뱃지: 숨겨진 채 대기 중인 아이템 수 표시
function updateRefreshBadge() {
if (!refreshBtn || isLoading) return;
const pending = getPendingCount();
refreshBtn.textContent = pending > 0 ? `갱신 (+${pending})` : '갱신';
}
// ===== 갱신 공통 로직: batches개의 배치를 로드하고 새 아이템 첫 페이지로 이동 =====
async function performRefresh(batches, progressBtn) {
if (isLoading) return;
isLoading = true;
const buttons = [refreshBtn, multiRefreshBtn].filter(Boolean);
buttons.forEach(b => { b.disabled = true; });
try {
const savedScrollY = window.scrollY;
const prevTotal = allItems.length;
// 이미 숨겨진 채 대기 중인 아이템은 배치 하나로 간주
let loadedAny = getPendingCount() > 0;
let remaining = batches - (loadedAny ? 1 : 0);
for (let i = 0; i < remaining; i++) {
if (progressBtn) progressBtn.textContent = `로딩 ${i + 1}/${remaining}`;
const cur = getDiscourseItems().length;
await triggerBottomLoad();
const gotNew = await waitForNewItems(cur);
if (!gotNew) break;
loadedAny = true;
}
if (!loadedAny) {
window.scrollTo(0, savedScrollY);
alert('추가로 로드할 항목이 없습니다.');
return;
}
collectAllItems();
updateSpacer();
// 새로 추가된 아이템이 시작되는 페이지로 자동 이동
const firstNewPage = Math.floor(prevTotal / itemsPerPage) + 1;
showPage(firstNewPage); // 내부에서 updatePaginationInterface() 호출 → 버튼 상태 복구
} finally {
isLoading = false;
// 실패 경로에서도 버튼 상태 복구
if (refreshBtn) { refreshBtn.disabled = false; }
if (multiRefreshBtn) { multiRefreshBtn.disabled = false; multiRefreshBtn.textContent = `+${multiRefreshBatches}배치`; }
updateRefreshBadge();
}
}
function addRefreshButtons() {
const baseStyle = `
margin-left: 6px;
padding: 5px 12px;
color: #fff;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
`;
// 갱신 (1배치)
refreshBtn = document.createElement('button');
refreshBtn.textContent = '갱신';
refreshBtn.style.cssText = baseStyle + `
margin-left: 10px;
background-color: #28a745;
border: 1px solid #28a745;
`;
refreshBtn.addEventListener('click', async () => {
refreshBtn.textContent = '로딩...';
await performRefresh(1, null);
});
paginationContainer.appendChild(refreshBtn);
// 연속 갱신 (N배치)
multiRefreshBtn = document.createElement('button');
multiRefreshBtn.textContent = `+${multiRefreshBatches}배치`;
multiRefreshBtn.style.cssText = baseStyle + `
background-color: #17a2b8;
border: 1px solid #17a2b8;
`;
multiRefreshBtn.addEventListener('click', async () => {
await performRefresh(multiRefreshBatches, multiRefreshBtn);
});
paginationContainer.appendChild(multiRefreshBtn);
}
function addPageButton(label, pageNum, isActive = false) {
const button = document.createElement('button');
button.textContent = label;
button.dataset.page = pageNum;
button.style.cssText = `
margin: 0 3px;
padding: 5px 10px;
background-color: ${isActive ? '#0088cc' : '#f8f9fa'};
color: ${isActive ? '#fff' : '#0088cc'};
border: 1px solid ${isActive ? '#0088cc' : '#ddd'};
border-radius: 3px;
cursor: pointer;
font-weight: ${isActive ? 'bold' : 'normal'};
transition: all 0.2s ease;
font-size: 12px;
white-space: nowrap;
`;
button.addEventListener('click', () => {
showPage(pageNum);
});
button.addEventListener('mouseover', () => {
if (!isActive) button.style.backgroundColor = '#e9ecef';
});
button.addEventListener('mouseout', () => {
if (!isActive) button.style.backgroundColor = '#f8f9fa';
});
paginationContainer.appendChild(button);
}
function showPage(pageNum) {
// 재렌더링 대비: 표시 직전에 살아있는 DOM 노드로 재동기화
syncAllItemsFromDom();
if (!allItems.length) return;
const totalPages = Math.ceil(allItems.length / itemsPerPage);
if (pageNum < 1) pageNum = 1;
if (pageNum > totalPages) pageNum = totalPages;
currentPage = pageNum;
const startIdx = (pageNum - 1) * itemsPerPage;
const endIdx = Math.min(startIdx + itemsPerPage, allItems.length);
allItems.forEach(item => item.style.display = 'none');
for (let i = startIdx; i < endIdx; i++) {
if (allItems[i]) allItems[i].style.display = '';
}
console.log(`페이지 ${pageNum} 표시: ${endIdx - startIdx}개 아이템`);
updateSpacer();
updatePaginationInterface();
saveState(); // 뒤로가기 복원용 상태 저장
const scrollTarget = contentContainer.offsetTop - 100;
window.scrollTo({top: Math.max(0, scrollTarget), behavior: 'smooth'});
if (history.pushState) {
try {
const pageParam = new URLSearchParams(window.location.search);
pageParam.set('custom_page', pageNum);
const newUrl = window.location.pathname + '?' + pageParam.toString();
window.history.replaceState({path: newUrl}, '', newUrl);
} catch (e) {
console.error("URL 업데이트 오류:", e);
}
}
}
function isInsideMedia(node) {
if (!node) return false;
let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
while (el) {
const tag = el.tagName && el.tagName.toLowerCase();
if (tag === 'video' || tag === 'audio' || tag === 'img' || tag === 'iframe') {
return true;
}
el = el.parentElement;
}
return false;
}
function setupContentObserver() {
if (observer) observer.disconnect();
observer = new MutationObserver((mutations) => {
let needsUpdate = false;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
if (paginationContainer && (paginationContainer.contains(mutation.target) || mutation.target === paginationContainer)) {
continue;
}
if (spacerEl && (mutation.target === spacerEl || spacerEl.contains(mutation.target))) {
continue;
}
if (isInsideMedia(mutation.target)) {
continue;
}
if (contentContainer.contains(mutation.target) || mutation.target === contentContainer) {
needsUpdate = true;
break;
}
}
}
// 자동 갱신 제거: 새 콘텐츠는 숨김 + 스페이서 갱신 + 뱃지 갱신만 하고
// 페이지네이션 반영은 갱신 버튼에서만 수행
if (needsUpdate && !isUpdatingPagination) {
isUpdatingPagination = true;
setTimeout(() => {
hideNewItemsOnly();
isUpdatingPagination = false;
}, 300);
}
});
observer.observe(contentContainer, {childList: true, subtree: true});
console.log("콘텐츠 변화 감지 옵저버 설정됨");
}
function checkUrlForPageNumber(savedPage) {
try {
const urlParams = new URLSearchParams(window.location.search);
let pageParam = urlParams.get('custom_page');
const totalItems = allItems.length;
const totalPages = Math.ceil(totalItems / itemsPerPage);
let pageNum = 1;
if (pageParam !== null) {
pageNum = parseInt(pageParam);
} else if (savedPage !== null && savedPage !== undefined) {
// URL에 파라미터가 없으면 저장된 페이지로 복원
pageNum = savedPage;
}
if (isNaN(pageNum) || pageNum < 1) {
pageNum = 1;
} else if (pageNum > totalPages) {
pageNum = totalPages > 0 ? totalPages : 1;
}
console.log(`페이지 번호 결정: ${pageNum}`);
currentPage = pageNum;
showPage(pageNum);
} catch (e) {
console.error("URL 파라미터 확인 오류:", e);
showPage(1);
}
}
})();