infinte scroll
// ==UserScript==
// @name xmegadrive infinite scroll
// @namespace http://tampermonkey.net/
// @version 1.8
// @description infinte scroll
// @author You
// @match https://www.xmegadrive.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ---------- SETTINGS ----------
const SHOW_STATUS = false; // true = green status label (bottom-left)
const SHOW_PAGE = true; // true = cyan "X / Y" label (bottom-right)
const SHOW_TOP = true; // true = "Top" button (bottom-right)
// ---------- BLOCK 0: labels ----------
const labels = document.createElement('div');
labels.style.cssText = 'position:fixed;right:8px;bottom:8px;z-index:99999;display:flex;flex-direction:column;gap:4px;align-items:flex-end;';
document.body.appendChild(labels);
const mkTag = (color) => {
const t = document.createElement('div');
t.style.cssText = 'background:#000;font:15px monospace;padding:4px 8px;opacity:.7;color:' + color;
return t;
};
const tag = mkTag('#0f0');
const pageTag = mkTag('#0ff');
if (SHOW_STATUS) labels.appendChild(tag);
const say = (t) => (tag.textContent = 'IS: ' + t);
say('script running');
// ---------- BLOCK 1: wait for the list, then start ----------
let tries = 0;
const wait = setInterval(() => {
tries++;
const first = document.querySelector('li.next a[data-action="ajax"]');
if (first && document.getElementById(first.dataset.blockId + '_items')) {
clearInterval(wait);
try { init(first); } catch (e) { say('error: ' + e.message); }
} else if (tries >= 20) {
clearInterval(wait);
say(first ? 'no list box for ' + first.dataset.blockId : 'no Next link on this page');
}
}, 300);
function init(first) {
// ---------- BLOCK 2: list + page numbers ----------
const blockId = first.dataset.blockId;
const getBox = () => {
const c = document.getElementById(blockId + '_items');
return c ? (c.querySelector('form') || c) : null;
};
const pageRe = /(?:^|;)from[^:;]*:(\d+)/;
const num = (a) => +((pageRe.exec(a?.dataset.parameters || '') || [])[1]);
let page = num(first);
const lastPage = num(document.querySelector('li.last a[data-block-id="' + blockId + '"]')) || Infinity;
const template = first.dataset.parameters;
const seen = new Set([...getBox().querySelectorAll('.item > a')].map((a) => a.href));
let loading = false, done = false;
// mark the items already on the page with their page number
getBox().querySelectorAll('.item').forEach((el) => (el.dataset.isPage = page - 1));
if (SHOW_PAGE) labels.appendChild(pageTag);
if (SHOW_TOP) {
const topBtn = mkTag('#fff');
topBtn.textContent = '↑';
topBtn.style.cssText += ';cursor:pointer;font-size:40px;width:50px;height:50px;padding:0;display:flex;align-items:center;justify-content:center;';
topBtn.onclick = () => scrollTo({ top: 0, behavior: 'smooth' });
labels.prepend(topBtn);
}
say('ready, next page ' + page + '/' + lastPage);
// ---------- BLOCK 3: load the next page ----------
async function loadMore() {
if (loading || done) return;
loading = true;
say('loading page ' + page + '...');
try {
const qs = ['mode=async', 'function=get_block', 'block_id=' + blockId];
for (const pair of template.split(';')) {
const i = pair.indexOf(':');
if (i < 1) continue;
const k = pair.slice(0, i);
const v = /^from/.test(k) ? page : pair.slice(i + 1);
k.split('+').forEach((name) => qs.push(name + '=' + encodeURIComponent(v)));
}
qs.push('_=' + Date.now());
const res = await fetch(location.pathname + '?' + qs.join('&'), {
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
});
const doc = new DOMParser().parseFromString(await res.text(), 'text/html');
const items = doc.querySelectorAll('.item');
const box = getBox();
if (!box) { say('list not found'); loading = false; return; }
const bottom = box.querySelector('.bottom');
let added = 0;
items.forEach((el) => {
const href = el.querySelector('a')?.href;
if (!href || seen.has(href)) return;
seen.add(href);
const img = el.querySelector('img[data-original]');
if (img) img.src = img.dataset.original;
const node = document.importNode(el, true);
node.dataset.isPage = page;
box.insertBefore(node, bottom);
added++;
});
say('p' + page + ' +' + added + ' | in list: ' + box.querySelectorAll('.item').length);
page++;
if (!items.length || !added || page > lastPage) done = true;
} catch (e) {
say('error: ' + e.message);
}
loading = false;
}
// ---------- BLOCK 4: scroll trigger ----------
const check = () => {
const el = document.documentElement;
if (el.scrollHeight - (scrollY + innerHeight) < 1200) loadMore();
};
window.addEventListener('scroll', check, { passive: true });
setInterval(check, 500);
// ---------- BLOCK 5: "Page X / Y" label ----------
function showPage() {
if (!SHOW_PAGE) return;
const box = getBox();
if (!box) return;
const items = box.querySelectorAll('.item');
let cur = items[items.length - 1];
for (const el of items) {
if (el.getBoundingClientRect().bottom > innerHeight / 2) { cur = el; break; }
}
pageTag.textContent = 'Page ' + (cur?.dataset.isPage || '?') + ' / ' + (lastPage === Infinity ? '?' : lastPage);
}
showPage();
setInterval(showPage, 300);
}
})();