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