Переглянути джерело

修复url变动时总结卡片内容没有跟随变动的问题

alibct 7 місяців тому
батько
коміт
f1d6238d9c
5 змінених файлів з 278 додано та 136 видалено
  1. 24 32
      css/content.css
  2. 21 14
      js/chat-ui.js
  3. 205 82
      js/content.js
  4. 8 0
      js/sidebar.js
  5. 20 8
      js/utils.js

+ 24 - 32
css/content.css

@@ -1,58 +1,50 @@
-/* 重置页面布局 - 移除全局overflow限制 */
+/* 基础样式 */
 html,
 body {
   margin: 0 !important;
   padding: 0 !important;
-  width: 100% !important;
-  height: 100% !important;
+  overflow-x: hidden !important;
 }
 
-/* 动画中状态 - 禁用页面滚动 */
-body.sidebar-animating {
-  overflow: hidden !important;
+/* 页面包装器 */
+.page-wrapper {
+  position: relative !important;
+  width: 100% !important;
+  transition: width 0.3s ease, margin-right 0.3s ease !important;
 }
 
-/* 主内容区域 */
-#paiwise-main-wrapper {
-  position: fixed !important;
-  top: 0 !important;
-  left: 0 !important;
-  width: 100% !important;
-  height: 100vh !important;
-  overflow-y: auto !important;
-  overflow-x: hidden !important;
-  transition: width 0.3s ease-out !important;
-  backface-visibility: hidden !important; /* 优化动画性能 */
-  -webkit-font-smoothing: antialiased !important;
+/* 当侧边栏打开时的页面包装器样式 */
+body.sidebar-open .page-wrapper {
+  width: calc(100% - 400px) !important;
+  margin-right: 400px !important;
 }
 
 /* 侧边栏 */
 #paiwise-sidebar {
   position: fixed !important;
   top: 0 !important;
-  right: 0 !important;
+  right: -400px !important;
   width: 400px !important;
   height: 100vh !important;
   background: #fff !important;
-  border: none !important;
-  box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08) !important;
+  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15) !important;
   z-index: 2147483647 !important;
-  transform: translateX(100%) !important;
-  visibility: hidden !important;
-  opacity: 0 !important;
-  transition: transform 0.3s ease-out, visibility 0.3s, opacity 0.3s !important;
+  border: none !important;
+  transition: right 0.3s ease !important;
 }
 
 /* 侧边栏显示状态 */
 #paiwise-sidebar.show {
-  transform: translateX(0) !important;
-  visibility: visible !important;
-  opacity: 1 !important;
+  right: 0 !important;
 }
 
-/* 主内容区域在侧边栏显示时的状态 */
-body.sidebar-open #paiwise-main-wrapper {
-  width: calc(100% - 400px) !important;
+/* 确保页面内容不会被侧边栏覆盖 */
+body.sidebar-open {
+  overflow-x: hidden !important;
 }
 
-/* 移除所有动画关键帧,使用transform实现动画 */
+/* 确保所有页面元素都在包装器内部正确显示 */
+.page-wrapper > * {
+  max-width: 100% !important;
+  box-sizing: border-box !important;
+}

+ 21 - 14
js/chat-ui.js

@@ -612,24 +612,31 @@ class ChatUI {
     window.addEventListener("message", (event) => {
       if (event.data.type === "PAGE_INFO") {
         const pageInfo = event.data.pageInfo;
-
-        // 更新卡片内容
-        const favicon = document.querySelector(".page-favicon");
-        const title = document.querySelector(".page-title");
-
-        // 设置网站图标
-        favicon.src = pageInfo.favicon;
-        favicon.onerror = () => {
-          // 如果图标加载失败,使用默认图标
-          favicon.src = chrome.runtime.getURL("images/icon16.png");
-        };
-
-        // 设置页面标题
-        title.textContent = pageInfo.title;
+        this.updatePageInfoCard(pageInfo);
       }
     });
   }
 
