import { useState, useEffect, useCallback, Fragment } from 'react'; import { Box, TextField, Alert, List, ListItem, IconButton, Typography, Divider, Container, Stack, alpha, Theme, 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 { useRouter } from '@/providers/RouterProvider'; import type { OpenUrlPreferences, OpenUrlEntry } from '@/types/storage'; const THEME_COLOR = '#9c27b0'; const INPUT_STYLE = { '& .MuiOutlinedInput-root': { bgcolor: 'background.paper', borderRadius: 3.5, border: '1px solid', borderColor: 'grey.100', transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)', '& fieldset': { border: 'none' }, '&:hover': { borderColor: 'grey.300', bgcolor: 'grey.50' }, '&.Mui-focused': { bgcolor: '#fff', borderColor: THEME_COLOR, boxShadow: (_theme: Theme) => `0 0 0 4px ${alpha(THEME_COLOR, 0.1)}`, }, }, '& .MuiInputBase-input': { py: 1.2, px: 2, fontSize: '0.85rem', fontWeight: 600, }, '& .MuiInputLabel-root': { fontSize: '0.85rem', fontWeight: 700, color: 'text.secondary', mb: 0.5, '&.Mui-focused': { color: THEME_COLOR }, }, }; 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 { syncNavigation } = useRouter(); 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 { await storageUtil.set('openUrl/currentUrl', entry.url); syncNavigation('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 实验室 多环境跳转与安全性预检 {/* Form Section */} setNewName(e.target.value)} fullWidth variant="outlined" sx={INPUT_STYLE} InputLabelProps={{ shrink: true }} /> setNewUrl(e.target.value)} fullWidth variant="outlined" sx={INPUT_STYLE} InputLabelProps={{ 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 && } ))} )} ); }