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

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

As of 2024-11-26. See the latest version.

// ==UserScript==
// @name         微信读书笔记列表跟随当前章节滚动
// @namespace    http://tampermonkey.net/
// @version      0.6.1
// @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";

  let lastNote = null;

  // 主函数,初始化脚本
  function init() {
    injectCss();
    injectNoteButton();
    addUnderlineClickListeners();
    observeDOMChanges();
  }

  // 注入自定义CSS样式
  function injectCss() {
    let css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = `
      .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: 0;
          right: 0;
          width: 80%;
          margin-left: 0;
          display: none;
          z-index: 10000;
          background: white;
      }
      .readerBottomBar {
          z-index: 9999;
      }
      `;
    document.head.appendChild(css);
  }

  // 在底部栏注入笔记按钮
  function injectNoteButton() {
    let note_btn = `
      <button title="笔记" class="rbb_item wr_note">
          <span class="icon"></span>
          <span class="txt">笔记</span>
      </button>`;
    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";
            jumpNote();

            // 添加事件监听器,检测点击面板外部隐藏面板
            setTimeout(() => {
              document.addEventListener("click", outsideClickListener);
            }, 0);
          } else {
            reader_note_panel.style.display = "none";
            document.removeEventListener("click", outsideClickListener);
          }
        });
    }
  }

  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 getChapterTitle() {
    let chapter_title = document.querySelector(".readerTopBar_title_chapter");
    if (chapter_title) {
      return chapter_title.innerText.trim();
    }
    return "";
  }

  // 滚动笔记面板到当前章节
  function jumpNote() {
    let chapterTitle = getChapterTitle();
    console.log("Chapter Title: " + chapterTitle);

    // 在笔记面板中查找匹配的章节
    let noteChapters = document.querySelectorAll(
      ".wr_reader_note_panel_chapter_title"
    );
    for (let i = 0; i < noteChapters.length; i++) {
      if (noteChapters[i].innerText.trim() === chapterTitle) {
        console.log("Found chapter in notes: " + chapterTitle);
        // 滚动到笔记面板中的该章节
        noteChapters[i].scrollIntoView({ block: "center" });
        // 如果之前有选中的笔记,滚动到该笔记
        if (lastNote) {
          lastNote.scrollIntoView({ block: "center" });
        }
        break;
      }
    }

      // 检查当前屏幕可见范围内是否有高亮文本
      let underlines = document.getElementsByClassName("wr_underline_wrapper");

      // 获取视口高度和宽度
      let viewportHeight = window.innerHeight || document.documentElement.clientHeight;
      let viewportWidth = window.innerWidth || document.documentElement.clientWidth;
      let viewportCenterY = viewportHeight / 2;

      // 筛选在视口内的高亮文本
      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]);
          }
      }

      if (visibleUnderlines.length > 0) {
          // 找到离视口中心最近的高亮文本
          let closestUnderline = visibleUnderlines.reduce((prev, curr) => {
              let prevRect = prev.getBoundingClientRect();
              let currRect = curr.getBoundingClientRect();

              let prevDistance = Math.abs((prevRect.top + prevRect.bottom) / 2 - viewportCenterY);
              let currDistance = Math.abs((currRect.top + currRect.bottom) / 2 - viewportCenterY);

              return prevDistance < currDistance ? prev : curr;
          });

          // 模拟点击最近的高亮文本,唤出复制按钮
          closestUnderline.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);
                          console.log('当前视野内高亮最先内容: ' + selectionText);

                          // 查找并滚动到该笔记项
                          scrollToNoteItem(selectionText);
                      }
                  }

                  document.addEventListener('copy', onCopy, true);

                  // 模拟点击复制按钮
                  copyButton.click();

              } else {
                  console.log('Copy button not found.');
              }
          }, 100); // 根据需要调整延迟,以确保工具栏已出现
      } else {
          console.log("No highlighted text in viewport.");
      }
  }

  function scrollToNoteItem(selectionText) {
    let noteItems = document.querySelectorAll('.wr_reader_note_panel_item_cell_wrapper');
    let foundIndex = -1;
    for (let j = 0; j < noteItems.length; j++) {
        let noteTextElement = noteItems[j].querySelector('.wr_reader_note_panel_item_cell_content_text');
        if (noteTextElement) {
            let noteText = noteTextElement.innerText.replace(/\s/g, '');
            if (selectionText === noteText) {
                foundIndex = j;
                break;
            }
        }
    }

    if (foundIndex >= 0) {
      //  滚动到item
      noteItems[foundIndex].scrollIntoView({ block: "center" });
      console.log('聚焦到笔记项:' + selectionText);
    } else {
        console.log("Cur Note not found for text: " + selectionText);
    }
  }

  // 为高亮文本添加事件监听器
  function addUnderlineClickListeners() {
    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;
      const doubleClickThreshold = 20;

      underlines[i].addEventListener("click", function (e) {
        const currentTime = new Date().getTime();
        clickCount++;
        if (clickCount === 1) {
          setTimeout(function () {
            if (clickCount === 1) {
              // 单击:可添加显示工具栏的逻辑
            } else if (clickCount === 2) {
              // 检测到双击
              handleDoubleClick(e.target);
            }
            clickCount = 0;
          }, doubleClickThreshold);
        }
        lastClickTime = currentTime;
      });
    }
  }

  // 处理高亮文本的双击事件
  function handleDoubleClick(element) {
    // 模拟点击元素以选中并显示工具栏
    element.click();

    setTimeout(() => {
      // 工具栏应该已显示
      let copyButton = document.querySelector(".toolbarItem.wr_copy");
      if (copyButton) {
        // 添加标志量,表示是自定义的复制操作
        window.isCustomCopy = true;
        // toast toast_Show , hide toast
        // 添加事件监听器,拦截复制事件
        function onCopy(e) {
          if (window.isCustomCopy) {
            // const selectionText = window.getSelection().toString();
            // 拦截复制事件的复制内容,从 e 中获取
            let selectionText = e.target.value;
            e.preventDefault();
            e.stopPropagation();
            // 获取选中文本
            // 重置标志量
            window.isCustomCopy = false;

            // 移除事件监听器
            document.removeEventListener("copy", onCopy, true);

            // 查找并点击下一条笔记
            findAndClickNextNoteItem(selectionText);
          }
        }

        document.addEventListener("copy", onCopy, true);

        // 模拟点击复制按钮
        copyButton.click();
      } else {
        console.log("Copy button not found.");
      }
    }, 50); // 根据需要调整延迟,以确保工具栏已出现
  }

  function findAndClickNextNoteItem(selectionText) {
    let noteItems = document.querySelectorAll(
      ".wr_reader_note_panel_item_cell_wrapper.clickable"
    );
    let foundIndex = -1;
    for (let j = 0; j < noteItems.length; j++) {
      let noteTextElement = noteItems[j].querySelector(
        ".wr_reader_note_panel_item_cell_content_text"
      );
      // 移除空格和换行符
      selectionText = selectionText.replace(/\s/g, "");

      if (noteTextElement) {
        let noteText = noteTextElement.innerText.replace(/\s/g, "");
        if (selectionText === noteText) {
          foundIndex = j;
          break;
        }
      }
    }

    if (foundIndex >= 0) {
      let nextIndex = foundIndex + 1;
      if (nextIndex >= noteItems.length) {
        nextIndex = 0;
      }

      noteItems[nextIndex].click();
      lastNote = noteItems[nextIndex];
      // 滚动笔记面板到下一条笔记
      noteItems[nextIndex].scrollIntoView({ block: "center" });
    } else {
      console.log("Note not found for text: " + selectionText);
    }
  }

  // 观察DOM变化,添加监听器到新添加的高亮文本
  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();
        }
      }
    };

    let observer = new MutationObserver(callback);
    observer.observe(targetNode, config);
  }

  // 等待页面加载必要的元素
  let timer = setInterval(function () {
    let reader_note_panel = document.querySelector(".readerNotePanel");
    if (reader_note_panel) {
      clearInterval(timer);
      init();
    }
  }, 1000);
})();