import { useState, useEffect, useCallback, Fragment } from 'react'; import { Box, TextField, Alert, List, ListItem, IconButton, Typography, Divider, Container, Stack, alpha, Tooltip, } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import VisibilityIcon from '@mui/icons-material/Visibility'; import AddIcon from '@mui/icons-material/Add'; import LanguageIcon from '@mui/icons-material/Language'; import LinkIcon from '@mui/icons-material/Link'; import Button from '@/components/Button'; import GlobalSnackbar, { useSnackbar } from '@/components/GlobalSnackbar'; import { storageUtil } from '@/utils/chromeStorage'; import type { OpenUrlPreferences, OpenUrlEntry } from '@/types/storage'; import { openUrlPageStyles, dashboardPageStyles } from '@/config/pageTheme'; const THEME_COLOR = openUrlPageStyles.themeColor; const DEFAULT_PREFERENCES: OpenUrlPreferences = { entries: [], }; export default function OpenUrlPage() { const [entries, setEntries] = useState(DEFAULT_PREFERENCES.entries); const [newName, setNewName] = useState(''); const [newUrl, setNewUrl] = useState(''); const [isLoaded, setIsLoaded] = useState(false); const { snackbarProps, showMessage } = useSnackbar(); const showMixedContentWarning = newUrl.startsWith('http://') && !newUrl.includes('localhost') && !newUrl.includes('127.0.0.1'); const isValidUrl = (url: string) => { if (!url.trim()) return false; try { new URL(url); return true; } catch { return false; } }; useEffect(() => { const loadPreferences = async () => { try { const saved = await storageUtil.get('openUrl/preferences', DEFAULT_PREFERENCES); if (saved && saved.entries) { setEntries(saved.entries); } } catch (error) { console.error('Failed to load Open Url preferences:', error); } finally { setIsLoaded(true); } }; loadPreferences(); }, []); const savePreferences = useCallback(() => { const preferences: OpenUrlPreferences = { entries }; storageUtil.set('openUrl/preferences', preferences).catch((error) => { console.error('Failed to save Open Url preferences:', error); }); }, [entries]); useEffect(() => { if (!isLoaded) return; const timer = setTimeout(() => { savePreferences(); }, 500); return () => clearTimeout(timer); }, [entries, isLoaded, savePreferences]); const handleAddEntry = () => { if (!newName.trim()) { showMessage('请输入名称', { severity: 'error' }); return; } if (!isValidUrl(newUrl)) { showMessage('请输入有效的 URL', { severity: 'error' }); return; } setEntries([...entries, { name: newName.trim(), url: newUrl.trim() }]); setNewName(''); setNewUrl(''); showMessage('添加成功', { severity: 'success' }); }; const handleDeleteEntry = (index: number) => { const newEntries = [...entries]; newEntries.splice(index, 1); setEntries(newEntries); showMessage('删除成功', { severity: 'success' }); }; const handleOpenInSidebar = async (entry: OpenUrlEntry) => { try { // 存储目标 URL await storageUtil.set('openUrl/currentUrl', entry.url); // 直接设置侧边栏的路由,而不是通过 syncNavigation 影响弹窗路由 await storageUtil.set('app/sidepanelRoute', 'openUrlViewer'); const [currentTab] = await chrome.tabs.query({ active: true, currentWindow: true, }); const tabId = currentTab.id; if (!tabId) { showMessage('无法获取当前标签页', { severity: 'error' }); return; } await chrome.sidePanel.setOptions({ tabId, path: 'sidepanel.html', enabled: true, }); await chrome.sidePanel.open({ windowId: currentTab.windowId }); // 仅当在 Popup 中时才关闭窗口,防止在侧边栏内点击预览时导致侧边栏关闭 if (window.location.pathname.includes('popup.html')) { window.close(); } } catch (error) { console.error('Failed to open side panel:', error); showMessage(`打开失败: ${(error as Error).message}`, { severity: 'error' }); } }; const handleOpenInNewTab = (entry: OpenUrlEntry) => { chrome.tabs.create({ url: entry.url }); window.close(); }; return ( {/* Header */} URL 工具 快速打开 URL 或复制链接 {/* Form Section */} setNewName(e.target.value)} fullWidth variant="outlined" sx={openUrlPageStyles.INPUT_STYLE} slotProps={{ inputLabel: { shrink: true, }, }} /> setNewUrl(e.target.value)} fullWidth variant="outlined" sx={openUrlPageStyles.INPUT_STYLE} slotProps={{ inputLabel: { shrink: true, }, }} /> {showMixedContentWarning && ( 混合内容警告:当前 HTTPS 页面无法加载 HTTP 资源。 )} {/* List Section */} 已保存的快捷方式 ({entries.length}) {entries.length === 0 ? ( 暂无快捷方式,请在上方添加 ) : ( {entries.map((entry, index) => ( {entry.name} {entry.url} handleOpenInSidebar(entry)} sx={{ color: THEME_COLOR, bgcolor: alpha(THEME_COLOR, 0.05), '&:hover': { bgcolor: THEME_COLOR, color: '#fff' }, }} > handleOpenInNewTab(entry)} sx={{ color: 'grey.500', bgcolor: 'grey.100', '&:hover': { bgcolor: 'grey.600', color: '#fff' }, }} > handleDeleteEntry(index)} sx={{ color: 'error.main', '&:hover': { color: 'error.dark', bgcolor: alpha('#f44336', 0.05) }, }} > {index < entries.length - 1 && } ))} )} ); }