+  updatePageInfoCard(pageInfo) {
+    // 更新卡片内容
+    const favicon = document.querySelector(".page-favicon");
+    const title = document.querySelector(".page-title");
+
+    // 如果favicon发生变化,更新图标
+    if (favicon.src !== pageInfo.favicon) {
+      favicon.src = pageInfo.favicon;
+      favicon.onerror = () => {
+        // 如果图标加载失败,使用默认图标
+        favicon.src = chrome.runtime.getURL("images/icon16.png");
+      };
+    }
+
+    // 如果标题发生变化,更新标题
+    if (title.textContent !== pageInfo.title) {
+      title.textContent = pageInfo.title;
+    }
+  }
+
   /**
    * 获取页面favicon
    * @returns {string} favicon URL

+ 205 - 82
js/content.js

@@ -13,8 +13,11 @@ class SidebarManager {
 
   constructor() {
     this.sidebarId = SidebarManager.CONFIG.SIDEBAR_ID;
-    this.wrapperId = SidebarManager.CONFIG.WRAPPER_ID;
     this.isOpen = false;
+    this.currentUrl = window.location.href;
+    this.sidebarFrame = null;
+    this.urlObserver = new UrlChangeObserver();
+    this.setupUrlChangeHandlers();
     this.init();
   }
 
@@ -23,10 +26,27 @@ class SidebarManager {
    */
   async init() {
     try {
-      await this.checkAndRestoreState();
-      this.setupEventListeners();
+      // 添加错误恢复机制
+      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.error("Sidebar initialization failed:", error);
+      console.warn("Sidebar initialization failed:", error);
     }
   }
 
@@ -135,6 +155,7 @@ class SidebarManager {
     const sidebar = document.getElementById(this.sidebarId);
     if (sidebar && this.isOpen) {
       sidebar.style.height = `${window.innerHeight}px`;
+      this.adjustPageLayout();
     }
   }
 
