123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- /**
- * 侧边栏管理类
- */
- class SidebarManager {
- /**
- * @constant {Object} 配置常量
- */
- static CONFIG = {
- SIDEBAR_ID: "paiwise-sidebar",
- WRAPPER_ID: "paiwise-main-wrapper",
- STORAGE_KEY: "sidebarOpen",
- };
- constructor() {
- this.sidebarId = SidebarManager.CONFIG.SIDEBAR_ID;
- this.isOpen = false;
- this.currentUrl = window.location.href;
- this.sidebarFrame = null;
- this.urlObserver = new UrlChangeObserver();
- this.setupUrlChangeHandlers();
- this.init();
- }
- /**
- * 初始化侧边栏
- */
- async init() {
- try {
- // 添加错误恢复机制
- const maxRetries = 3;
- let retries = 0;
- while (retries < maxRetries) {
- try {
- await this.checkAndRestoreState();
- this.setupEventListeners();
- this.observeTitleChanges();
- break; // 成功则跳出循环
- } catch (error) {
- retries++;
- if (retries === maxRetries) {
- throw error;
- }
- // 等待一段时间后重试
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
- }
- } catch (error) {
- console.warn("Sidebar initialization failed:", error);
- }
- }
- /**
- * 设置事件监听器
- */
- setupEventListeners() {
- // 监听页面加载完成事件
- window.addEventListener("load", () => this.checkAndRestoreState());
- // 监听来自 sidebar 的消息
- window.addEventListener("message", this.handleMessage.bind(this));
- // 监听窗口大小变化
- window.addEventListener(
- "resize",
- Utils.debounce(() => this.handleResize(), 100)
- );
- // 监听页面可见性变化
- document.addEventListener("visibilitychange", () => {
- if (!document.hidden) {
- this.checkAndRestoreState();
- }
- });
- // 监听 history 变化
- window.addEventListener("popstate", () => {
- this.checkAndRestoreState();
- });
- // 监听页面 URL 变化
- let lastUrl = location.href;
- new MutationObserver(() => {
- const url = location.href;
- if (url !== lastUrl) {
- lastUrl = url;
- this.checkAndRestoreState();
- }
- }).observe(document, { subtree: true, childList: true });
- // 监听来自sidebar的消息
- window.addEventListener("message", async (event) => {
- if (event.data.type === "COPY_TO_CLIPBOARD") {
- try {
- await navigator.clipboard.writeText(event.data.text);
- // 可选:发送成功消息回sidebar
- event.source.postMessage(
- {
- type: "COPY_SUCCESS",
- },
- "*"
- );
- } catch (err) {
- console.error("Failed to copy text:", err);
- // 可选:发送失败消息回sidebar
- event.source.postMessage(
- {
- type: "COPY_ERROR",
- error: err.message,
- },
- "*"
- );
- }
- }
- });
- // 监听来自iframe的消息
- window.addEventListener("message", (event) => {
- // 确保消息来自我们的iframe
- if (
- event.source ===
- document.getElementById("paiwise-sidebar")?.contentWindow
- ) {
- if (event.data.type === "ANALYZE_PAGE") {
- // 分析页面并返回结果
- const pageInfo = window.pageAnalyzer.analyzePage();
- // 发送分析结果回iframe
- event.source.postMessage(
- {
- type: "PAGE_ANALYSIS_RESULT",
- pageInfo: pageInfo,
- },
- "*"
- );
- }
- }
- });
- }
- /**
- * 处理接收到的消息
- * @param {MessageEvent} event
- */
- handleMessage(event) {
- if (event.data.action === "closeSidebar") {
- this.removeSidebar();
- }
- }
- /**
- * 处理窗口大小变化
- */
- handleResize() {
- const sidebar = document.getElementById(this.sidebarId);
- if (sidebar && this.isOpen) {
- sidebar.style.height = `${window.innerHeight}px`;
- this.adjustPageLayout();
- }
- }
- /**
- * 检查并恢复侧边栏状态
- */
- async checkAndRestoreState() {
- try {
- const isOpen = await Utils.getStorageData(
- SidebarManager.CONFIG.STORAGE_KEY
- );
- // 如果获取存储数据失败,默认为关闭状态
- if (isOpen === null) {
- return;
- }
- if (isOpen && !this.isOpen) {
- // 只有当应该打开且当前未打开时才创建
- this.createSidebar();
- } else if (!isOpen && this.isOpen) {
- // 只有当应该关闭且当前打开时才移除
- this.removeSidebar();
- }
- } catch (error) {
- console.warn("Failed to restore sidebar state:", error);
- // 出错时不做任何操作,保持当前状态
- }
- }
- /**
- * 发送页面信息到iframe
- */
- sendPageInfo() {
- const iframe = document.getElementById(this.sidebarId);
- if (!iframe) return;
- // 获取页面favicon
- let favicon = "";
- const iconLink = document.querySelector('link[rel*="icon"]');
- if (iconLink) {
- favicon = iconLink.href;
- } else {
- // 如果没有找到,使用网站根目录的favicon.ico
- const url = new URL(window.location.href);
- favicon = `${url.protocol}//${url.hostname}/favicon.ico`;
- }
- // 发送页面信息到iframe
- iframe.contentWindow.postMessage(
- {
- type: "PAGE_INFO",
- pageInfo: {
- favicon: favicon,
- title: document.title,
- url: window.location.href,
- },
- },
- "*"
- );
- }
- /**
- * 创建侧边栏
- */
- async createSidebar() {
- if (document.getElementById(this.sidebarId)) return;
- try {
- // 创建页面包装器
- let wrapper = document.querySelector(".page-wrapper");
- if (!wrapper) {
- wrapper = document.createElement("div");
- wrapper.className = "page-wrapper";
- // 保存body的当前内容
- const bodyContent = Array.from(document.body.children);
- // 将所有内容(除了已有的侧边栏)移动到包装器中
- bodyContent.forEach((child) => {
- if (child.id !== this.sidebarId) {
- wrapper.appendChild(child);
- }
- });
- // 添加包装器到body
- if (wrapper.hasChildNodes()) {
- document.body.insertBefore(wrapper, document.body.firstChild);
- }
- }
- this.sidebarFrame = document.createElement("iframe");
- this.sidebarFrame.id = this.sidebarId;
- this.sidebarFrame.src = chrome.runtime.getURL("sidebar.html");
- document.body.appendChild(this.sidebarFrame);
- document.body.classList.add("sidebar-open");
- // 确保在页面加载完成后重新应用布局
- if (document.readyState === "complete") {
- this.reapplyLayout();
- } else {
- window.addEventListener("load", () => this.reapplyLayout(), {
- once: true,
- });
- }
- // 等待iframe加载完成
- await new Promise((resolve) => {
- this.sidebarFrame.onload = () => {
- this.sendPageInfo();
- resolve();
- };
- });
- this.sidebarFrame.classList.add("show");
- this.isOpen = true;
- await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, true);
- } catch (error) {
- console.error("Failed to create sidebar:", error);
- if (this.sidebarFrame) {
- this.sidebarFrame.remove();
- this.sidebarFrame = null;
- }
- document.body.classList.remove("sidebar-open");
- }
- }
- /**
- * 移除侧边栏
- */
- async removeSidebar() {
- if (!this.isOpen) return;
- try {
- if (this.sidebarFrame) {
- this.sidebarFrame.classList.remove("show");
- // 等待动画完成后再移除元素
- await new Promise((resolve) => {
- this.sidebarFrame.addEventListener(
- "transitionend",
- () => {
- document.documentElement.classList.remove("sidebar-open");
- this.sidebarFrame.remove();
- this.sidebarFrame = null;
- resolve();
- },
- { once: true }
- );
- });
- }
- this.isOpen = false;
- await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, false);
- } catch (error) {
- console.error("Failed to remove sidebar:", error);
- }
- }
- /**
- * 切换侧边栏显示状态
- */
- toggle() {
- if (this.isOpen) {
- this.removeSidebar();
- } else {
- this.createSidebar();
- }
- }
- setupUrlChangeHandlers() {
- // 注册URL变化的处理函数
- this.urlObserver.addObserver(() => {
- // 更新页面信息
- this.updatePageInfo();
- // 重新应用布局
- this.reapplyLayout();
- });
- // 开始监听URL变化
- this.urlObserver.startObserving();
- }
- observeTitleChanges() {
- // 监听document.title的变化
- const titleObserver = new MutationObserver(() => {
- this.updatePageInfo();
- });
- titleObserver.observe(document.querySelector("head > title"), {
- subtree: true,
- characterData: true,
- childList: true,
- });
- }
- updatePageInfo() {
- const frame = this.sidebarFrame || document.getElementById(this.sidebarId);
- if (frame) {
- const pageInfo = {
- title: document.title,
- url: window.location.href,
- favicon: this.getFavicon(),
- };
- frame.contentWindow.postMessage(
- {
- type: "PAGE_INFO",
- pageInfo,
- },
- "*"
- );
- }
- }
- getFavicon() {
- // 尝试获取页面favicon
- const iconLink = document.querySelector("link[rel*='icon']");
- if (iconLink) return iconLink.href;
- // 如果没有找到,返回网站根目录的favicon.ico
- const url = new URL(window.location.href);
- return `${url.protocol}//${url.hostname}/favicon.ico`;
- }
- /**
- * 重新应用布局
- */
- reapplyLayout() {
- if (!this.isOpen) return;
- document.body.classList.remove("sidebar-open");
- requestAnimationFrame(() => {
- document.body.classList.add("sidebar-open");
- });
- }
- }
- class UrlChangeObserver {
- constructor() {
- this.observers = [];
- this.currentUrl = window.location.href;
- }
- // 添加观察者
- addObserver(observer) {
- this.observers.push(observer);
- }
- // 通知所有观察者
- notifyObservers(newUrl) {
- this.observers.forEach((observer) => observer(newUrl));
- }
- // 开始监听URL变化
- startObserving() {
- // 使用MutationObserver监听DOM变化
- const observer = new MutationObserver(() => {
- if (this.currentUrl !== window.location.href) {
- const newUrl = window.location.href;
- this.currentUrl = newUrl;
- this.notifyObservers(newUrl);
- }
- });
- // 监听整个文档的变化
- observer.observe(document.documentElement, {
- childList: true,
- subtree: true,
- });
- // 监听history变化
- window.addEventListener("popstate", () => {
- this.notifyObservers(window.location.href);
- });
- // 监听hashchange事件
- window.addEventListener("hashchange", () => {
- this.notifyObservers(window.location.href);
- });
- }
- }
- // 创建一个全局的URL观察者实例
- window.urlObserver = new UrlChangeObserver();
- // 初始化侧边栏管理器
- const sidebarManager = new SidebarManager();
- // 监听来自背景脚本的消息
- chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
- if (message.action === "toggleSidebar") {
- sidebarManager.toggle();
- sendResponse({ success: true });
- }
- });
- // 其他需要监听URL变化的功能也可以注册到观察者
- window.urlObserver.addObserver((newUrl) => {
- // 例如:更新总结卡片
- console.log("URL changed to:", newUrl);
- // 在这里添加更新总结卡片的逻辑
- });
- // 开始监听URL变化
- window.urlObserver.startObserving();
- // 创建一个新的js文件用于处理侧边栏内部的关闭按钮事件
|