您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
添加打开/隐藏笔记面板按钮、笔记列表自动匹配当前章节进度、双击笔记跳转下一个笔记
// ==UserScript== // @name 微信读书移动端笔记列表增强 // @namespace http://tampermonkey.net/ // @version 0.7.2 // @description 添加打开/隐藏笔记面板按钮、笔记列表自动匹配当前章节进度、双击笔记跳转下一个笔记 // @author XQH // @match https://weread.qq.com/web/reader/* // @icon https://weread.qq.com/favicon.ico // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // variable // 上一个点击的笔记列表项 let lastNote = null; let doubleClickThreshold = 700; const DEBUG = false; console.log("[微信读书移动端笔记列表增强] js加载成功"); // 注入一个pre显示日志 if (DEBUG) { let pre = document.createElement("pre"); pre.style.position = "fixed"; pre.style.top = "0"; pre.style.left = "0"; pre.style.zIndex = "10000"; pre.style.background = "rgba(255,255,255,0.9)"; pre.style.padding = "10px"; pre.style.border = "1px solid #ccc"; pre.style.maxHeight = "200px"; pre.style.width = "200px"; pre.style.color = "#000"; pre.style.height = "100px"; pre.style.overflow = "auto"; document.body.appendChild(pre); window.log = function (msg) { pre.innerText = msg + "\n" + pre.innerText; }; window.console.log = function (msg) { pre.innerText = msg + "\n" + pre.innerText; }; // 拦截报错log window.console.error = function (msg) { pre.innerText = msg + "\n" + pre.innerText; }; } console.log("脚本启动v5"); initStyle(); setTimeout(async() => { await waitPageLoaded(); injectNoteButton(); // 为页面高亮文本添加点击事件监听 observeDOMChanges(); }, 700); // 主循环,检查是否跨页笔记,跨页笔记体现为可视范围内存在上一页按钮, // 且当前页面未出现文本与 lastNote 相同的高亮文本 // lastNote (最后操作的笔记项,即当前笔记项) 赋值来源 // 1. 点击笔记列表项 // 2. 双击高亮文本 // setInterval(() => { // // checkNeedBackPage // if (lastNote) { // lastNote // } // }, 1000); console.log("脚本注入组件完毕"); // side effect method function initStyle() { addStyle(` .wr_reader_note_panel_footer_button, .wr_btn.wr_btn_Big.rbb_addShelf, .readerFooter_button.blue, .reader_toolbar_color_container, .toolbarItem.underlineHandWrite, .toolbarItem.underlineStraight, .toolbarItem.review, .toolbarItem.query, .wr_reader_note_panel_header_wrapper, .toast.toast_Show { display: none !important; } .readerNotePanel { overflow-y: auto; position: fixed; bottom: 0; left: 25%; width: 80%; margin-left: 0; display: none; z-index: 10000; background: white; } .readerBottomBar { z-index: 9999; } `); } // 在底部栏注入笔记按钮 function injectNoteButton() { let note_btn = ` <button title="笔记" class="rbb_item wr_note"> <span class="icon"></span> <span class="txt">笔记</span> </button>`; try { let note_btn_container = document.querySelector( ".readerBottomBar_content" ); if (note_btn_container) { if (note_btn_container.querySelector(".rbb_item.wr_note")) { return; } note_btn_container.insertAdjacentHTML("afterbegin", note_btn); document .querySelector(".rbb_item.wr_note") .addEventListener("click", function (event) { event.stopPropagation(); // 防止点击事件冒泡到外部 let reader_note_panel = document.querySelector(".readerNotePanel"); if ( reader_note_panel.style.display === "none" || reader_note_panel.style.display === "" ) { reader_note_panel.style.display = "block"; scrollNotePanelToProgress(); setTimeout(() => { document.addEventListener("click", outsideClickListener); }, 0); // 为笔记面板中的笔记项添加点击事件监听,点击跳转后关闭笔记面板 let noteItems = document.querySelectorAll( ".wr_reader_note_panel_item_cell_wrapper.clickable" ); for (let i = 0; i < noteItems.length; i++) { noteItems[i].addEventListener("click", function () { reader_note_panel.style.display = "none"; lastNote = noteItems[i]; checkNeedSwitchPage(); }); } } else { reader_note_panel.style.display = "none"; document.removeEventListener("click", outsideClickListener); } }); } } catch (error) { console.log("注入笔记按钮失败", error); } } function outsideClickListener(event) { let reader_note_panel = document.querySelector(".readerNotePanel"); let btn = document.querySelector(".rbb_item.wr_note"); if ( !reader_note_panel.contains(event.target) && !btn.contains(event.target) ) { reader_note_panel.style.display = "none"; document.removeEventListener("click", outsideClickListener); } } function scrollNotePanelToProgress() { let inViewUnderlines = getInViewUnderline(); if (inViewUnderlines.length > 0) { let jumpText = inViewUnderlines[0]; scrollNotePanelToText(jumpText); } else if (lastNote) { lastNote.scrollIntoView({ block: "center" }); } else { scrollNotePanelToText(getChapterTitle()); } } function scrollNotePanelToText(text) { // 在笔记面板中查找匹配的章节 let noteChapters = document.querySelectorAll( ".wr_reader_note_panel_chapter_title" ); for (let i = 0; i < noteChapters.length; i++) { if (noteChapters[i].innerText.trim() === text) { console.log("Found chapter in notes: " + text); // 滚动到笔记面板中的该章节 noteChapters[i].scrollIntoView({ block: "center" }); // 如果之前有选中的笔记,滚动到该笔记 break; } } } function getInViewUnderline(targetText) { let underlines = document.getElementsByClassName("wr_underline_wrapper"); let viewportHeight = window.innerHeight || document.documentElement.clientHeight; let viewportWidth = window.innerWidth || document.documentElement.clientWidth; let visibleUnderlines = []; for (let i = 0; i < underlines.length; i++) { let rect = underlines[i].getBoundingClientRect(); if ( rect.bottom >= 0 && rect.top <= viewportHeight && rect.right >= 0 && rect.left <= viewportWidth ) { visibleUnderlines.push(underlines[i]); } } console.log("visibleUnderlines", visibleUnderlines); let textArr = []; for (let i = 0; i < visibleUnderlines.length; i++) { visibleUnderlines[i].click(); setTimeout(() => { let copyButton = document.querySelector(".toolbarItem.wr_copy"); if (copyButton) { window.isCustomCopy = true; function onCopy(e) { if (window.isCustomCopy) { let selectionText = e.target.value; e.preventDefault(); e.stopPropagation(); window.isCustomCopy = false; document.removeEventListener("copy", onCopy, true); textArr.push(selectionText); if (targetText && selectionText === targetText) { return textArr; } } } document.addEventListener("copy", onCopy, true); copyButton.click(); // 拦截可能出现的复制提示框 let toast = document.querySelector(".toast.toast_Show"); if (toast) { toast.style.display = "none"; } } }, 100); } return textArr; } async function lastPageInView() { let readerHeaderButton = await whenElementExist(".readerHeaderButton"); if (readerHeaderButton && readerHeaderButton.innerText === "上一页") { // 判断是否在可见范围 let rect = readerHeaderButton.getBoundingClientRect(); let viewportHeight = window.innerHeight || document.documentElement.clientHeight; if (rect.top >= 0 && rect.bottom <= viewportHeight) { return true; } } return false; } async function checkNeedSwitchPage() { // 如果为上一页 await waitPageLoaded(); console.log("[检查是否需要切换页面]"); setTimeout(async () => { let inViewUnderlines = getInViewUnderline(); // 如果当前页面没有找到与上一个笔记相同的高亮文本,则跳转到上一页 if (lastNote && !inViewUnderlines.includes(lastNote.innerText)) { console.log("当前页面没有与上一个笔记相同的高亮文本, 细分判断"); // 如果存在上一页 let isLastPageInView = await lastPageInView(); if (isLastPageInView) { console.log("当前页面存在上一页按钮, 点击上一页按钮"); elClick(document.querySelector(".readerHeaderButton")); // 等待页面加载完成 await waitPageLoaded(); setTimeout(() => { if (lastNote) { elClick(lastNote); checkNeedSwitchPage(); } }, 500); } else { console.log("当前页面不存在上一页按钮, 尝试切换章节实现定位"); // 再检查一遍高亮文本 let checkInViewUnderlines = getInViewUnderline(); if (!checkInViewUnderlines.includes(lastNote.innerText)) { console.log( "当前页面没有与上一个笔记相同的高亮文本, 尝试切换章节实现定位" ); let contentList = getContentList(); let chapterTitle = getChapterTitle(); contentList.forEach((content, index) => { // chapterTitle include content if (chapterTitle.includes(content)) { // 边界判断加载下一章节再跳转笔记 let idx = index + 1; if (idx >= contentList.length) { idx = 0; } // 加载 let nextChapter = document.querySelectorAll( ".readerCatalog_list_item" )[idx]; elClick(nextChapter); setTimeout(() => { console.log("已加载下一章节, 尝试回到目标笔记进度"); elClick(lastNote); // checkNeedSwitchPage(); }, 500); } }); } } } else { console.log("当前页面有与上一个笔记相同的高亮文本, 释放 lastNote"); lastNote = null; } }, 200); } function getContentList() { let contentList = document.querySelectorAll(".readerCatalog_list_item"); let contentArr = []; for (let i = 0; i < contentList.length; i++) { let content = contentList[i].innerText; contentArr.push(content); } return contentArr; } // 获取当前章节标题 function getChapterTitle() { let chapter_title = document.querySelector(".readerTopBar_title_chapter"); if (chapter_title) { return chapter_title.innerText.trim(); } return ""; } async function findAndClickNextNoteItem(jumpText) { console.log("[跳转下一个划线笔记]", "当前笔记文本 " + jumpText); let noteItems = document.querySelectorAll( ".wr_reader_note_panel_item_cell_wrapper.clickable" ); let foundIndex = -1; for (let j = 0; j < noteItems.length; j++) { let noteText = noteItems[j].innerText.replace(/\s/g, ""); console.log("[笔记文本]", noteText); // 移除空格和换行符 noteText = noteText.replace(/\s/g, ""); if (noteText === jumpText) { foundIndex = j; break; } } if (foundIndex >= 0) { let nextIndex = foundIndex + 1; if (nextIndex >= noteItems.length) { nextIndex = 0; } elClick(noteItems[nextIndex]); lastNote = noteItems[nextIndex]; console.log("[下一个笔记]", lastNote.innerText); checkNeedSwitchPage(); } else { console.log("查找下一个划线笔记文本失败: " + noteText); } } // 通过高亮文本的点击后的工具栏来获取高亮文本 async function getHighlightText(element) { element.click(); let copyBtn = await whenElementExist(".toolbarItem.wr_copy"); return new Promise((resolve) => { function onCopy(e) { if (window.isCustomCopy) { let copyText = e.target.value; e.preventDefault(); e.stopPropagation(); window.isCustomCopy = false; document.removeEventListener("copy", onCopy, true); resolve(copyText); } } document.addEventListener("copy", onCopy, true); window.isCustomCopy = true; copyBtn.click(); }); } function elClick(el) { el.dispatchEvent( new MouseEvent("click", { clientX: 1, clientY: 1, }) ); } // 为高亮文本添加点击事件监听 function addUnderlineClickListeners(dbClickThreshold) { let underlines = document.getElementsByClassName("wr_underline_wrapper"); for (let i = 0; i < underlines.length; i++) { if (underlines[i].getAttribute("data-listener-added")) { continue; } underlines[i].setAttribute("data-listener-added", "true"); let clickCount = 0; let lastClickTime = 0; underlines[i].addEventListener("click", function (e) { const currentTime = new Date().getTime(); clickCount++; if (clickCount === 1) { setTimeout(async function () { if (clickCount === 1) { // 单击:可添加显示工具栏的逻辑 } else if (clickCount === 2) { const copyText = await getHighlightText(underlines[i]); findAndClickNextNoteItem(copyText); } clickCount = 0; }, dbClickThreshold); } lastClickTime = currentTime; }); } } function observeDOMChanges() { let targetNode = document.querySelector(".readerContent"); if (!targetNode) { console.log("Reader content not found for observing DOM changes."); return; } let config = { childList: true, subtree: true }; let callback = function (mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.addedNodes.length > 0) { addUnderlineClickListeners(doubleClickThreshold); } } }; let observer = new MutationObserver(callback); observer.observe(targetNode, config); } // base method async function waitPageLoaded() { await whenElementExist(".readerCatalog_list_item"); } function addStyle(cssRules) { const styleElement = document.createElement("style"); styleElement.innerHTML = cssRules; document.head.appendChild(styleElement); } function whenElementExist(selector) { return new Promise((resolve) => { const checkForElement = () => { let element = null; if (typeof selector === "function") { element = selector(); } else { element = document.querySelector(selector); } if (element) { resolve(element); } else { requestAnimationFrame(checkForElement); } }; checkForElement(); }); } })();