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

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

当前为 2023-09-05 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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";
                                }, 1600);
                            }

                            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);
})();