微信读书笔记列表跟随当前章节滚动

笔记列表跟随当前章节滚动

目前为 2023-09-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         微信读书笔记列表跟随当前章节滚动
// @namespace    http://tampermonkey.net/
// @version      0.4.9
// @description  笔记列表跟随当前章节滚动
// @author       XQH
// @match        https://weread.qq.com/web/reader/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=qq.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    // 等待页面加载完毕
    setTimeout(function () {
        // 保存点击的上一个笔记
        let lastNote = null;
        let lastPos = -1;
        // 双击
        let clickCount = 0;
        let lastClickTime = 0;
        const doubleClickThreshold = 300; // ms
        // 要注入的底部栏
        let invoke = document.querySelector(".readerBottomBar.showShadow");
        // 笔记面板
        let notePanel = document.getElementsByClassName("readerNotePanel")[0];
        if (notePanel) {
            notePanel.style.display = "none";
        }

        let title = document.querySelector(".readerTopBar_title_chapter");
        let noteList = document.getElementsByClassName("readerNoteList")[0];
        let noteTexts = noteList.getElementsByClassName("sectionListItem_title");

        let noteItems = document.getElementsByClassName("sectionListItem_content noteItem_content clickable");
        // 获取网页标题 css:.readerTopBar_title_chapter

        function jumpNote() {
            // 获取title文字,查找noteList中div(class:noteItemClz)子标签文字对应标签
            let titleText = title.innerText;
            for (let i = 0; i < noteTexts.length; i++) {
                if (noteTexts[i].innerText == titleText) {
                    noteList.scrollTop = noteTexts[i].offsetTop - 100;
                    break;
                }
            }
            // 如果保存了上一个笔记
            if (lastNote) {
                // 滚动到笔记位置
                noteList.scrollTop = lastNote.offsetTop - 100;
            }

        }

        function getClipboardText() {
            return new Promise(function (resolve, reject) {
                if (navigator.clipboard && navigator.clipboard.readText) {
                    // 使用新的 navigator.clipboard API 获取剪贴板内容
                    navigator.clipboard.readText().then(function (text) {
                        resolve(text);
                    }).catch(function (err) {
                        reject(err);
                    });
                } else if (window.clipboardData && window.clipboardData.getData) {
                    // 使用旧的 window.clipboardData 获取剪贴板内容
                    let clipboardText = window.clipboardData.getData('Text');
                    resolve(clipboardText);
                } else if (event.clipboardData && event.clipboardData.getData) {
                    // 使用事件对象的 clipboardData 获取剪贴板内容
                    let clipboardText = event.clipboardData.getData('text/plain');
                    resolve(clipboardText);
                } else {
                    reject(new Error('Clipboard API not supported'));
                }
            });
        }

        // 定时隐藏推广按钮
        setInterval(function () {
            // 隐藏推广按钮
            let appDownloadBtn = document.querySelector(".readerControls_item.download");
            if (appDownloadBtn) {
                appDownloadBtn.style.display = "none";
            }
            // 移动端UA下会显示打开app阅读
            let toAppBtn = document.querySelector(".readerFooter_button.blue");
            if (toAppBtn) {
                // 直接删除该元素
                toAppBtn.parentNode.removeChild(toAppBtn);
            }
            // 笔记面板底部栏移除
            let notePanelFooter = document.querySelector(".readerNotePanelBottomBar");
            if (notePanelFooter) {
                notePanelFooter.parentNode.removeChild(notePanelFooter);
            }

            // 如果底部栏未显示,同步隐藏笔记面板
            if (invoke && !invoke.classList.contains("active")) {
                // 检测是否宽屏显示侧栏(clz readerControls readerControls)
                let readerControls = document.querySelector(".readerControls.readerControls");
                if (readerControls && readerControls.style.display == "none") {
                    notePanel.style.display = "none";
                }
            }

            // 获取所有 clz:wr_underline_wrapper, 设置点击事件
            let underlines = document.getElementsByClassName("wr_underline_wrapper");
            for (let i = 0; i < underlines.length; i++) {
                // 判断是否已添加点击事件
                if (underlines[i].getAttribute("data-clicked")) {
                    continue;
                }
                underlines[i].setAttribute("data-clicked", true);
                underlines[i].addEventListener("click", function () {
                    // 调用复制按钮点击事件 toolbarItem copy
                    let copyBtn = document.querySelector(".toolbarItem.copy");
                    copyBtn.dispatchEvent(
                        new MouseEvent("click", {
                            clientX: 1,
                            clientY: 1,
                        })
                    );
                    // 设置当前lastNode 为 剪切板内容比较noteTexts结果
                    setTimeout(function () {
                        let toasts = document.querySelectorAll(".toast.toast_Show");
                        for (let i = 0; i < toasts.length; i++) {
                            toasts[i].parentNode.removeChild(toasts[i]); // 移除dom
                        }
                    }, 50);
                    getClipboardText().then(function (text) {

                        for (let i = 0; i < noteTexts.length; i++) {
                            if (noteTexts[i].innerText == text) {
                                lastNote = noteItems[i];
                                lastPos = i;
                                break;
                            }
                        }

                        // 隐藏复制成功提示



                    }).catch(function (err) {
                        console.error('Failed to get clipboard text:', err);
                    });

                    // 双击判断
                    const currentTime = new Date().getTime();
                    if (currentTime - lastClickTime < doubleClickThreshold && clickCount == 1) {
                        clickCount = 0;
                        // 获取当前 note 的下一条
                        let index = lastPos + 1;
                        if (index >= noteItems.length) {
                            index = 0;
                        }
                        lastPos = index;

                        let nextNote = noteItems[lastPos];
                        nextNote.click(); // 触发点击事件跳转到下一条笔记
                        lastNote = nextNote;

                        setTimeout(function () {

                            jumpNote();
                        }, 50);
                    } else {
                        clickCount++;
                        setTimeout(function () {
                            if (clickCount == 1) {
                                // 设置显示 clz:reader_toolbar_container
                                let toolbar = document.querySelector(".reader_toolbar_container");
                                toolbar.style.display = "";
                                setTimeout(function () {
                                    toolbar.style.display = "none";
                                }, 2000);
                            }

                            clickCount = 0;
                        }, doubleClickThreshold);
                    }
                    lastClickTime = currentTime;
                });
            }


            
            // 持续替换笔记操作栏中
            // 移除波浪线,直线item,替换为上下一条笔记
            
            let hand = document.querySelector(".toolbarItem.underlineHandWrite");
            let straight = document.querySelector(".toolbarItem.underlineStraight");
            let lastPage = document.querySelector(".toolbarItem.lastPage");
            let nextPage = document.querySelector(".toolbarItem.nextPage");
            // 检测到删除划线按钮(clz:toolbarItem removeUnderline)时,添加上下一条笔记按钮
            if (!document.querySelector(".toolbarItem.removeUnderline")) {
                // 移除上下一条笔记按钮
                if (lastPage) {
                    lastPage.parentNode.removeChild(lastPage);
                }
                if (nextPage) {
                    nextPage.parentNode.removeChild(nextPage);
                }
                return;
            } else if (hand && straight){
                let par = hand.parentElement;
                par.removeChild(hand);
                par.removeChild(straight);
            }


            if (!lastPage || !nextPage) {
                
                let toolInvoke = document.querySelector(".toolbarItem.copy");

                let lastPage = document.createElement("button");
                lastPage.className = "toolbarItem lastPage";
                lastPage.title = "上一条笔记";
                lastPage.innerHTML = '<span class="toolbarItem_text">后退</span>';

                let nextPage = document.createElement("button");
                nextPage.className = "toolbarItem nextPage";
                nextPage.title = "下一条笔记";
                nextPage.innerHTML = '<span class="toolbarItem_text">前进</span>';

                toolInvoke.parentNode.insertBefore(lastPage, toolInvoke);
                toolInvoke.parentNode.insertBefore(nextPage, toolInvoke);

                // lastPage触发当前note的上一条的点击事件,nextPage触发当前note的下一条的点击事件
                lastPage.addEventListener("click", function () {
                    // 获取当前note的上一条
                    let index = lastPos - 1;
                    if (index < 0) {
                        index = noteItems.length - 1;
                    }
                    lastPos = index;
                    let prevNote = noteItems[lastPos];
                    prevNote.dispatchEvent(
                        new MouseEvent("click", {
                            clientX: 1,
                            clientY: 1,
                        })
                    );
                    lastNote = prevNote;

                    // 跳转到上一个笔记位置
                    setTimeout(function () {
                        jumpNote();

                    }, 100);
                });
                nextPage.addEventListener("click", function () {
                    // 获取当前note的下一条
                    let index = lastPos - 1;
                    if (index < 0) {
                        index = noteItems.length - 1;
                    }
                    lastPos = index;
                    let nextNote = noteItems[lastPos];
                    nextNote.dispatchEvent(
                        new MouseEvent("click", {
                            clientX: 1,
                            clientY: 1,
                        })
                    );
                    lastNote = nextNote;
                    // 跳转到上一个笔记位置
                    setTimeout(function () {
                        jumpNote();

                    }, 100);
                }
                );
            }

        }, 800)

        // 检测移动端左右滑动手势,左滑显示笔记面板,右滑隐藏笔记面板
        var start_x = 0; // 记录起始位置

        document.addEventListener('touchstart', function (e) {
            // 获取当前手指的横向位置
            start_x = e.touches[0].clientX;
        });

        document.addEventListener('touchmove', function (e) {
            // 计算手指的横向偏移量
            var offset_x = e.touches[0].clientX - start_x;

            // 如果偏移量大于一定值,则认为是左右滑手势
            if (Math.abs(offset_x) > 20) {
                e.preventDefault(); // 阻止默认事件
            }
        });

        document.addEventListener('touchend', function (e) {
            // 计算手指的横向偏移量
            var offset_x = e.changedTouches[0].clientX - start_x;

            // 如果偏移量大于一定值,则认为是左右滑手势
            if (offset_x > 20) {
                // 右滑处理逻辑
                notePanel.style.width = "100%";
                notePanel.style.display = "none";
                // 清除过渡动画
                notePanel.style.transition = "";

            } else if (offset_x < -20) {
                // 注入过渡动画
                notePanel.style.transition = "width 0.5s";
                // 左滑处理逻辑
                notePanel.style.display = "";
                // 设置宽度占3/4
                notePanel.style.width = "75%";
                notePanel.style.height = "100%";
                // 设置间隔
                notePanel.style.marginLeft = "25%";
                // 置顶
                notePanel.style.top = "0px";
                // 显示invoke (添加acitve)
                invoke.classList.add("active");

            }
        });
        






        // 为笔记列表添加点击事件



        // 为所有(sectionListItem_content noteItem_content clickable)设置点击隐藏notePanel
        for (let i = 0; i < noteItems.length; i++) {
            noteItems[i].addEventListener("click", function () {
                // 保存到上一个笔记
                lastNote = noteItems[i];
                lastPos = i;
                notePanel.style.display = "none";
                // 获取笔记文字
                let noteText = noteItems[i].querySelector(".text").innerText;
                // 调用大声朗读api
                setTimeout(function () {
                    // 获取当前.app_content(div)  离顶部的距离
                    let appContent = document.querySelector(".app_content");
                    let appContentTop = Math.round(appContent.getBoundingClientRect().top);
                    // 如果top 为-75(PC) -11(MOBILE),这个高度可能受屏幕高度影响(?),需检测是否有上一页,可能需要调用翻页并重新调用跳转笔记
                    console.log("appContentTop:" + appContentTop);
                    // TODO 考虑重构代码理清逻辑
                    if (appContentTop == -75 || appContentTop == -11 || appContentTop == -12) {
                        //    则判断为上一页,调用翻页并重新调用跳转笔记
                        console.log("start to judge if it is prev page");
                        // 可以考虑每个笔记跳转时都直接切换章节再跳转避免触发翻页bug
                        setTimeout(function () {
                            // 高度无变化,判断为需要切换上一页
                            if (appContentTop == -75 || appContentTop == -11 || appContentTop == -12) {
                                // 获取 ul(clz:readerCatalog_list),
                                // 下的div(chapterItem_link chapterItem_level1)比较div中span文字
                                // 选择不与当前标题相同的item调用点击事件,再重新调用切换笔记
                                let catalogList = document.querySelector(".readerCatalog_list");
                                let catalogItems = catalogList.getElementsByClassName("chapterItem_link chapterItem_level1");
                                let titleText = title.innerText;
                                for (let i = 0; i < catalogItems.length; i++) {
                                    if (catalogItems[i].querySelector("span").innerText != titleText) {
                                        console.log("jump another chapter");
                                        catalogItems[i].dispatchEvent(
                                            new MouseEvent("click", {
                                                clientX: 1,
                                                clientY: 1,
                                            })
                                        );
                                        break;
                                    }
                                }

                                setTimeout(function () {
                                    lastNote.dispatchEvent(
                                        new MouseEvent("click", {
                                            clientX: 1,
                                            clientY: 1,
                                        })
                                    );
                                    // 跳转到上一个笔记位置
                                    setTimeout(function () {
                                        jumpNote();

                                    }, 100);
                                }, 600);
                                // console.log("start to jumpNote");
                                // localStorage.setItem("lastNotePos", i);
                                // localStorage.setItem("need_jump", true);

                            }
                        }, 1600);
                    }
                }, 100);
            });


        }

        // 如果localStorage中保存了上一个笔记
        // if (localStorage.getItem("lastNotePos")) {
        //     // 获取上一个笔记
        //     let lastPos = localStorage.getItem("lastNotePos");
        //     if (lastPos) {
        //         lastNote = noteItems[lastPos];
        //         // 获取是否需要跳转
        //         let need_jump = localStorage.getItem("need_jump");
        //         if (need_jump == "true") {
        //             lastNote.dispatchEvent(
        //                 new MouseEvent("click", {
        //                   clientX: 1,
        //                   clientY: 1,
        //                 })
        //               );
        //             // 跳转到上一个笔记位置
        //             setTimeout(function () {
        //                 jumpNote();
        //                 // 清除localStorage
        //                 localStorage.removeItem("lastNotePos");
        //                 localStorage.removeItem("need_jump");
        //             }, 100);
        //         }
        //     }

        // }


        // 底部添加笔记按钮

        // 获取button.rbb_item
        if (invoke) {
            // 获取笔记图标
            let noteIcon = document.querySelector('.readerControls_item.note').querySelector('.icon');
            let backgroundImg = getComputedStyle(noteIcon).getPropertyValue("background-image");

            // 在readerBottomBar 的 rbb_item.setting(bar内第三个button) 后添加一个button
            let newInvoke = document.createElement("button");
            newInvoke.className = "rbb_item note";
            newInvoke.title = "笔记";
            newInvoke.innerHTML = '<span class="icon""></span><span class="txt">笔记</span>';

            invoke.insertBefore(newInvoke, invoke.children[3]);
            newInvoke.querySelector('.icon').style.backgroundImage = backgroundImg;
            // 设置点击事件为切换readerNotePanel的display
            newInvoke.addEventListener("click", function () {
                // 不隐藏readerBottomBar, 为readerBottomBar添加active class
                setTimeout(function () {
                    invoke.classList.add("active");

                }, 100);
                let notePanel = document.getElementsByClassName("readerNotePanel")[0];
                if (notePanel.style.display == "none") {

                    // 注入过渡动画
                    notePanel.style.transition = "width 0.5s";
                    // 左滑处理逻辑
                    notePanel.style.display = "";
                    // 设置宽度占3/4
                    notePanel.style.width = "75%";
                    notePanel.style.height = "100%";
                    // 设置间隔
                    notePanel.style.marginLeft = "25%";
                    // 置顶
                    notePanel.style.top = "0px";
                } else {
                    notePanel.style.width = "100%";
                    notePanel.style.display = "none";
                }
                // 延迟100ms
                setTimeout(function () {
                    jumpNote();
                }, 100);
            });

        }
        // 监听点击事件(readerControls_item note)
        document.querySelector(".readerControls_item.note").addEventListener("click", function () {
            jumpNote();
        });




    }, 1500);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址