Skip to content

Commit 7e1df15

Browse files
committed
feat(popup): add window pin toggle, fix shortcut issues, and improve code maintainability
Add window pin toggle to control auto-close behavior Introduce PopupStateManager singleton for centralized state management Fix double-click shortcut issue after window close Adjust tooltip styles to match Sequoia theme Fix popup closing behavior when clicking inside Ensure quick button removal when opening popup via shortcut Improve code organization, error handling, and event handling logic for better maintainability
1 parent 94889df commit 7e1df15

11 files changed

+383
-118
lines changed

.DS_Store

0 Bytes
Binary file not shown.

src/.DS_Store

0 Bytes
Binary file not shown.

src/content/components/DragHandle.js

+3-29
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function resizeMoveListener(event) {
5555
}
5656
}
5757

58-
export function createDragHandle() {
58+
export function createDragHandle(removeCallback) {
5959
const dragHandle = document.createElement("div");
6060
Object.assign(dragHandle.style, {
6161
position: "absolute",
@@ -132,34 +132,8 @@ export function createDragHandle() {
132132

133133
closeButton.addEventListener("click", (event) => {
134134
event.stopPropagation();
135-
const popup = document.querySelector("#ai-popup");
136-
if (popup) {
137-
// 移除主题监听器
138-
if (popup._removeThemeListener) {
139-
popup._removeThemeListener();
140-
}
141-
142-
// 清理滚动条实例
143-
if (window.aiResponseContainer?.perfectScrollbar) {
144-
window.aiResponseContainer.perfectScrollbar.destroy();
145-
delete window.aiResponseContainer.perfectScrollbar;
146-
}
147-
148-
// 清理滚动状态管理器
149-
if (window.aiResponseContainer?.scrollStateManager?.cleanup) {
150-
window.aiResponseContainer.scrollStateManager.cleanup();
151-
}
152-
153-
// 移除事件监听器
154-
if (window.aiResponseContainer?.cleanup) {
155-
window.aiResponseContainer.cleanup();
156-
}
157-
158-
// 移除弹窗
159-
document.body.removeChild(popup);
160-
161-
// 清理全局引用
162-
window.aiResponseContainer = null;
135+
if (typeof removeCallback === 'function') {
136+
removeCallback();
163137
}
164138
});
165139

src/content/content.js

+37-18
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import './styles/style.css';
22
import { createSvgIcon, createIcon } from "./components/IconManager";
33
import { createPopup } from "./popup";
44
import "perfect-scrollbar/css/perfect-scrollbar.css";
5+
import { popupStateManager } from './utils/popupStateManager';
56

67
let currentIcon = null;
7-
let isCreatingPopup = false;
88
let isHandlingIconClick = false;
99
let isSelectionEnabled = true; // 默认启用
1010
let selectedText = "";
1111
let currentPopup = null; // 新增:跟踪当前弹窗
1212
let isRememberWindowSize = false; // 默认不记住窗口大小
13-
let isPopupVisible = false;
1413

1514
const link = document.createElement("link");
1615
link.rel = "stylesheet";
@@ -52,10 +51,10 @@ function removeIcon() {
5251

5352
// 更新安全的弹窗移除函数
5453
function safeRemovePopup() {
54+
// 立即重置所有状态
55+
popupStateManager.reset();
56+
5557
if (!currentPopup) {
56-
// 即使没有当前弹窗,也要确保状态被重置
57-
isPopupVisible = false;
58-
isCreatingPopup = false;
5958
window.aiResponseContainer = null;
6059
return;
6160
}
@@ -71,12 +70,15 @@ function safeRemovePopup() {
7170
// 清理所有观察者和事件监听器
7271
if (currentPopup._resizeObserver) {
7372
currentPopup._resizeObserver.disconnect();
73+
delete currentPopup._resizeObserver;
7474
}
7575
if (currentPopup._mutationObserver) {
7676
currentPopup._mutationObserver.disconnect();
77+
delete currentPopup._mutationObserver;
7778
}
7879
if (currentPopup._removeThemeListener) {
7980
currentPopup._removeThemeListener();
81+
delete currentPopup._removeThemeListener;
8082
}
8183

8284
// 清理滚动相关实例
@@ -86,9 +88,11 @@ function safeRemovePopup() {
8688
}
8789
if (window.aiResponseContainer?.scrollStateManager?.cleanup) {
8890
window.aiResponseContainer.scrollStateManager.cleanup();
91+
delete window.aiResponseContainer.scrollStateManager;
8992
}
9093
if (window.aiResponseContainer?.cleanup) {
9194
window.aiResponseContainer.cleanup();
95+
delete window.aiResponseContainer.cleanup;
9296
}
9397

9498
// 使用 try-catch 包装 DOM 操作
@@ -103,8 +107,6 @@ function safeRemovePopup() {
103107
// 确保状态被重置
104108
window.aiResponseContainer = null;
105109
currentPopup = null;
106-
isPopupVisible = false;
107-
isCreatingPopup = false;
108110
} catch (error) {
109111
console.warn('Failed to remove popup:', error);
110112
// 确保在出错时也能重置所有状态
@@ -115,21 +117,28 @@ function safeRemovePopup() {
115117
console.warn('Error removing popup in catch block:', e);
116118
}
117119
}
120+
// 重置所有状态
118121
window.aiResponseContainer = null;
119122
currentPopup = null;
120-
isPopupVisible = false;
121-
isCreatingPopup = false;
122123
}
124+
125+
// 最后再次确保所有状态都被重置
126+
popupStateManager.reset();
123127
}
124128

125129
function handlePopupCreation(selectedText, rect, hideQuestion = false) {
126-
if (isCreatingPopup) return;
130+
if (popupStateManager.isCreating()) return;
127131

128-
isCreatingPopup = true;
132+
popupStateManager.setCreating(true);
129133

130134
try {
135+
// 先移除快捷按钮
136+
removeIcon();
137+
// 清除选中的文本
138+
window.getSelection().removeAllRanges();
139+
131140
safeRemovePopup();
132-
currentPopup = createPopup(selectedText, rect, hideQuestion);
141+
currentPopup = createPopup(selectedText, rect, hideQuestion, safeRemovePopup);
133142
currentPopup.style.minWidth = '300px';
134143
currentPopup.style.minHeight = '200px';
135144

@@ -148,7 +157,7 @@ function handlePopupCreation(selectedText, rect, hideQuestion = false) {
148157
}
149158

150159
document.body.appendChild(currentPopup);
151-
isPopupVisible = true; // 更新状态
160+
popupStateManager.setVisible(true); // 更新状态
152161

153162
// 设置窗口大小监听
154163
if (isRememberWindowSize && currentPopup) {
@@ -159,25 +168,35 @@ function handlePopupCreation(selectedText, rect, hideQuestion = false) {
159168
safeRemovePopup();
160169
} finally {
161170
setTimeout(() => {
162-
isCreatingPopup = false;
171+
popupStateManager.setCreating(false);
163172
}, 100);
164173
}
165174
}
166175

167-
// 添加切换窗口显示状态的函数
168176
function togglePopup(selectedText, rect, hideQuestion = false) {
177+
// 如果正在处理中,直接返回
178+
if (popupStateManager.isCreating()) return;
179+
169180
try {
170-
if (isPopupVisible) {
181+
if (popupStateManager.isVisible()) {
171182
safeRemovePopup();
183+
// 添加一个短暂的延迟,确保状态完全重置
184+
setTimeout(() => {
185+
popupStateManager.reset();
186+
}, 100);
172187
} else {
173188
// 在创建新弹窗前确保清理旧的状态
174189
safeRemovePopup();
175-
handlePopupCreation(selectedText, rect, hideQuestion);
190+
// 添加一个短暂的延迟,确保旧状态完全清理
191+
setTimeout(() => {
192+
handlePopupCreation(selectedText, rect, hideQuestion);
193+
}, 100);
176194
}
177195
} catch (error) {
178196
console.warn('Error in togglePopup:', error);
179197
// 确保在出错时重置状态
180198
safeRemovePopup();
199+
popupStateManager.reset();
181200
}
182201
}
183202

@@ -237,7 +256,7 @@ function handleIconClick(e, selectedText, rect, selection) {
237256
}
238257

239258
document.addEventListener("mouseup", function (event) {
240-
if (!isSelectionEnabled || isCreatingPopup || isHandlingIconClick) return;
259+
if (!isSelectionEnabled || popupStateManager.isCreating() || isHandlingIconClick) return;
241260

242261
const selection = window.getSelection();
243262
const selectedText = selection.toString().trim();

src/content/popup.js

+25-53
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { addIconsToElement, updateLastAnswerIcons } from "./components/IconManag
88
import { createScrollManager, getAllowAutoScroll, setAllowAutoScroll, updateAllowAutoScroll, handleUserScroll, setupScrollHandlers, scrollToBottom } from './utils/scrollManager';
99
import { isDarkMode, watchThemeChanges, applyTheme } from './utils/themeManager';
1010
import { STYLE_CONSTANTS } from './utils/constants';
11+
import { popupStateManager } from './utils/popupStateManager';
1112

1213
// 将aiResponseContainer移动到window对象上
1314
window.aiResponseContainer = null;
@@ -71,7 +72,13 @@ const getPopupInitialStyle = (rect) => ({
7172
...adjustPopupPosition(rect)
7273
});
7374

74-
export function createPopup(text, rect, hideQuestion = false) {
75+
export function createPopup(text, rect, hideQuestion = false, removeCallback) {
76+
// 确保移除快捷按钮
77+
if (window.currentIcon && document.body.contains(window.currentIcon)) {
78+
document.body.removeChild(window.currentIcon);
79+
window.currentIcon = null;
80+
}
81+
7582
const popup = document.createElement("div");
7683
popup.id = "ai-popup";
7784
popup.classList.add('theme-adaptive');
@@ -81,6 +88,22 @@ export function createPopup(text, rect, hideQuestion = false) {
8188

8289
Object.assign(popup.style, getPopupInitialStyle(rect));
8390

91+
// 添加点击事件监听
92+
document.addEventListener('mousedown', async (event) => {
93+
// 检查是否启用了固定窗口
94+
const isPinned = await chrome.storage.sync.get('pinWindow').then(result => result.pinWindow || false);
95+
96+
// 如果启用了固定窗口,或者点击的是弹窗内部,则不关闭
97+
if (isPinned || event.target.closest('#ai-popup')) {
98+
return;
99+
}
100+
101+
// 关闭弹窗
102+
if (typeof removeCallback === 'function') {
103+
removeCallback();
104+
}
105+
});
106+
84107
const aiResponseElement = document.createElement("div");
85108
window.aiResponseContainer = document.createElement("div");
86109
styleResponseContainer(window.aiResponseContainer);
@@ -158,62 +181,11 @@ export function createPopup(text, rect, hideQuestion = false) {
158181
window.aiResponseContainer
159182
);
160183

161-
const dragHandle = createDragHandle();
184+
const dragHandle = createDragHandle(removeCallback);
162185
popup.appendChild(dragHandle);
163186

164187
setupInteractions(popup, dragHandle, window.aiResponseContainer);
165188

166-
// 设置关闭按钮的处理逻辑
167-
const closeButton = popup.querySelector('.close-button');
168-
if (closeButton) {
169-
closeButton.onclick = async (event) => {
170-
event.preventDefault();
171-
event.stopPropagation();
172-
173-
try {
174-
// 移除主题监听器
175-
if (popup._removeThemeListener) {
176-
popup._removeThemeListener();
177-
}
178-
179-
// 清理滚动条实例
180-
if (window.aiResponseContainer?.perfectScrollbar) {
181-
window.aiResponseContainer.perfectScrollbar.destroy();
182-
delete window.aiResponseContainer.perfectScrollbar;
183-
}
184-
185-
// 清理滚动状态管理器
186-
if (window.aiResponseContainer?.scrollStateManager?.cleanup) {
187-
window.aiResponseContainer.scrollStateManager.cleanup();
188-
}
189-
190-
// 移除事件监听器
191-
if (window.aiResponseContainer?.cleanup) {
192-
window.aiResponseContainer.cleanup();
193-
}
194-
195-
// 确保popup还存在于文档中
196-
if (document.body.contains(popup)) {
197-
// 使用 requestAnimationFrame 确保在下一帧执行移除操作
198-
requestAnimationFrame(() => {
199-
if (document.body.contains(popup)) {
200-
document.body.removeChild(popup);
201-
}
202-
// 清理全局引用
203-
window.aiResponseContainer = null;
204-
});
205-
}
206-
} catch (error) {
207-
console.warn('Error during popup cleanup:', error);
208-
// 如果出错,仍然尝试移除popup
209-
if (document.body.contains(popup)) {
210-
document.body.removeChild(popup);
211-
}
212-
window.aiResponseContainer = null;
213-
}
214-
};
215-
}
216-
217189
const questionInputContainer = createQuestionInputContainer(window.aiResponseContainer);
218190
popup.appendChild(questionInputContainer);
219191

0 commit comments

Comments
 (0)