// ==UserScript== // @name 笔趣阁外观优化 // @namespace https://gitee.com/linhq1999/OhMyScript // @version 2.11 // @description 专注阅读 // @author LinHQ // @match http*://www.shuquge.com/*.html // @exclude http*://www.shuquge.com/*index.html // @match http*://www.sywx8.com/*.html // @match http*://www.biqugetv.com/*.html // @match http*://www.bqxs520.com/*.html // @grant GM_addStyle // @grant GM_xmlhttpRequest // @inject-into auto // @license MIT // @downloadURL none // ==/UserScript== 'use strict'; /** 配置示例 * "sites": [ * { * "desc": "shuquge", 网站链接关键字 * "main": "div.reader", 主要部分选择器 * "title": ".reader h1", 标题选择器 * "txt": "#content", 文字部分选择器 * "toc": "dd a", 目录链接选择器 * "tocJump": 12, 跳过前面多少章 * "filter": ["div.header", "div.nav", "div.link"], 带有此选择器的元素将被删除 * "txtfilter": ["shuqu"] 带有此关键字的行将被删除 * } * ] */ (() => { // 缺省值,一般不用修改 const lineHeight = 1.3, defaultFont = "楷体"; let configs = { "sites": [ { "desc": "shuquge", "main": "div.reader", "title": ".reader h1", "txt": "#content", "toc": "dd a", "tocJump": 12, "filter": [ "div.header", "div.nav", "div.link", "img", "#coupletleft", "#coupletright", "#HMRichBox" ], "txtfilter": ["shuqu"], "funcFilter": () => { var _a, _b; return (_b = (_a = document.querySelector("#content")) === null || _a === void 0 ? void 0 : _a.previousSibling) === null || _b === void 0 ? void 0 : _b.remove(); } }, { "desc": "sywx", "main": "div#container", "title": "div>h1", "toc": "li a", "tocJump": 0, "txt": "div#BookText", "filter": ["div.top", ".link.xb", "#footer"], "txtfilter": ["最快更新", "松语"] }, { "desc": "bqxs", "main": ".box_con", "title": "div.content_read h1", "toc": "#list dd a", "tocJump": 9, "txt": "#content", "filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set", ".bookname~.box_con"], "txtfilter": [] }, { "desc": "biqugetv", "main": ".box_con", "title": "div.content_read h1", "toc": "#list dd a", "tocJump": 0, "txt": "#content", "filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set"], "txtfilter": [] } ], "states": { "fontSize": 16, "lineHeight": 16 * lineHeight, "toc": false }, "style": ` body { background-color: #EAEAEF !important; } .bqg.inject.win { width: 55vw !important; min-width: 600px; border: 2px double gray !important; border-radius: 8px; } .bqg.inject.txt { font-family: ${defaultFont}!important; background-color: #EAEAEF !important; padding: 0.5em 1em !important; margin: 0.5em auto !important; width: auto !important; } .bqg.inject.title { color: black; background-color: #EAEAEF; font-family: ${defaultFont}!important; cursor: pointer !important; } .bqg.inject.title:hover { color: #0258d8 !important; } .hq.inject.toc { font-family: Arial,微软雅黑,文泉驿微米黑; width: 275px; position: fixed; top: 30px; padding: 5px; display: flex; flex-flow: column; transition: left 0.5s cubic-bezier(0.35, 1.06, 0.83, 0.99); background: rgb(246 246 246 / 60%); border-radius: 8px; } .hq.inject ul { max-height: 280px; width: 100%; /*offsetTop 计算需要*/ position:relative; overflow: auto; } .hq.inject ul li { cursor: pointer; margin: 2px; width: 95%; padding: 1px 4px; font-size: 12px; border-radius: 4px; } .hq.inject ul li:hover { background: #0258d8; color: #f6f6f6; } .hq.inject.toc>h3 { font-size: 1.1rem; font-weight: bold; border-radius: 2px; align-self: center; cursor: pointer; margin: 4px 0 8px 0; } .hq.inject.toc>h3:hover { color: #ffa631 !important; } ` }; // 查询已经保存的字体信息 let savedStates = localStorage.getItem("bqg_cfg"); // 检查是否存在已有设置且和当前版本相符 let states; if (savedStates === null) { states = configs.states; console.warn("当前状态已保存"); } else { let cfg = JSON.parse(savedStates); let defaultStates = Object.keys(configs.states); let cfg_ = Object.keys(cfg); let useSaved = true; // 检查键是否匹配 if (defaultStates.length == cfg_.length) { for (let key of Object.keys(cfg)) { if (!defaultStates.includes(key)) { useSaved = false; break; } } } else { useSaved = false; } if (useSaved) { states = cfg; } else { states = configs.states; console.warn("检测到版本变化,状态已重置"); } } // 检测当前的网址,应用对应的设置 let tmp = configs.sites.filter(site => document.URL.includes(site.desc)); if (tmp.length == 0) { console.warn("没有匹配的设置,脚本已终止!"); return; } let cfg = tmp[0]; // 完成样式注入 GM_addStyle(configs.style); let saveStates = () => { localStorage.setItem("bqg_cfg", JSON.stringify(states)); }; // 上一章 let prevChapter = () => { var _a; let prevs = document.querySelectorAll("a"); for (const prev of prevs) { if ((_a = prev.textContent) === null || _a === void 0 ? void 0 : _a.includes("上一")) { prev.click(); break; } } }; // 下一章 let nextChapter = () => { var _a; let nexts = document.querySelectorAll("a"); for (const next of nexts) { if ((_a = next.textContent) === null || _a === void 0 ? void 0 : _a.includes("下一")) { next.click(); break; } } }; // 目录开关 let toggleToc = () => { let toc = document.querySelector(".hq.inject.toc"); if (parseInt(toc.style.left) < 0) { toc.style.left = "8px"; states.toc = true; } else { toc.style.left = "-300px"; states.toc = false; } // 每一次触发目录操作都保存一次状态 saveStates(); }; // 对可变部分产生影响 let doInject = function () { var _a, _b; // 执行元素过滤 cfg.filter.forEach(filter => { var _a; return (_a = document.querySelectorAll(filter)) === null || _a === void 0 ? void 0 : _a.forEach(ele => ele.remove()); }); // 执行自定义过滤 if (cfg.funcFilter) { cfg.funcFilter(); } // 应用已经保存的状态 let textWin = document.querySelector(cfg.txt); textWin.setAttribute("style", `font-size:${states.fontSize}px;line-height:${states.lineHeight}px`); textWin.classList.add("bqg", "inject", "txt"); // 执行文字过滤 if (cfg.txtfilter !== undefined) { textWin.innerText = (_b = (_a = textWin.innerText) === null || _a === void 0 ? void 0 : _a.split("\n\n")) === null || _b === void 0 ? void 0 : _b.filter(line => { for (const key of cfg.txtfilter) { if (line.includes(key)) { return false; } } return true; }).join("\n\n"); } let mainWin = document.querySelector(cfg.main); mainWin.classList.add("bqg", "inject", "win"); let title = document.querySelector(cfg.title); title.title = "点击显示目录"; title.classList.add("bqg", "inject", "title"); title.onclick = (ev) => { toggleToc(); // 避免跳到上一章 // 比下面的更为具体,所以有效。 ev.stopPropagation(); }; // 阻止双击事件被捕获(双击会回到顶部) document.body.ondblclick = (ev) => ev.stopImmediatePropagation(); document.body.onclick = (ev) => { let root = document.documentElement; let winHeight = window.innerHeight; // 下半屏单击下滚,反之上滚 if (ev.clientY > root.clientHeight / 2) { if (root.scrollTop + winHeight >= root.scrollHeight) { nextChapter(); } window.scrollBy({ top: (window.innerHeight - lineHeight) * 1 }); } else { if (root.scrollTop === 0) { prevChapter(); } window.scrollBy({ top: (window.innerHeight - lineHeight) * -1 }); } }; document.body.onkeydown = (ev) => { switch (ev.key) { case "-": states.fontSize -= 2; textWin.style.fontSize = `${states.fontSize}px`; states.lineHeight = states.fontSize * lineHeight; textWin.style.lineHeight = `${states.lineHeight}px`; saveStates(); break; case "=": states.fontSize += 2; textWin.style.fontSize = `${states.fontSize}px`; states.lineHeight = states.fontSize * lineHeight; textWin.style.lineHeight = `${states.lineHeight}px`; saveStates(); break; case "j": window.scrollBy({ top: window.innerHeight - states.lineHeight }); break; case "k": window.scrollBy({ top: -1 * (window.innerHeight - states.lineHeight) }); break; case "h": prevChapter(); break; case "l": nextChapter(); break; case "t": toggleToc(); break; default: break; } }; }; // 先调用一次,后面是有变化时才会触发,避免有时无法起作用 doInject(); // 强力覆盖 new MutationObserver((_, ob) => { doInject(); }).observe(document.body, { childList: true }); // 添加目录 let toc = document.createElement("div"); toc.className = "hq inject toc"; toc.onclick = ev => ev.stopPropagation(); // 已保存状态读取 toc.style.left = (states.toc) ? "8px" : "-300px"; document.body.append(toc); // 目录状态指示灯 let pointer = document.createElement("h3"); let pointerColors = { "loaded": "#afdd22", "loading": "#ffa631", "unload": "#ed5736" }; pointer.title = "点击以重新加载目录"; pointer.innerHTML = "目录"; pointer.style.color = pointerColors.unload; toc.append(pointer); // 目录列表 let ul = document.createElement("ul"); toc.append(ul); // fetchTOC 获取目录信息并重新渲染 let fetchTOC = function (currentBookLink, pointer) { // 修改指示灯状态 pointer.style.color = pointerColors.loading; GM_xmlhttpRequest({ url: currentBookLink, // 直接返回 dom responseType: "document", onload: (resp) => { var _a, _b; let doc = resp.response; let tocs = doc.querySelectorAll(cfg.toc); let data = []; // 序列化存储准备 for (let link of tocs) { data.push({ "title": (_a = link.textContent) !== null && _a !== void 0 ? _a : "", "href": (_b = link.href) !== null && _b !== void 0 ? _b : "" }); } if (cfg.tocJump) data = data.slice(cfg.tocJump + 1); // 缓存目录信息 sessionStorage.setItem(currentBookLink, JSON.stringify(data)); renderToc(data, ul); pointer.style.color = pointerColors.loaded; }, onerror: (_) => pointer.style.color = pointerColors.unload }); }; let renderToc = function (toc, ul) { // 清空旧内容 ul.innerHTML = ""; let current = null; // 进度计数器 let counter = 1; for (let lnk of toc) { let li = document.createElement("li"); li.textContent = lnk.title; if (current == null && document.URL == lnk.href) { li.innerHTML = `${lnk.title}${(counter / toc.length * 100).toFixed(1)}%`; current = li; } li.onclick = (ev) => { document.location.href = lnk.href; ev.stopPropagation(); }; ul.append(li); counter++; } // 滚动到当前位置,并高亮 current === null || current === void 0 ? void 0 : current.setAttribute("style", "display:flex;font-weight:bold;background: #0258d8;color: #f6f6f6;"); ul.scrollTo({ top: (current === null || current === void 0 ? void 0 : current.offsetTop) - 130 }); }; let source = document.URL.split("/"); source.pop(); // 最后加斜杠保险 let currentBook = source.join("/") + "/"; let currentBookToc = sessionStorage.getItem(currentBook); if (currentBookToc === null) { fetchTOC(currentBook, pointer); } else { pointer.style.color = pointerColors.loaded; renderToc(JSON.parse(currentBookToc), ul); } // 单击指示灯刷新目录缓存 pointer.onclick = () => fetchTOC(currentBook, pointer); })();