/** * 侧边栏管理类 */ 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文件用于处理侧边栏内部的关闭按钮事件