// ==UserScript== // @name Colg 帖子紧凑显示 + 自动翻页 // @namespace https://bbs.colg.cn/ // @version 0.1.7 // @description 压缩 Colg 帖子楼层空白,并在滚动到底部时自动加载下一页。 // @author Codex // @match https://bbs.colg.cn/thread-* // @match https://bbs.colg.cn/forum.php?mod=viewthread* // @run-at document-end // @grant none // ==/UserScript== (function () { 'use strict'; const CONFIG = { // compact: 保留小头像和作者名;mini: 隐藏左侧作者栏,只保留正文。 mode: 'compact', autoPager: true, triggerDistance: 900, requestDelayMs: 250, statusHideMs: 2200, smileySize: 70, replyImageMaxWidth: 420, replyImageMaxHeight: 320, }; const STYLE_ID = 'colg-compact-autopager-style'; const POST_SELECTOR = '#postlist > div[id^="post_"]'; const PAGE_SEPARATOR_CLASS = 'colg-auto-page-separator'; const STATUS_ID = 'colg-auto-page-status'; const state = { nextUrl: null, loading: false, done: false, loadedPostIds: new Set(), checkScheduled: false, statusTimer: 0, }; function init() { const postList = document.querySelector('#postlist'); if (!postList) { return; } document.documentElement.classList.add('colg-compact-on'); document.documentElement.classList.add(`colg-mode-${CONFIG.mode}`); injectStyle(); markExistingPosts(); applyCompactDom(document); state.nextUrl = findNextUrl(document, location.href); if (CONFIG.autoPager && state.nextUrl) { window.addEventListener('scroll', scheduleScrollCheck, { passive: true }); window.addEventListener('resize', scheduleScrollCheck, { passive: true }); scheduleScrollCheck(); } observeNewPosts(postList); } function injectStyle() { if (document.getElementById(STYLE_ID)) { return; } const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` html.colg-compact-on #postlist > div[id^="post_"] .dfsj_post { margin-bottom: 8px !important; border-color: #b8cfe3 !important; overflow: visible !important; } html.colg-compact-on #postlist > div[id^="post_"] { position: relative !important; z-index: 1 !important; overflow: visible !important; } html.colg-compact-on #postlist > div[id^="post_"]:hover { z-index: 10000 !important; } html.colg-compact-on #postlist > div[id^="post_"] table.plhin { width: 100% !important; table-layout: fixed !important; overflow: visible !important; } html.colg-compact-on #postlist > div[id^="post_"] td.plc { padding: 6px 12px !important; position: relative !important; z-index: 1 !important; } html.colg-compact-on #postlist > div[id^="post_"] td.plc.img-ref-w-cotnainer { padding-top: 4px !important; padding-bottom: 4px !important; } html.colg-compact-on #postlist > div[id^="post_"] .plc .pi { min-height: 24px !important; padding: 0 0 4px !important; } html.colg-compact-on #postlist > div[id^="post_"] .pct { min-height: 0 !important; padding: 4px 0 2px !important; } html.colg-compact-on #postlist > div[id^="post_"] .pcb { margin: 0 !important; width: auto !important; } html.colg-compact-on #postlist > div[id^="post_"] .t_f, html.colg-compact-on #postlist > div[id^="post_"] .t_fsz { min-height: 0 !important; line-height: 1.65 !important; } html.colg-compact-on #postlist > div[id^="post_"] .t_f img[smilieid], html.colg-compact-on #postlist > div[id^="post_"] .t_f img[src*="/image/smiley/colgbb/"] { width: ${CONFIG.smileySize}px !important; height: auto !important; max-width: ${CONFIG.smileySize}px !important; vertical-align: text-bottom !important; } html.colg-compact-on #postlist > div[id^="post_"] .t_f img.zoom[file], html.colg-compact-on #postlist > div[id^="post_"] .t_f img[id^="aimg_"][file] { width: auto !important; height: auto !important; max-width: min(${CONFIG.replyImageMaxWidth}px, 100%) !important; max-height: ${CONFIG.replyImageMaxHeight}px !important; object-fit: contain !important; cursor: zoom-in !important; } html.colg-compact-on #postlist > div[id^="post_"] td.plc.plm, html.colg-compact-on #postlist > div[id^="post_"] .sign, html.colg-compact-on #postlist > div[id^="post_"] tr.ad { display: none !important; } html.colg-compact-on #postlist > div[id^="post_"] .po { min-height: 22px !important; padding: 2px 8px !important; } html.colg-compact-on #postlist > div[id^="post_"] .pob { line-height: 22px !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls { width: 175px !important; min-width: 175px !important; padding: 0 !important; border-right-color: #c8d9e8 !important; overflow: visible !important; position: relative !important; z-index: 20 !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls:hover { z-index: 10001 !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .pls.favatar, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar { width: 175px !important; min-height: 0 !important; overflow: visible !important; position: relative !important; z-index: 2 !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar:hover { z-index: 10002 !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .pi { padding: 8px 12px 3px !important; text-align: left !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .authi, html.colg-compact-on #postlist > div[id^="post_"] td.pls .authi a { display: block !important; max-width: 150px !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .avatar { margin: 8px auto 10px !important; width: 120px !important; text-align: center !important; position: relative !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .avatar img { width: 120px !important; max-width: 120px !important; height: auto !important; padding: 0 !important; background: none !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .mobile-type { position: absolute !important; top: auto !important; left: auto !important; width: 58px !important; height: 80px !important; margin: -5px 0 -85px 103px !important; z-index: 3 !important; transform: none !important; transform-origin: left top !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .mobile-type a { width: 46px !important; height: 80px !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .pi .vip-level-icon, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > .tns, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > p, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > .pbg2, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > dl.pil, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > ul.o, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > .tip, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > div[id^="g_up"][id$="_menu"], html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > div:not(.p_pop) .vip-level-name, html.colg-compact-on #postlist > div[id^="post_"] td.pls .favatar > div:not(.p_pop) .vip-medal { display: none !important; } html.colg-compact-on #postlist > div[id^="post_"] td.pls .p_pop { z-index: 10003 !important; } html.colg-compact-on.colg-mode-mini #postlist > div[id^="post_"] td.pls { display: none !important; } html.colg-compact-on.colg-mode-mini #postlist > div[id^="post_"] td.plc { width: auto !important; } html.colg-compact-on .${PAGE_SEPARATOR_CLASS} { margin: 12px 0 8px !important; padding: 6px 10px !important; color: #4f6f8c !important; background: #eef6ff !important; border: 1px solid #bfd4e8 !important; font-size: 13px !important; text-align: center !important; } html.colg-compact-on #${STATUS_ID} { position: fixed !important; right: 24px !important; bottom: 76px !important; z-index: 2147483647 !important; max-width: 260px !important; padding: 8px 12px !important; color: #fff !important; background: rgba(32, 48, 68, 0.88) !important; border-radius: 4px !important; font-size: 13px !important; line-height: 1.5 !important; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18) !important; } html.colg-compact-on #${STATUS_ID}.is-error { background: rgba(180, 62, 48, 0.92) !important; } `; document.head.appendChild(style); } function markExistingPosts() { document.querySelectorAll(POST_SELECTOR).forEach((post) => { if (post.id) { state.loadedPostIds.add(post.id); } }); } function applyCompactDom(root) { root.querySelectorAll(POST_SELECTOR).forEach((post) => { post.classList.add('colg-compact-post'); post.querySelectorAll('td.plc.plm').forEach((cell) => { const row = cell.closest('tr'); if (row) { row.classList.add('colg-hidden-sign-row'); } }); }); } function observeNewPosts(postList) { const observer = new MutationObserver((mutations) => { let shouldApply = false; for (const mutation of mutations) { if (mutation.addedNodes.length > 0) { shouldApply = true; break; } } if (shouldApply) { applyCompactDom(document); } }); observer.observe(postList, { childList: true, subtree: true, }); } function scheduleScrollCheck() { if (state.checkScheduled) { return; } state.checkScheduled = true; requestAnimationFrame(() => { state.checkScheduled = false; checkAutoPager(); }); } function checkAutoPager() { if (!CONFIG.autoPager || state.loading || state.done || !state.nextUrl) { return; } const scrollTop = window.scrollY || document.documentElement.scrollTop || 0; const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0; const pageHeight = Math.max( document.documentElement.scrollHeight, document.body ? document.body.scrollHeight : 0 ); if (scrollTop + viewportHeight + CONFIG.triggerDistance >= pageHeight) { loadNextPage(); } } async function loadNextPage() { if (state.loading || state.done || !state.nextUrl) { return; } const url = state.nextUrl; state.loading = true; setStatus('正在加载下一页...'); try { await delay(CONFIG.requestDelayMs); const response = await fetch(url, { credentials: 'include', }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const html = await response.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const nextPageInfo = getPageInfo(doc, url); const addedCount = appendPostsFromDocument(doc, nextPageInfo); state.nextUrl = findNextUrl(doc, url); state.done = !state.nextUrl; if (addedCount === 0) { state.done = true; setStatus('没有找到新的楼层,已停止自动翻页。', 'error'); return; } applyCompactDom(document); setStatus(state.done ? '已加载最后一页。' : `已加载第 ${nextPageInfo.current || '?'} 页。`); if (!state.done) { window.setTimeout(scheduleScrollCheck, 120); } } catch (error) { console.error('[Colg compact autopager] load failed:', error); setStatus('下一页加载失败,可以刷新后重试。', 'error'); } finally { state.loading = false; } } function appendPostsFromDocument(doc, pageInfo) { const postList = document.querySelector('#postlist'); if (!postList) { return 0; } const posts = Array.from(doc.querySelectorAll(POST_SELECTOR)); const newPosts = posts.filter((post) => post.id && !state.loadedPostIds.has(post.id)); if (newPosts.length === 0) { return 0; } const fragment = document.createDocumentFragment(); fragment.appendChild(createPageSeparator(pageInfo)); newPosts.forEach((post) => { state.loadedPostIds.add(post.id); fragment.appendChild(document.importNode(post, true)); }); postList.appendChild(fragment); return newPosts.length; } function createPageSeparator(pageInfo) { const separator = document.createElement('div'); separator.className = PAGE_SEPARATOR_CLASS; separator.textContent = pageInfo.current ? `第 ${pageInfo.current} 页` : '已加载下一页'; return separator; } function findNextUrl(doc, baseUrl) { const selectors = [ '.pgbtn a[href]', '.pg a.nxt[href]', 'a.nxt[href]', ]; for (const selector of selectors) { const link = doc.querySelector(selector); if (!link) { continue; } const href = link.getAttribute('href'); if (!href || href === 'javascript:;') { continue; } const absoluteUrl = new URL(href.replace(/&/g, '&'), baseUrl).href; if (absoluteUrl !== new URL(baseUrl, location.href).href) { return absoluteUrl; } } return null; } function getPageInfo(doc, pageUrl) { const strong = doc.querySelector('.pg strong'); const totalText = doc.querySelector('.pg label span')?.textContent || ''; const currentFromPager = parseInt(strong?.textContent || '', 10); const totalMatch = totalText.match(/\/\s*(\d+)\s*页/); const currentFromUrl = pageUrl.match(/thread-\d+-(\d+)-\d+\.html/)?.[1]; return { current: Number.isFinite(currentFromPager) ? currentFromPager : parseInt(currentFromUrl || '', 10), total: totalMatch ? parseInt(totalMatch[1], 10) : null, }; } function setStatus(text, type) { let status = document.getElementById(STATUS_ID); if (!status) { status = document.createElement('div'); status.id = STATUS_ID; document.body.appendChild(status); } status.textContent = text; status.classList.toggle('is-error', type === 'error'); status.style.display = 'block'; window.clearTimeout(state.statusTimer); if (text !== '正在加载下一页...') { state.statusTimer = window.setTimeout(() => { status.style.display = 'none'; }, CONFIG.statusHideMs); } } function delay(ms) { return new Promise((resolve) => window.setTimeout(resolve, ms)); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();