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

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

目前為 2024-11-26 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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