@@ -146,6 +167,11 @@ class SidebarManager {
       const isOpen = await Utils.getStorageData(
         SidebarManager.CONFIG.STORAGE_KEY
       );
+      // 如果获取存储数据失败,默认为关闭状态
+      if (isOpen === null) {
+        return;
+      }
+
       if (isOpen && !this.isOpen) {
         // 只有当应该打开且当前未打开时才创建
         this.createSidebar();
@@ -154,56 +180,9 @@ class SidebarManager {
         this.removeSidebar();
       }
     } catch (error) {
-      console.error("Failed to restore sidebar state:", error);
-    }
-  }
-
-  /**
-   * 包装页面内容
-   */
-  wrapPageContent() {
-    if (document.getElementById(this.wrapperId)) return;
-
-    const wrapper = document.createElement("div");
-    wrapper.id = this.wrapperId;
-
-    // 保存body的原始样式
-    this.originalBodyStyle = {
-      width: document.body.style.width,
-      margin: document.body.style.margin,
-      position: document.body.style.position,
-      overflow: document.body.style.overflow,
-    };
-
-    // 包装内容
-    while (document.body.firstChild) {
-      if (document.body.firstChild.id !== this.sidebarId) {
-        wrapper.appendChild(document.body.firstChild);
-      } else {
-        document.body.removeChild(document.body.firstChild);
-      }
-    }
-
-    document.body.appendChild(wrapper);
-  }
-
-  /**
-   * 解除页面内容包装
-   */
-  unwrapPageContent() {
-    const wrapper = document.getElementById(this.wrapperId);
-    if (!wrapper) return;
-
-    // 恢复body的原始样式
-    if (this.originalBodyStyle) {
-      Object.assign(document.body.style, this.originalBodyStyle);
-    }
-
-    while (wrapper.firstChild) {
-      document.body.insertBefore(wrapper.firstChild, wrapper);
+      console.warn("Failed to restore sidebar state:", error);
+      // 出错时不做任何操作,保持当前状态
     }
-
-    wrapper.remove();
   }
 
   /**
@@ -245,41 +224,62 @@ class SidebarManager {
     if (document.getElementById(this.sidebarId)) return;
 
     try {
-      this.wrapPageContent();
+      // 创建页面包装器
+      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);
+        }
+      }
 
-      const iframe = document.createElement("iframe");
-      iframe.id = this.sidebarId;
-      iframe.src = chrome.runtime.getURL("sidebar.html");
+      this.sidebarFrame = document.createElement("iframe");
+      this.sidebarFrame.id = this.sidebarId;
+      this.sidebarFrame.src = chrome.runtime.getURL("sidebar.html");
 
-      // 先添加到DOM,但不添加show类
-      document.body.appendChild(iframe);
+      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) => {
-        iframe.onload = () => {
-          // 发送页面信息
+        this.sidebarFrame.onload = () => {
           this.sendPageInfo();
           resolve();
         };
       });
 
-      // 等待一帧以确保DOM更新
-      await new Promise((resolve) => requestAnimationFrame(resolve));
-
-      // 触发动画
-      document.body.classList.add("sidebar-open");
-      iframe.classList.add("show");
-
-      // 等待动画完成
-      await new Promise((resolve) => {
-        iframe.addEventListener("transitionend", resolve, { once: true });
-      });
-
+      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);
-      this.unwrapPageContent();
+      if (this.sidebarFrame) {
+        this.sidebarFrame.remove();
+        this.sidebarFrame = null;
+      }
+      document.body.classList.remove("sidebar-open");
     }
   }
 
@@ -290,19 +290,17 @@ class SidebarManager {
     if (!this.isOpen) return;
 
     try {
-      const sidebar = document.getElementById(this.sidebarId);
-
-      if (sidebar) {
-        document.body.classList.remove("sidebar-open");
-        sidebar.classList.remove("show");
+      if (this.sidebarFrame) {
+        this.sidebarFrame.classList.remove("show");
 
         // 等待动画完成后再移除元素
         await new Promise((resolve) => {
-          sidebar.addEventListener(
+          this.sidebarFrame.addEventListener(
             "transitionend",
             () => {
-              this.unwrapPageContent();
-              sidebar.remove();
+              document.documentElement.classList.remove("sidebar-open");
+              this.sidebarFrame.remove();
+              this.sidebarFrame = null;
               resolve();
             },
             { once: true }
@@ -327,8 +325,123 @@ class SidebarManager {
       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();
 
@@ -340,4 +453,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
   }
 });
 
+// 其他需要监听URL变化的功能也可以注册到观察者
+window.urlObserver.addObserver((newUrl) => {
+  // 例如:更新总结卡片
+  console.log("URL changed to:", newUrl);
+  // 在这里添加更新总结卡片的逻辑
+});
+
+// 开始监听URL变化
+window.urlObserver.startObserving();
+
 // 创建一个新的js文件用于处理侧边栏内部的关闭按钮事件

+ 8 - 0
js/sidebar.js

@@ -7,3 +7,11 @@ document.addEventListener("DOMContentLoaded", function () {
       window.parent.postMessage({ action: "closeSidebar" }, "*");
     });
 });
+
+window.addEventListener("unload", async () => {
+  try {
+    await Utils.setStorageData(SidebarManager.CONFIG.STORAGE_KEY, false);
+  } catch (error) {
+    console.warn("Failed to update sidebar state:", error);
+  }
+});

+ 20 - 8
js/utils.js

@@ -27,13 +27,19 @@ class Utils {
    */
   static async getStorageData(key) {
     try {
-      return new Promise((resolve) => {
-        chrome.storage.local.get([key], (result) => {
-          resolve(result[key]);
+      const result = await new Promise((resolve) => {
+        chrome.storage.local.get(key, (data) => {
+          if (chrome.runtime.lastError) {
+            console.warn("Storage access error:", chrome.runtime.lastError);
+            resolve(null);
+            return;
+          }
+          resolve(data[key]);
         });
       });
+      return result;
     } catch (error) {
-      console.error("Storage access error:", error);
+      console.warn("Storage access error:", error);
       return null;
     }
   }
@@ -46,12 +52,18 @@ class Utils {
    */
   static async setStorageData(key, value) {
     try {
-      return new Promise((resolve) => {
-        chrome.storage.local.set({ [key]: value }, resolve);
+      await new Promise((resolve) => {
+        chrome.storage.local.set({ [key]: value }, () => {
+          if (chrome.runtime.lastError) {
+            console.warn("Storage access error:", chrome.runtime.lastError);
+            resolve();
+            return;
+          }
+          resolve();
+        });
       });
     } catch (error) {
-      console.error("Storage write error:", error);
-      return false;
+      console.warn("Storage access error:", error);
     }
   }
 }