// ==UserScript==
// @name Side Scroll Wheel Pagination
// @namespace Hal74
// @version 0.1
// @description 鼠标侧滚轮翻页,支持键盘热键翻页
// @author Hal74
// @match *://*/*
// @license MIT
// ==/UserScript==
// 热键翻页开关
const needHotKey = false;
// 编辑下面的数组来自定义规则
const specialXpaths = [
{
//匹配的url
urls: ["taobao.com"],
//上一页节点的xpath
prev: '//button[contains(@aria-label,"上一页")]',
//下一页节点的xpath
next: '//button[contains(@aria-label,"下一页")]',
},
// elementUI
{
urls: ["localhost"],
prev: '//button[@class="btn-prev"]',
next: '//button[@class="btn-next"]',
},
];
const generalXpaths = [
["//a[(text()='", "')]"],
["//a[@class='", "']"],
["//button[(text()='", "')]"],
["//button[@class='", "']"],
["//input[@type='button' and @value='", "']"],
];
const Strs = {
next: [
"下一页",
"下页",
"下一页 »",
"下一页 >",
"下一节",
"下一章",
"下一篇",
"后一章",
"后一篇",
"后页>",
"»",
"next",
"next page",
"old",
"older",
"earlier",
"下頁",
"下一頁",
"后一页",
"后一頁",
"翻下页",
"翻下頁",
"后页",
"后頁",
"下翻",
"下一个",
"下一张",
"下一幅",
],
prev: [
"上一页",
"上页",
"« 上一页",
"< 上一页",
"上一节",
"上一章",
"上一篇",
"前一章",
"前一篇",
"<前页",
"«",
"previous",
"prev",
"previous page",
"new",
"newer",
"later",
"上頁",
"上一頁",
"前一页",
"前一頁",
"翻上页",
"翻上頁",
"前页",
"前頁",
"上翻",
"上一个",
"上一张",
"上一幅",
]
}
const keys = {
prev: [
'ArrowLeft',
'a',
],
next: [
'ArrowRight',
'd'
]
}
function checkTextArea(node) {
var name = node.localName.toLowerCase();
if (name === "textarea" || name === "input" || name === "select") {
return true;
}
if (name === "div" && (node.id.toLowerCase().includes("textarea") || node.contentEditable !== 'inherit')) {
return true;
}
return false;
}
function hasHorizontalScrollbar(node) {
while (node) {
if ((node.scrollWidth - node.clientWidth) > 20) {
return true;
}
node = node.parentNode;
}
return false;
}
function xpath(query) {
return unsafeWindow.document.evaluate(
query,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null
);
}
function getNode(lnstr) {
var node = getNodeByGeneralXpath(lnstr);
if (!node) node = getNodeBySpecialXpath(lnstr);
return node;
}
function getNodeByGeneralXpath(lnstr) {
var strs;
strs = Strs[lnstr];
var x = generalXpaths;
for (var i in x) {
for (var j in strs) {
var query = x[i][0] + strs[j] + x[i][1];
var nodes = xpath(query);
if (nodes.snapshotLength > 0) return nodes.snapshotItem(0);
}
}
return null;
}
function getNodeBySpecialXpath(lnstr) {
var s = specialXpaths;
for (var i in s) {
if (checkXpathUrl(s[i].urls)) {
return xpath(s[i][lnstr]).snapshotItem(0);
}
}
return null;
}
function checkXpathUrl(urls) {
for (var i in urls) if (location.href.indexOf(urls[i]) >= 0) return true;
return false;
}
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = new Date().getTime();
if (now - lastCall < delay) {
return;
}
lastCall = now;
return func(...args);
};
}
function findParentKey(obj, value) {
for (let key in obj) {
if (obj[key].includes(value)) {
return key;
}
}
return null;
}
function checkKey(e) {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
if (checkTextArea(e.target)) return;
const key = findParentKey(keys, e.key)
const link = getNode(key);
if (key && !!link) {
link.click()
}
}
function checkWheel(e) {
if (hasHorizontalScrollbar(e.target)) return
if (event.deltaX !== 0) {
turnPage(event.deltaX);
}
}
function turnPage(direction) {
const keyText = direction > 0 ? 'prev' : 'next';
const link = getNode(keyText);
if (!!link) {
link.click()
}
}
if (top.location != self.location) return;
unsafeWindow.document.addEventListener('wheel', throttle((event) => {
checkWheel(event)
}, 1000));
if (needHotKey) {
unsafeWindow.document.addEventListener("keydown", checkKey, false);
}