fix popup options.html context

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent 7fe689202f
commit debc231e9f
5 changed files with 303 additions and 116 deletions
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

+9 -7
View File
@@ -39,18 +39,20 @@
"tabs" "tabs"
], ],
"host_permissions": [ "host_permissions": [
"<all_urls>", "<all_urls>"
"*://mitm/*"
], ],
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": [ "resources": [
"types/*.js", "images/*",
"proxy/*.js", "proxy/*"
"socket.js", ],
"proxy.js", "matches": ["<all_urls>"]
"proxy/content.js" },
{
"resources": [
"/images/yak.svg"
], ],
"matches": ["<all_urls>"] "matches": ["<all_urls>"]
} }
+80 -17
View File
@@ -9,6 +9,9 @@ const ProxyActionType = {
UPDATE_PROXY_CONFIG: 'UPDATE_PROXY_CONFIG' UPDATE_PROXY_CONFIG: 'UPDATE_PROXY_CONFIG'
}; };
// 在文件顶部添加常量声明
const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg');
// 添加一个通用的消息发送函数 // 添加一个通用的消息发送函数
async function sendMessageWithRetry(message, maxRetries = 3) { async function sendMessageWithRetry(message, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) { for (let i = 0; i < maxRetries; i++) {
@@ -139,26 +142,39 @@ async function updatePanel() {
// 修改这里的判断逻辑 // 修改这里的判断逻辑
let html = ` let html = `
<div class="proxy-item ${currentProxy.proxy === '' ? 'active' : ''}" data-id="direct"> <div class="proxy-item ${!currentProxy.enable && currentProxy.proxy === '' ? 'active' : ''}"
<span style="color: ${currentProxy.proxy === '' ? '#ff6b00' : '#666'}">🔴</span> data-id="direct"
title="直接连接">
<span style="color: ${!currentProxy.enable && currentProxy.proxy === '' ? '#ff6b00' : '#666'}">🟢</span>
<span>[直接连接]</span> <span>[直接连接]</span>
<img src="${YAK_ICON_URL}" class="watermark-icon" alt="" />
</div> </div>
<div class="proxy-item ${currentProxy.proxy === 'system' ? 'active' : ''}" data-id="system"> <div class="proxy-item ${currentProxy.proxy === 'system' ? 'active' : ''}"
data-id="system"
title="系统代理">
<span style="color: ${currentProxy.proxy === 'system' ? '#ff6b00' : '#666'}">⚙️</span> <span style="color: ${currentProxy.proxy === 'system' ? '#ff6b00' : '#666'}">⚙️</span>
<span>[系统代理]</span> <span>[系统代理]</span>
<img src="${YAK_ICON_URL}" class="watermark-icon" alt="" />
</div> </div>
<div class="divider"></div>
`; `;
// 添加自定义代理配置 // 修改自定义代理配置的判断逻辑
configs.forEach(config => { configs.forEach(config => {
if (config.id !== 'direct' && config.id !== 'system') { if (config.id !== 'direct' && config.id !== 'system') {
// 检查当前代理是否与配置匹配 const proxyUrl = `${config.scheme}://${config.host}:${config.port}`;
const isActive = currentProxy.enable && const isActive = currentProxy.enable && currentProxy.proxy === proxyUrl && config.enabled;
currentProxy.proxy === `${config.scheme}://${config.host}:${config.port}`;
// 添加 title 属性显示详细信息
const tooltipText = `${config.scheme.toUpperCase()} ${config.host}:${config.port}`;
html += ` html += `
<div class="proxy-item ${isActive ? 'active' : ''}" data-id="${config.id}"> <div class="proxy-item ${isActive ? 'active' : ''}"
data-id="${config.id}"
title="${tooltipText}">
<span style="color: ${isActive ? '#ff6b00' : '#666'}">🌐</span> <span style="color: ${isActive ? '#ff6b00' : '#666'}">🌐</span>
<span>${config.name}</span> <span>${config.name}</span>
<img src="${YAK_ICON_URL}" class="watermark-icon" alt="" />
</div> </div>
`; `;
} }
@@ -247,7 +263,7 @@ function createFloatingPanel() {
position: fixed; position: fixed;
top: 20px; top: 20px;
right: 0; right: 0;
width: 200px; width: 180px;
background: white; background: white;
border-radius: 8px 0 0 8px; border-radius: 8px 0 0 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 2px 10px rgba(0,0,0,0.1);
@@ -299,21 +315,41 @@ function createFloatingPanel() {
} }
.panel-content { .panel-content {
padding: 12px; padding: 4px;
} }
.proxy-item { .proxy-item {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px 12px; padding: 4px 10px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
color: #666; color: #666;
border-left: 3px solid transparent; border-left: 3px solid transparent;
overflow: hidden;
} }
.proxy-item:hover { .proxy-item > span {
background: #f5f5f5; position: relative;
z-index: 1;
}
.watermark-icon {
position: absolute;
right: 0;
width: 100%;
height: 100%;
opacity: 0.3;
pointer-events: none;
display: none;
object-fit: contain;
object-position: right center;
padding: 4px;
}
.proxy-item.active .watermark-icon {
display: block;
} }
.proxy-item.active { .proxy-item.active {
@@ -333,6 +369,10 @@ function createFloatingPanel() {
transition: color 0.2s; transition: color 0.2s;
} }
.proxy-item:hover {
background: #f5f5f5;
}
.proxy-item:hover span:first-child { .proxy-item:hover span:first-child {
color: #ff6b00; color: #ff6b00;
} }
@@ -340,7 +380,7 @@ function createFloatingPanel() {
.add-proxy { .add-proxy {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 8px 12px; padding: 4px 10px;
color: #1890ff; color: #1890ff;
cursor: pointer; cursor: pointer;
border-top: 1px solid #eee; border-top: 1px solid #eee;
@@ -352,7 +392,7 @@ function createFloatingPanel() {
} }
.settings { .settings {
padding: 8px 12px; padding: 4px 10px;
color: #666; color: #666;
cursor: pointer; cursor: pointer;
border-top: 1px solid #eee; border-top: 1px solid #eee;
@@ -362,6 +402,25 @@ function createFloatingPanel() {
.settings:hover { .settings:hover {
background: #f5f5f5; background: #f5f5f5;
} }
.panel-header .header-content {
display: flex;
align-items: center;
gap: 8px;
padding: 0 4px;
}
.yak-icon {
width: 24px;
height: 24px;
object-fit: contain;
}
.divider {
height: 1px;
background-color: #eee;
margin: 4px 0;
}
`; `;
// 创建面板内容 // 创建面板内容
@@ -370,20 +429,24 @@ function createFloatingPanel() {
panel.innerHTML = ` panel.innerHTML = `
<div class="collapse-trigger"></div> <div class="collapse-trigger"></div>
<div class="panel-header"> <div class="panel-header">
<div class="header-content">
<img src="${YAK_ICON_URL}" class="yak-icon" alt="Yak" />
<span>代理设置</span> <span>代理设置</span>
</div> </div>
</div>
<div class="panel-content"> <div class="panel-content">
<div class="proxy-item active"> <div class="proxy-item active">
<span>🔴</span> <span>🟢</span>
<span>[直接连接]</span> <span>[直接连接]</span>
</div> </div>
<div class="proxy-item"> <div class="proxy-item">
<span>⚙️</span> <span>⚙️</span>
<span>[系统代理]</span> <span>[系统代理]</span>
</div> </div>
<div class="divider"></div>
<div class="add-proxy"> <div class="add-proxy">
<span>➕</span> <span>➕</span>
<span>添加代理...</span> <span>[添加代理...]</span>
</div> </div>
<div class="settings"> <div class="settings">
<span>👨‍💻</span> <span>👨‍💻</span>
+32
View File
@@ -201,3 +201,35 @@ body {
.ant-menu-item .menu-icon { .ant-menu-item .menu-icon {
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.proxy-switch-container {
position: relative;
width: 180px;
overflow: hidden;
}
.panel-watermark {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0.03;
pointer-events: none;
object-fit: contain;
object-position: right bottom;
z-index: 0;
}
/* 确保菜单项在水印上层 */
.ant-menu-item {
position: relative;
z-index: 1;
background: transparent !important;
}
/* 确保分割线在水印上层 */
.ant-menu-item-divider {
position: relative;
z-index: 1;
}
+150 -70
View File
@@ -6,6 +6,9 @@ import "./index.css";
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import type { ProxyConfig } from '@/types/proxy'; import type { ProxyConfig } from '@/types/proxy';
// 添加 YAK 图标 URL 常量
const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg');
// 固定的代理模式 // 固定的代理模式
const FIXED_MODES = [ const FIXED_MODES = [
{ {
@@ -48,18 +51,21 @@ interface ProxySwitchProps {
onProxyChange: (config: ProxyConfig) => void; onProxyChange: (config: ProxyConfig) => void;
} }
export const ProxySwitch: React.FC<ProxySwitchProps> = ({ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
proxyConfigs, const [initialized, setInitialized] = useState<boolean>(false);
currentProxy, const [currentMode, setCurrentMode] = useState<string>('');
onProxyChange,
}) => {
const [currentMode, setCurrentMode] = useState<string>('direct');
const [customProxies, setCustomProxies] = useState<CustomProxy[]>([]); const [customProxies, setCustomProxies] = useState<CustomProxy[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
loadProxyStatus(); const init = async () => {
loadCustomProxies(); await Promise.all([
loadProxyStatus(),
loadCustomProxies()
]);
setInitialized(true);
};
init();
}, []); }, []);
const loadProxyStatus = async () => { const loadProxyStatus = async () => {
@@ -68,26 +74,36 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = ({
action: ProxyActionType.GET_PROXY_STATUS action: ProxyActionType.GET_PROXY_STATUS
}); });
if (!response) {
console.log('No response from background script');
return;
}
if (response.success) { if (response.success) {
const activeMode = response.data.mode; const activeMode = response.data.mode;
setCurrentMode(activeMode);
if (FIXED_MODES.some(mode => mode.key === activeMode)) { if (FIXED_MODES.some(mode => mode.key === activeMode)) {
setCurrentMode(activeMode); setCurrentMode(activeMode);
} }
} }
} catch (error) { } catch (error) {
console.error('Error loading proxy status:', error); console.error('Error loading proxy status:', error);
setCurrentMode('direct');
} }
}; };
const loadCustomProxies = async () => { const loadCustomProxies = async () => {
try { try {
// 首先检查当前是否在 options 页面的上下文中
const currentUrl = window.location.href;
const isInOptionsContext = currentUrl.includes('chrome-extension://') && currentUrl.includes('options.html');
if (isInOptionsContext) {
// 如果在 options 页面上下文中,直接使用消息通信
const response = await chrome.runtime.sendMessage({ const response = await chrome.runtime.sendMessage({
action: ProxyActionType.GET_PROXY_CONFIGS action: ProxyActionType.GET_PROXY_CONFIGS
}); });
if (response.success && response.data) { if (response?.success && response.data) {
const proxies = response.data const proxies = response.data
.filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id)) .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id))
.map((proxy: ProxyConfig): CustomProxy => ({ .map((proxy: ProxyConfig): CustomProxy => ({
@@ -99,24 +115,113 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = ({
})); }));
setCustomProxies(proxies); setCustomProxies(proxies);
const enabledProxy = proxies.find((proxy: ProxyConfig) => proxy.enabled); const enabledProxy = response.data.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) { if (enabledProxy) {
setCurrentMode(enabledProxy.key); setCurrentMode(enabledProxy.id);
} else {
setCurrentMode('direct');
}
return;
} }
} }
// 如果不在 options 页面上下文中,使用 IndexedDB
const DB_NAME = 'yaklang_extension';
const STORE_NAME = 'proxy_configs';
// 打开数据库
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
// 从数据库读取代理配置
const configs = await new Promise<ProxyConfig[]>((resolve, reject) => {
try {
const transaction = db.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.getAll();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result || []);
} catch (error) {
reject(error);
}
});
// 处理代理配置
const proxies = configs
.filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id))
.map((proxy: ProxyConfig): CustomProxy => ({
key: proxy.id,
name: proxy.name,
color: '#1890ff',
config: proxy,
enabled: proxy.enabled
}));
setCustomProxies(proxies);
const enabledProxy = configs.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) {
setCurrentMode(enabledProxy.id);
} else {
setCurrentMode('direct');
}
} catch (error) { } catch (error) {
console.error('Error loading custom proxies:', error); console.error('Error loading custom proxies:', error);
setCustomProxies([]);
setCurrentMode('direct');
} }
}; };
const handleApplyConfig = async (mode: string) => { const handleModeChange = async (mode: string) => {
if (mode === 'setting') {
await chrome.runtime.openOptionsPage?.();
return;
}
if (mode === 'add') {
try {
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
if (activeTab?.url === optionsUrl) {
chrome.tabs.sendMessage(activeTab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
} else {
const tab = await chrome.tabs.create({
url: optionsUrl
});
const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
if (tabId === tab.id && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(tab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
}
};
chrome.tabs.onUpdated.addListener(listener);
}
} catch (error) {
console.error('Failed to get current tab:', error);
}
return;
}
try { try {
setIsLoading(true); setIsLoading(true);
const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode); const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode);
const customProxy = customProxies.find(proxy => proxy.key === mode); const customProxy = customProxies.find(proxy => proxy.key === mode);
const config = fixedMode?.config || customProxy?.config; const config = fixedMode?.config || customProxy?.config;
if (!config) { if (!config) {
console.error('No config found for mode:', mode); console.error('No config found for mode:', mode);
return; return;
@@ -149,63 +254,13 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = ({
} }
}; };
const handleModeChange = async (mode: string) => {
if (mode === 'setting') {
await chrome.runtime.openOptionsPage?.();
return;
}
if (mode === 'add') {
try {
// 获取当前活动标签页
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
if (activeTab?.url === optionsUrl) {
// 如果当前就在 options 页面,直接发消息触发添加代理
chrome.tabs.sendMessage(activeTab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
} else {
// 如果不在 options 页面,创建新的
const tab = await chrome.tabs.create({
url: optionsUrl
});
// 等待页面加载完成
const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
if (tabId === tab.id && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(tab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
}
};
chrome.tabs.onUpdated.addListener(listener);
}
} catch (error) {
console.error('Failed to get current tab:', error);
}
return;
}
try {
await handleApplyConfig(mode);
} catch (error) {
console.error('Failed to change proxy mode:', error);
}
};
const menuItems: MenuProps['items'] = [ const menuItems: MenuProps['items'] = [
...FIXED_MODES.map(mode => ({ ...FIXED_MODES.map(mode => ({
key: mode.key, key: mode.key,
icon: <span className="menu-icon" style={{ color: mode.color }}>{mode.icon}</span>, icon: <span className="menu-icon" style={{ color: mode.color }}>{mode.icon}</span>,
label: mode.name, label: mode.name,
className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
title: mode.name.replace(/[\[\]]/g, '')
})), })),
{ type: 'divider' }, { type: 'divider' },
...customProxies.map(proxy => ({ ...customProxies.map(proxy => ({
@@ -215,7 +270,8 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = ({
color: currentMode === proxy.key ? 'var(--yakit-primary)' : 'inherit', color: currentMode === proxy.key ? 'var(--yakit-primary)' : 'inherit',
opacity: isLoading ? 0.7 : 1 opacity: isLoading ? 0.7 : 1
}}>{proxy.name}</span>, }}>{proxy.name}</span>,
className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
title: `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
})), })),
{ {
key: 'add', key: 'add',
@@ -231,13 +287,37 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = ({
} }
]; ];
return ( return initialized ? (
<div className="proxy-switch-container" style={{ position: 'relative' }}>
<img
src={YAK_ICON_URL}
className="panel-watermark"
alt=""
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
height: '100%',
opacity: 0.1,
backgroundColor: '#fff7e6',
pointerEvents: 'none',
objectFit: 'contain',
objectPosition: 'right bottom',
zIndex: 0
}}
/>
<Menu <Menu
items={menuItems} items={menuItems}
selectedKeys={[currentMode]} selectedKeys={[currentMode]}
onClick={({ key }) => !isLoading && handleModeChange(key)} onClick={({ key }) => !isLoading && handleModeChange(key)}
style={{ width: 180 }} style={{ width: 180, position: 'relative', zIndex: 1, background: 'transparent' }}
className={isLoading ? 'menu-loading' : ''} className={isLoading ? 'menu-loading' : ''}
/> />
</div>
) : (
<div style={{ width: 180, height: 100, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<span>加载中...</span>
</div>
); );
}; };