finish function, with claude and augument

This commit is contained in:
songtianlun 2025-07-23 13:57:36 +08:00
commit d87e02004e
16 changed files with 1536 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
claude

99
INSTALLATION.md Normal file
View File

@ -0,0 +1,99 @@
# Installation Guide / 安装指南
## Prerequisites / 前置要求
- Google Chrome browser / Google Chrome 浏览器
- Developer mode enabled in Chrome extensions / Chrome 扩展开发者模式已启用
## Installation Steps / 安装步骤
### 1. Verify Files / 验证文件
All required files are already included in the project:
- ✅ `manifest.json` - Extension configuration
- ✅ `background.js` - Service worker
- ✅ `content.js` - Main functionality
- ✅ `styles.css` - Styling
- ✅ `popup.html` - Popup interface
- ✅ `icons/icon16.png` - 16x16 icon
- ✅ `icons/icon32.png` - 32x32 icon
- ✅ `icons/icon48.png` - 48x48 icon
- ✅ `icons/icon128.png` - 128x128 icon
### 2. Load Extension / 加载扩展
1. Open Chrome and navigate to `chrome://extensions/` / 打开 Chrome 并访问扩展页面
2. Enable "Developer mode" in the top right corner / 在右上角启用"开发者模式"
3. Click "Load unpacked" button / 点击"加载已解压的扩展程序"按钮
4. Select the `dropdown-copy-helper` directory / 选择项目目录
5. The extension should now appear in your extensions list / 扩展应该出现在扩展列表中
### 3. Verify Installation / 验证安装
1. Check that the extension icon appears in the Chrome toolbar / 检查扩展图标是否出现在工具栏中
2. Visit google.com or youtube.com / 访问 google.com 或 youtube.com
3. The extension should be active on these sites / 扩展应该在这些网站上激活
## Testing / 测试
### Test on Google Search / 在 Google 搜索上测试
1. Go to https://www.google.com / 访问 Google 搜索
2. Click on the search input field / 点击搜索输入框
3. Type a few characters to trigger search suggestions / 输入几个字符触发搜索建议
4. Right-click on the search input field / 在搜索输入框上右键点击
5. Select "Copy All Dropdown Items / 复制所有下拉项" from the context menu / 从右键菜单选择复制选项
6. Check if a success toast notification appears / 检查是否出现成功提示
7. Paste (Ctrl+V) to verify the copied content / 粘贴验证复制的内容
### Test on YouTube / 在 YouTube 上测试
1. Go to https://www.youtube.com / 访问 YouTube
2. Click on the search input field / 点击搜索输入框
3. Type a few characters to trigger search suggestions / 输入几个字符触发搜索建议
4. Right-click on the search input field / 在搜索输入框上右键点击
5. Select "Copy All Dropdown Items / 复制所有下拉项" from the context menu / 从右键菜单选择复制选项
6. Check if a success toast notification appears / 检查是否出现成功提示
7. Paste (Ctrl+V) to verify the copied content / 粘贴验证复制的内容
## Troubleshooting / 故障排除
### Extension not loading / 扩展无法加载
- Make sure all required files are present / 确保所有必需文件都存在
- Check that icon files are in the `icons/` directory / 检查图标文件是否在 `icons/` 目录中
- Verify manifest.json syntax is correct / 验证 manifest.json 语法正确
### Context menu not appearing / 右键菜单不出现
- Make sure you're on a supported website (Google or YouTube) / 确保在支持的网站上
- Try refreshing the page / 尝试刷新页面
- Check that you're right-clicking on the correct input field / 检查是否在正确的输入框上右键点击
### "No input element found" error / "未找到输入元素"错误
- **First, click on the search input field** to focus it / 首先点击搜索输入框以聚焦
- Try typing something in the search box / 尝试在搜索框中输入内容
- Open browser console (F12) to see debug messages / 打开浏览器控制台查看调试信息
- Use the test page `input-detection-test.html` to verify input detection / 使用测试页面验证输入检测
### Copy function not working / 复制功能不工作
- Check browser console for error messages / 检查浏览器控制台的错误信息
- Make sure clipboard permissions are granted / 确保剪贴板权限已授予
- Try typing in the input field first to trigger suggestions / 先在输入框中输入以触发建议
### No dropdown suggestions / 没有下拉建议
- Type more characters in the search field / 在搜索框中输入更多字符
- Wait a moment for suggestions to load / 等待建议加载
- Check your internet connection / 检查网络连接
### Debug Steps / 调试步骤
1. Open `input-detection-test.html` to test input detection / 打开测试页面检测输入框识别
2. Open browser console (F12) and look for extension messages / 打开控制台查看扩展消息
3. Check if the extension content script is loaded / 检查内容脚本是否加载
4. Verify that the search input is being detected / 验证搜索输入框是否被检测到
## Development / 开发
To modify the extension:
1. Make changes to the source files / 修改源文件
2. Go to `chrome://extensions/` / 访问扩展页面
3. Click the refresh button on the extension card / 点击扩展卡片上的刷新按钮
4. Test your changes / 测试更改

95
README.md Normal file
View File

@ -0,0 +1,95 @@
# Dropdown Copy Helper / 下拉复制助手
A Chrome extension that helps you copy all dropdown options from input fields on supported websites like Google Search and YouTube.
## ✨ Features / 功能
- **🔍 Smart Detection**: Automatically detects dropdown menus associated with search input fields
- **📋 Right-click Menu**: Adds a context menu option "Copy All Dropdown Items / 复制所有下拉项"
- **📢 Toast Notifications**: Shows success/failure notifications with item count
- **🌐 Multi-site Support**: Currently supports Google Search and YouTube
- **🎯 Precise Targeting**: Only activates on supported websites for better performance
- **🐛 Debug Support**: Comprehensive logging for troubleshooting
## 🌍 Supported Websites / 支持的网站
- **Google Search** (google.com) - Main search suggestions
- **YouTube** (youtube.com) - Video search suggestions
## 📦 Installation / 安装
### Quick Start / 快速开始
1. **Ready to Use / 即开即用**
- All required files including icons are already included / 所有必需文件包括图标都已包含
- No additional setup required / 无需额外设置
2. **Load Extension / 加载扩展**
- Open Chrome and go to `chrome://extensions/` / 打开Chrome扩展页面
- Enable "Developer mode" / 启用开发者模式
- Click "Load unpacked" and select this directory / 加载此目录
3. **Verify Installation / 验证安装**
- Extension icon should appear in Chrome toolbar / 工具栏应显示扩展图标
- Visit google.com or youtube.com to test / 访问支持的网站测试
For detailed installation instructions, see [INSTALLATION.md](INSTALLATION.md)
## 🚀 Usage / 使用方法
1. **Navigate** to Google Search or YouTube / 访问Google搜索或YouTube
2. **Click** on the search input field / 点击搜索输入框
3. **Type** a few characters to trigger dropdown suggestions / 输入字符触发下拉建议
4. **Right-click** on the search input field / 在搜索框上右键点击
5. **Select** "Copy All Dropdown Items / 复制所有下拉项" / 选择复制选项
6. **Success!** All suggestions are copied to clipboard, one per line / 成功复制所有建议到剪贴板
## 🧪 Testing / 测试
Open `test.html` in your browser for a comprehensive testing guide with step-by-step instructions.
## 📁 Project Structure / 项目结构
```
dropdown-copy-helper/
├── manifest.json # Extension configuration / 扩展配置
├── background.js # Service worker for context menus / 后台服务
├── content.js # Main functionality / 主要功能实现
├── styles.css # Toast notification styles / 通知样式
├── popup.html # Extension popup interface / 弹窗界面
├── icons/ # Extension icons / 扩展图标
├── generate-icons.html # Icon generator tool / 图标生成工具
├── test.html # Testing guide / 测试指南
├── INSTALLATION.md # Detailed installation guide / 详细安装指南
└── README.md # This file / 说明文档
```
## 🔧 Development / 开发
### Key Components / 核心组件
- **`manifest.json`**: Defines permissions, content scripts, and extension metadata
- **`background.js`**: Handles context menu creation and clipboard operations
- **`content.js`**: Core functionality for dropdown detection and text extraction
- **`styles.css`**: Styling for toast notifications with responsive design
- **`popup.html`**: User-friendly popup with usage instructions
### Debugging / 调试
The extension includes comprehensive logging. Open browser console (F12) to see:
- Content script loading status
- Input element detection
- Dropdown item discovery
- Copy operation results
## 🤝 Contributing / 贡献
1. Fork the repository / 分叉仓库
2. Create a feature branch / 创建功能分支
3. Make your changes / 进行更改
4. Test thoroughly using `test.html` / 使用测试页面充分测试
5. Submit a pull request / 提交拉取请求
## 📄 License / 许可证
MIT License - see LICENSE file for details

107
background.js Normal file
View File

@ -0,0 +1,107 @@
// Background script for Dropdown Copy Helper
// 下拉复制助手后台脚本
// Context menu item ID
const CONTEXT_MENU_ID = 'copy-dropdown-items';
// Create context menu when extension is installed
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: CONTEXT_MENU_ID,
title: 'Copy All Dropdown Items\n复制所有下拉项',
contexts: ['editable'],
documentUrlPatterns: [
'https://www.google.com/*',
'https://google.com/*',
'https://www.youtube.com/*',
'https://youtube.com/*'
]
});
});
// Handle context menu clicks
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === CONTEXT_MENU_ID) {
// Send message to content script to copy dropdown items
chrome.tabs.sendMessage(tab.id, {
action: 'copyDropdownItems',
frameId: info.frameId
}, (response) => {
// Handle response or connection errors
if (chrome.runtime.lastError) {
console.log('Connection error:', chrome.runtime.lastError.message);
// Try to inject content script and retry
injectContentScriptAndRetry(tab.id);
} else if (response) {
console.log('Copy operation result:', response);
}
});
}
});
// Function to inject content script and retry the operation
function injectContentScriptAndRetry(tabId) {
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content.js']
}, () => {
if (chrome.runtime.lastError) {
console.error('Failed to inject content script:', chrome.runtime.lastError.message);
} else {
// Retry after a short delay
setTimeout(() => {
chrome.tabs.sendMessage(tabId, {
action: 'copyDropdownItems'
}, (response) => {
if (chrome.runtime.lastError) {
console.error('Retry failed:', chrome.runtime.lastError.message);
}
});
}, 500);
}
});
}
// Handle messages from content script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'ping') {
sendResponse({ status: 'ready' });
}
// Note: Clipboard operations are now handled directly in content script
});
// Handle extension startup
chrome.runtime.onStartup.addListener(() => {
console.log('Dropdown Copy Helper extension started');
});
// Handle tab updates to ensure content script is ready
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' && tab.url) {
const supportedSites = [
'https://www.google.com/',
'https://google.com/',
'https://www.youtube.com/',
'https://youtube.com/'
];
const isSupported = supportedSites.some(site => tab.url.startsWith(site));
if (isSupported) {
// Ping to check if content script is ready
chrome.tabs.sendMessage(tabId, { action: 'ping' }, (response) => {
if (chrome.runtime.lastError) {
// Content script not ready, inject it
console.log('Injecting content script for tab:', tabId);
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content.js']
}).catch(error => {
console.error('Failed to inject content script:', error);
});
} else {
console.log('Content script ready for tab:', tabId);
}
});
}
}
});

701
content.js Normal file
View File

@ -0,0 +1,701 @@
// Content script for Dropdown Copy Helper
// 下拉复制助手内容脚本
class DropdownCopyHelper {
constructor() {
this.currentInputElement = null;
this.init();
}
init() {
// Prevent multiple initialization
if (window.dropdownCopyHelperInitialized) {
console.log('⚠️ Dropdown Copy Helper already initialized');
return;
}
window.dropdownCopyHelperInitialized = true;
// Listen for messages from background script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
try {
if (request.action === 'copyDropdownItems') {
this.handleCopyRequest(sendResponse);
return true; // Keep message channel open for async response
} else if (request.action === 'ping') {
sendResponse({ status: 'ready', timestamp: Date.now() });
return false; // Synchronous response
}
} catch (error) {
console.error('❌ Message handling error:', error);
sendResponse({ success: false, error: error.message });
}
});
// Track focused input elements
this.trackInputFocus();
console.log('✅ Dropdown Copy Helper loaded on:', window.location.hostname);
}
// Get dropdown items using simplified strategy
getDropdownItems() {
const hostname = window.location.hostname;
let items = [];
if (hostname.includes('google.com')) {
items = this.getGoogleDropdownItems();
} else if (hostname.includes('youtube.com')) {
items = this.getYouTubeDropdownItems();
}
// If no items found with standard methods, try emergency fallback
if (items.length === 0) {
items = this.getEmergencyFallbackItems();
}
// Filter and clean items
const cleanItems = items
.filter(item => item && item.trim().length > 0)
.filter(item => !this.isUIElement(item))
.map(item => this.cleanSearchSuggestionText(item))
.filter(item => item && item.length > 0 && item.length < 150);
return [...new Set(cleanItems)]; // Remove duplicates
}
// Get Google search suggestions
getGoogleDropdownItems() {
const items = [];
// Comprehensive list of selectors from most modern to legacy
const selectors = [
// Most modern Google selectors (2024+)
'ul[role="listbox"] li[role="option"]',
'div[role="listbox"] div[role="option"]',
'[role="listbox"] [role="option"]',
// Alternative modern structures
'.G43f7e li[role="option"]',
'.G43f7e div[role="option"]',
'.aajZCb li[role="option"]',
'.aajZCb div[role="option"]',
'.erkvQe li[role="option"]',
'.erkvQe li',
'.erkvQe div',
// Google suggestions container variations
'.sbsb_b li',
'.sbsb_b div',
'.sbsb_c',
'.sbqs_c',
'.gsq_a',
'.pcTkSc',
'.sbtc',
'.sbl1',
// Broader searches for any suggestion-like elements
'li[data-ved]',
'div[data-ved]',
'span[data-ved]',
// Last resort - any li under potential containers
'.sbsb_b li',
'ul li',
'div[jsname] li',
'div[jsname] div'
];
for (const selector of selectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
let foundInThisSelector = 0;
elements.forEach(el => {
if (this.isVisible(el)) {
const text = this.extractCleanText(el);
if (text && text.length > 0 && text.length < 200) {
if (this.looksLikeSearchSuggestion(text)) {
items.push(text);
foundInThisSelector++;
}
}
}
});
// If we found items with this selector, stop trying others
if (foundInThisSelector > 0) {
break;
}
}
}
return items;
}
// Check if text looks like a search suggestion rather than UI element
looksLikeSearchSuggestion(text) {
// Reject obviously non-suggestion text
const rejectPatterns = [
/^(搜索|查看更多|删除|更多|清除|历史记录)$/i,
/^(search|view more|delete|more|clear|history)$/i,
/^[×✕]$/,
/^[\s]*$/,
/^[0-9]+$/,
/^(OM|中国国际航空|Airbnb)$/
];
return !rejectPatterns.some(pattern => pattern.test(text));
}
// Extract clean text from element - simplified version
extractCleanText(element) {
if (!element) return '';
// Get the text content
let text = element.textContent || element.innerText || '';
// Basic cleaning
text = text.trim();
// Remove obvious UI elements by splitting and taking the first part
const parts = text.split(/[,,·•\|\n]/);
text = parts[0].trim();
// Remove common trailing UI text
text = text.replace(/(查看更多|删除|更多|OM|View more|Delete|Remove)$/gi, '').trim();
return text;
}
// Check if text appears to be a UI element rather than search suggestion
isUIElement(text) {
const uiPatterns = [
/^查看更多/,
/删除$/,
/^更多$/,
/^搜索$/,
/^OM$/,
/^×$/,
/^✕$/,
/^\s*$/ // empty or whitespace only
];
return uiPatterns.some(pattern => pattern.test(text));
}
// Get YouTube search suggestions
getYouTubeDropdownItems() {
const items = [];
const selectors = [
// Modern YouTube selectors
'[role="listbox"] [role="option"]',
'.ytd-searchbox [role="option"]',
// Traditional YouTube selectors
'.sbsb_c',
'.sbsb_a',
'.sbqs_c',
// Container-based searches
'.ytd-searchbox .sbsb_c',
'#search-container [role="option"]',
'.search-container [role="option"]',
// Broader searches
'li[data-ved]',
'div[data-ved]',
// Last resort
'.ytd-searchbox li',
'.ytd-searchbox div',
'#search li',
'#search div'
];
for (const selector of selectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
let foundInThisSelector = 0;
elements.forEach(el => {
if (this.isVisible(el)) {
const text = this.extractCleanText(el);
if (text && text.length > 0 && text.length < 200) {
if (this.looksLikeSearchSuggestion(text)) {
items.push(text);
foundInThisSelector++;
}
}
}
});
if (foundInThisSelector > 0) {
break;
}
}
}
return items;
}
// Emergency fallback - try to find ANY dropdown-like elements on the page
getEmergencyFallbackItems() {
const items = [];
// Look for any elements that might contain suggestions
const emergencySelectors = [
// Any list items anywhere
'li',
// Any divs with suggestion-like attributes
'div[role]',
'span[role]',
// Elements with data attributes (common in modern web apps)
'[data-ved]',
'[data-value]',
'[data-suggestion]',
// Any elements that might be in a dropdown
'ul > *',
'ol > *',
'.dropdown *',
'.suggestions *',
'.autocomplete *'
];
for (const selector of emergencySelectors) {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
let foundCount = 0;
elements.forEach(el => {
if (foundCount >= 10) return; // Limit to prevent spam
if (this.isVisible(el)) {
const text = this.extractCleanText(el);
if (text && text.length > 2 && text.length < 100) {
if (this.looksLikeSearchSuggestion(text) && !items.includes(text)) {
items.push(text);
foundCount++;
}
}
}
});
if (foundCount > 0) {
break;
}
}
}
return items.slice(0, 10); // Limit to 10 items max
}
// Track input element focus
trackInputFocus() {
// Track right-click context menu
document.addEventListener('contextmenu', (event) => {
this.handleContextMenu(event);
});
// Also track focus and input events to better detect active search inputs
document.addEventListener('focusin', (event) => {
if (this.isSearchInput(event.target)) {
this.currentInputElement = event.target;
console.log('✅ Search input focused:', event.target);
}
});
// Track input events to detect when user is typing
document.addEventListener('input', (event) => {
if (this.isSearchInput(event.target)) {
this.currentInputElement = event.target;
console.log('✅ Search input active:', event.target);
}
});
}
// Handle context menu event
handleContextMenu(event) {
// Check if right-clicked element is a search input
if (this.isSearchInput(event.target)) {
this.currentInputElement = event.target;
console.log('✅ Search input detected via context menu:', event.target);
return;
}
// Check if right-clicked near a search input
const nearbyInput = this.findNearbySearchInput(event.target);
if (nearbyInput) {
this.currentInputElement = nearbyInput;
console.log('✅ Nearby search input detected:', nearbyInput);
return;
}
// Fallback: find main search input on page
this.currentInputElement = this.findMainSearchInput();
console.log('🔍 Using main search input fallback:', this.currentInputElement);
}
// Find search input near the clicked element
findNearbySearchInput(element) {
// Check parent elements up to 5 levels
let current = element;
for (let i = 0; i < 5 && current; i++) {
const searchInput = current.querySelector('input, textarea');
if (searchInput && this.isSearchInput(searchInput)) {
return searchInput;
}
current = current.parentElement;
}
// Check sibling elements
if (element.parentElement) {
const siblings = element.parentElement.querySelectorAll('input, textarea');
for (const sibling of siblings) {
if (this.isSearchInput(sibling)) {
return sibling;
}
}
}
return null;
}
// Find the main search input on the page
findMainSearchInput() {
const hostname = window.location.hostname;
if (hostname.includes('google.com')) {
const selectors = [
'input[name="q"]',
'textarea[name="q"]',
'.gLFyf',
'input[role="combobox"]',
'input[aria-label*="Search"]',
'input[aria-label*="搜索"]',
'input[title*="Search"]',
'input[placeholder*="Search"]'
];
for (const selector of selectors) {
const input = document.querySelector(selector);
if (input && this.isVisible(input)) {
return input;
}
}
}
if (hostname.includes('youtube.com')) {
const selectors = [
'input[name="search_query"]',
'input#search',
'input[aria-label*="Search"]',
'input[placeholder*="Search"]',
'.ytd-searchbox input',
'#search-input input'
];
for (const selector of selectors) {
const input = document.querySelector(selector);
if (input && this.isVisible(input)) {
return input;
}
}
}
// Generic fallback - look for any search-related input
const genericSelectors = [
'input[name="search"]',
'input[name="query"]',
'input[name="q"]',
'input[type="search"]',
'input[aria-label*="search" i]',
'input[placeholder*="search" i]',
'input[title*="search" i]'
];
for (const selector of genericSelectors) {
const input = document.querySelector(selector);
if (input && this.isVisible(input)) {
return input;
}
}
return null;
}
// Enhanced search input detection
isSearchInput(element) {
if (!element || (element.tagName !== 'INPUT' && element.tagName !== 'TEXTAREA')) return false;
// Check if element is visible
if (!this.isVisible(element)) return false;
const hostname = window.location.hostname;
const name = element.name?.toLowerCase() || '';
const id = element.id?.toLowerCase() || '';
const className = element.className?.toLowerCase() || '';
const placeholder = element.placeholder?.toLowerCase() || '';
const ariaLabel = element.getAttribute('aria-label')?.toLowerCase() || '';
const title = element.title?.toLowerCase() || '';
const type = element.type?.toLowerCase() || '';
// Site-specific checks
if (hostname.includes('google.com')) {
return name === 'q' ||
className.includes('glfyf') ||
element.getAttribute('role') === 'combobox' ||
ariaLabel.includes('search') ||
ariaLabel.includes('搜索');
}
if (hostname.includes('youtube.com')) {
return name === 'search_query' ||
id === 'search' ||
className.includes('search') ||
ariaLabel.includes('search');
}
// Generic search input detection
const searchTerms = ['search', 'query', 'find', '搜索', '查找'];
const searchFields = [name, id, className, placeholder, ariaLabel, title];
// Check if type is search
if (type === 'search') return true;
// Check if any field contains search terms
return searchTerms.some(term =>
searchFields.some(field => field.includes(term))
) || name === 'q';
}
// Handle copy request from context menu - simplified and fast
async handleCopyRequest(sendResponse) {
let responseSent = false;
const safeResponse = (data) => {
if (!responseSent) {
responseSent = true;
try {
sendResponse(data);
} catch (error) {
console.error('Failed to send response:', error);
}
}
};
try {
// Find search input if not already set
if (!this.currentInputElement) {
this.currentInputElement = this.findMainSearchInput();
}
if (!this.currentInputElement) {
const errorMsg = 'No search input found. Please click on a search input field first.\n未找到搜索输入框。请先点击搜索框然后输入内容显示下拉建议。';
this.showToast(`${errorMsg}`, 'error');
safeResponse({ success: false, error: errorMsg });
return;
}
// Focus input briefly to ensure dropdown is active
this.currentInputElement.focus();
// Get dropdown items immediately - no complex waiting
let dropdownItems = this.getDropdownItems();
// If no items found, wait a very short time and try once more
if (dropdownItems.length === 0) {
await new Promise(resolve => setTimeout(resolve, 300));
dropdownItems = this.getDropdownItems();
}
if (dropdownItems.length === 0) {
const errorMsg = 'No dropdown suggestions found. Please type something to show suggestions first.\n未找到下拉建议。请在搜索框中输入内容以显示建议列表。';
this.showToast(`${errorMsg}`, 'error');
safeResponse({ success: false, error: errorMsg });
return;
}
// Copy to clipboard
const textToCopy = dropdownItems.join('\n');
await this.copyToClipboard(textToCopy);
this.showToast(`✅ Successfully copied ${dropdownItems.length} items!\n成功复制 ${dropdownItems.length} 条建议!`, 'success');
safeResponse({ success: true, count: dropdownItems.length, items: dropdownItems });
} catch (error) {
console.error('❌ Copy failed:', error);
this.showToast(`${error.message}`, 'error');
safeResponse({ success: false, error: error.message });
}
}
// Extract clean text from element with filtering
extractTextFromElement(element) {
// Remove script and style elements
const clone = element.cloneNode(true);
const scripts = clone.querySelectorAll('script, style');
scripts.forEach(script => script.remove());
let text = clone.textContent || clone.innerText || '';
// Clean up the text
text = this.cleanSearchSuggestionText(text);
return text;
}
// Clean search suggestion text - simplified version
cleanSearchSuggestionText(text) {
if (!text) return '';
// Basic cleaning
text = text.trim().replace(/\s+/g, ' ');
// Remove common UI elements at the end
text = text.replace(/(查看更多删除|查看更多|删除|更多|OM|View more|Delete|Remove)$/gi, '').trim();
// Remove trailing dots and clean up
text = text.replace(/\.{2,}$/, '').trim();
return text;
}
// Check if element is visible
isVisible(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0' &&
element.offsetWidth > 0 &&
element.offsetHeight > 0;
}
// Copy text to clipboard
async copyToClipboard(text) {
if (navigator.clipboard) {
await navigator.clipboard.writeText(text);
} else {
// Fallback
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
}
// Show enhanced toast notification
showToast(message, type = 'info') {
// Remove existing toast
const existingToast = document.getElementById('dropdown-copy-toast');
if (existingToast) existingToast.remove();
const toast = document.createElement('div');
toast.id = 'dropdown-copy-toast';
toast.className = `dropdown-copy-toast ${type}`;
// Handle multi-line messages (bilingual support)
if (message.includes('\n')) {
const lines = message.split('\n');
lines.forEach((line, index) => {
const lineDiv = document.createElement('div');
lineDiv.textContent = line;
if (index > 0) {
lineDiv.style.fontSize = '12px';
lineDiv.style.opacity = '0.9';
lineDiv.style.marginTop = '2px';
}
toast.appendChild(lineDiv);
});
} else {
toast.textContent = message;
}
// Enhanced styling
Object.assign(toast.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '16px 24px',
borderRadius: '8px',
color: 'white',
backgroundColor: this.getToastColor(type),
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
fontSize: '14px',
fontWeight: '500',
zIndex: '2147483647', // Maximum z-index
boxShadow: '0 8px 32px rgba(0,0,0,0.2)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
maxWidth: '400px',
wordBreak: 'break-word',
transition: 'all 0.3s ease',
opacity: '0',
transform: 'translateX(100%)'
});
document.body.appendChild(toast);
// Animate in
requestAnimationFrame(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateX(0)';
});
// Auto-remove with fade out animation
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 4000);
// Click to dismiss
toast.addEventListener('click', () => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
});
}
// Get toast background color based on type
getToastColor(type) {
switch (type) {
case 'success': return '#10b981';
case 'error': return '#ef4444';
case 'warning': return '#f59e0b';
case 'info':
default: return '#3b82f6';
}
}
}
// Initialize the helper when DOM is ready (only once)
if (!window.dropdownCopyHelper) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.dropdownCopyHelper = new DropdownCopyHelper();
});
} else {
window.dropdownCopyHelper = new DropdownCopyHelper();
}
}

1
create-icons.js Normal file
View File

@ -0,0 +1 @@

39
icons/README.md Normal file
View File

@ -0,0 +1,39 @@
# Icons for Dropdown Copy Helper
This directory contains all the required icon files for the Chrome extension:
## ✅ Available Icon Files:
- ✅ `icon16.png` (16x16 pixels) - Toolbar icon
- ✅ `icon32.png` (32x32 pixels) - Extension management page
- ✅ `icon48.png` (48x48 pixels) - Extension management page
- ✅ `icon128.png` (128x128 pixels) - Chrome Web Store and installation
- ✅ `icon.png` (1080x1080 pixels) - Original high-resolution source
- ✅ `icon.svg` (Vector format) - Scalable vector source
## Icon Creation Process:
The icons were automatically generated from your original `icon.png` (1080x1080) using ImageMagick:
```bash
magick icons/icon.png -resize 16x16 icons/icon16.png
magick icons/icon.png -resize 32x32 icons/icon32.png
magick icons/icon.png -resize 48x48 icons/icon48.png
magick icons/icon.png -resize 128x128 icons/icon128.png
```
## Icon Usage in Extension:
- **icon16.png**: Displayed in the Chrome toolbar
- **icon32.png**: Used in extension management pages
- **icon48.png**: Used in extension management pages and details
- **icon128.png**: Used in Chrome Web Store and during installation
## File Verification:
All icon files have been verified:
- ✅ Correct dimensions
- ✅ PNG format with transparency support
- ✅ Proper color depth
- ✅ Ready for Chrome extension use
The extension is now ready to be loaded into Chrome with proper icon support!

BIN
icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

182
icons/icon.svg Normal file
View File

@ -0,0 +1,182 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1080.000000pt" height="1080.000000pt" viewBox="0 0 1080.000000 1080.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,1080.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M745 9630 c-3 -5 -24 -10 -46 -10 -21 0 -47 -4 -57 -9 -9 -5 -37 -15
-62 -22 -213 -62 -403 -277 -459 -521 -8 -34 -11 -846 -11 -2818 0 -2738 1
-2808 30 -2885 5 -11 14 -36 21 -55 27 -73 84 -155 163 -235 107 -108 198
-163 331 -198 58 -16 5902 -22 5931 -6 12 6 32 29 44 51 21 37 21 43 9 80 -26
80 209 72 -2353 76 -1260 2 -2297 5 -2303 7 -18 6 -19 5315 -1 5333 18 18
8501 17 8515 0 6 -8 9 -959 9 -2478 l-1 -2465 -23 -50 c-58 -129 -105 -193
-179 -248 -67 -48 -114 -74 -164 -87 -95 -26 -130 -115 -71 -181 26 -30 34
-32 88 -31 132 2 342 145 450 307 48 72 67 111 97 202 l22 68 0 2800 c0 2670
-1 2803 -18 2858 -39 125 -116 252 -196 322 -19 17 -47 41 -63 55 -27 24 -134
82 -183 100 -55 20 -94 30 -118 30 -15 0 -27 5 -27 10 0 7 -1568 10 -4684 10
-3113 0 -4687 -3 -4691 -10z m9426 -241 c95 -31 128 -51 194 -117 52 -52 79
-94 129 -201 7 -15 11 -97 11 -219 l0 -196 -30 -6 c-16 -3 -2307 -3 -5090 0
l-5060 5 -3 155 c-3 146 2 193 30 275 24 70 57 123 115 181 64 64 134 105 219
128 41 11 855 14 4735 15 l4686 1 64 -21z m-8432 -972 c8 -10 11 -727 11
-2673 l0 -2659 -22 -8 c-13 -5 -234 -7 -493 -4 -405 3 -478 6 -529 21 -186 53
-324 201 -369 396 -16 66 -21 4908 -6 4926 17 21 1391 21 1408 1z"/>
<path d="M682 9124 c-62 -32 -80 -111 -40 -170 24 -35 50 -52 83 -55 55 -4 76
3 110 36 31 32 35 41 35 88 0 45 -4 57 -28 80 -37 36 -112 45 -160 21z"/>
<path d="M1190 9133 c-30 -6 -57 -29 -76 -65 -42 -81 17 -169 112 -171 60 0
89 18 115 72 19 40 20 50 9 81 -14 38 -42 67 -78 81 -24 9 -39 10 -82 2z"/>
<path d="M1622 9097 c-27 -28 -32 -40 -32 -84 0 -56 21 -88 80 -118 61 -31
145 11 170 84 10 33 9 42 -9 82 -25 52 -55 69 -126 69 -45 0 -55 -4 -83 -33z"/>
<path d="M9803 9106 c-45 -21 -69 -78 -52 -126 6 -19 24 -45 38 -57 25 -22 34
-23 190 -23 l163 0 34 34 c45 45 47 96 5 146 l-29 35 -158 3 c-121 2 -167 -1
-191 -12z"/>
<path d="M647 8093 c-62 -32 -76 -121 -27 -176 l29 -34 264 -6 c337 -7 379 -3
418 35 54 54 35 148 -38 186 -35 19 -609 14 -646 -5z"/>
<path d="M633 7623 c-27 -5 -71 -55 -79 -89 -9 -35 13 -80 55 -114 l36 -30
383 0 c419 0 419 0 461 59 25 35 27 76 5 120 -30 61 -34 61 -456 60 -211 -1
-393 -4 -405 -6z"/>
<path d="M660 7143 c-8 -3 -27 -17 -42 -31 -24 -22 -28 -34 -28 -80 0 -48 4
-57 31 -83 l31 -29 270 0 c260 0 271 1 300 21 17 12 37 37 44 56 13 30 13 38
-4 77 -33 72 -50 76 -339 75 -136 0 -255 -3 -263 -6z"/>
<path d="M2466 8019 c-33 -38 -38 -89 -13 -137 24 -50 42 -52 388 -52 l322 0
32 24 c62 47 69 107 19 165 l-26 31 -348 0 -348 0 -26 -31z"/>
<path d="M3514 8019 c-24 -25 -29 -39 -29 -79 0 -41 5 -55 28 -77 l27 -28 390
-3 c432 -3 431 -3 470 61 30 48 24 78 -24 135 l-19 22 -407 0 -406 0 -30 -31z"/>
<path d="M4681 8021 c-25 -23 -31 -37 -31 -69 0 -57 14 -89 49 -107 42 -21
577 -23 619 -1 49 25 66 53 66 108 0 44 -4 54 -30 74 l-29 24 -306 0 -307 0
-31 -29z"/>
<path d="M2820 7508 c-19 -5 -53 -14 -75 -21 -139 -39 -278 -137 -386 -274
-53 -66 -113 -202 -132 -298 -13 -65 -13 -268 0 -325 24 -107 77 -223 138
-306 99 -132 275 -243 455 -286 83 -19 6749 -19 6830 0 65 16 162 49 193 65
55 30 168 112 199 145 75 81 147 206 173 297 9 33 21 74 26 91 13 43 11 274
-2 331 -37 152 -101 268 -208 374 -107 107 -181 149 -362 207 -46 15 -6795 15
-6849 0z m6807 -219 c27 -6 56 -15 65 -20 10 -5 23 -9 29 -9 23 0 138 -91 181
-142 43 -52 93 -146 117 -221 18 -56 17 -221 -3 -286 -58 -194 -166 -307 -361
-378 -48 -17 -185 -18 -3424 -16 -2548 2 -3382 5 -3410 14 -119 39 -210 103
-281 198 -63 84 -85 142 -100 261 -14 115 5 220 55 312 79 145 197 243 343
283 64 17 6715 21 6789 4z"/>
<path d="M9162 6987 c-13 -14 -22 -36 -22 -56 0 -27 11 -43 65 -94 36 -34 65
-69 65 -78 0 -9 -29 -42 -65 -74 -57 -49 -65 -61 -65 -92 0 -25 8 -43 29 -64
47 -46 72 -40 157 42 l73 71 69 -71 c79 -82 110 -90 156 -45 45 45 36 75 -44
156 -38 39 -70 74 -70 77 0 4 32 39 70 78 54 55 70 79 70 101 0 40 -33 72 -75
72 -29 0 -45 -10 -99 -65 -59 -59 -89 -77 -98 -57 -1 4 -31 33 -66 65 -72 65
-112 75 -150 34z"/>
<path d="M2604 5706 c-17 -14 -36 -42 -43 -64 -12 -37 -12 -43 9 -81 41 -71
48 -72 404 -69 297 3 315 4 336 23 47 42 62 104 38 147 -38 66 -48 68 -401 68
l-312 0 -31 -24z"/>
<path d="M3635 5718 c-51 -28 -65 -50 -65 -104 0 -43 5 -57 28 -81 l27 -30
285 -6 c157 -4 303 -4 325 -1 25 4 52 18 73 37 28 27 32 37 32 82 0 45 -4 55
-33 83 l-33 32 -309 0 c-207 -1 -317 -4 -330 -12z"/>
<path d="M4609 5715 c-55 -30 -76 -104 -45 -163 28 -55 41 -57 378 -57 l310 0
34 37 c27 30 34 46 34 79 0 36 -6 48 -40 81 l-41 38 -302 0 c-241 -1 -307 -4
-328 -15z"/>
<path d="M5589 5715 c-37 -20 -59 -58 -59 -104 0 -31 7 -46 35 -74 19 -19 49
-37 66 -41 59 -11 571 -6 609 7 68 24 100 111 61 170 -36 56 -46 57 -380 57
-244 -1 -311 -4 -332 -15z"/>
<path d="M6579 5707 c-35 -30 -49 -59 -49 -99 0 -50 32 -95 77 -107 23 -7 153
-11 322 -11 271 0 285 1 318 21 78 48 84 136 14 196 -26 23 -29 23 -341 23
-312 0 -315 0 -341 -23z"/>
<path d="M2618 5244 c-73 -39 -91 -145 -34 -199 36 -34 136 -47 179 -22 47 26
67 59 67 112 0 57 -17 88 -60 110 -36 19 -116 18 -152 -1z"/>
<path d="M3195 5255 c-5 -2 -26 -6 -46 -9 -20 -4 -49 -18 -63 -33 -35 -35 -37
-115 -3 -153 42 -48 59 -50 363 -50 312 0 323 2 363 59 29 40 28 108 -2 141
-42 47 -60 50 -340 49 -144 0 -266 -2 -272 -4z"/>
<path d="M4110 5238 c-39 -23 -70 -71 -70 -107 0 -24 11 -42 45 -76 l44 -45
298 1 c333 1 341 2 376 70 31 59 11 129 -48 162 -14 8 -111 12 -319 12 -260 0
-302 -2 -326 -17z"/>
<path d="M5100 5243 c-39 -20 -80 -75 -80 -108 0 -30 39 -85 75 -106 27 -16
63 -18 326 -18 l295 -1 42 35 c61 53 66 113 14 173 -28 32 -75 37 -377 40
-235 3 -263 1 -295 -15z"/>
<path d="M6182 5251 c-101 -6 -104 -7 -133 -39 -35 -39 -39 -91 -9 -139 36
-59 60 -63 384 -63 l292 0 41 35 c71 61 64 148 -15 193 -34 19 -52 20 -247 20
-115 -1 -256 -4 -313 -7z"/>
<path d="M7542 5008 c-18 -13 -35 -34 -39 -48 -4 -14 -8 -122 -8 -240 0 -193
2 -218 19 -242 26 -39 81 -54 127 -34 64 27 64 25 64 281 0 257 0 256 -68 290
-41 20 -57 19 -95 -7z"/>
<path d="M6892 4800 c-36 -22 -52 -54 -52 -103 0 -28 20 -51 171 -197 178
-171 200 -189 235 -190 34 0 83 30 99 60 20 38 19 59 -3 95 -23 37 -310 323
-343 341 -34 19 -69 17 -107 -6z"/>
<path d="M8045 4667 c-206 -211 -205 -209 -205 -265 0 -55 24 -86 79 -102 59
-17 74 -5 307 241 109 115 123 142 97 197 -21 45 -49 62 -102 62 -45 0 -47 -1
-176 -133z"/>
<path d="M2603 4743 c-45 -41 -57 -80 -39 -125 9 -20 29 -46 45 -57 28 -20 41
-21 345 -21 351 0 346 -1 385 66 30 51 26 77 -17 124 l-39 40 -324 0 -325 0
-31 -27z"/>
<path d="M3630 4763 c-37 -14 -80 -75 -80 -115 0 -27 39 -82 69 -96 18 -9 116
-12 342 -12 l316 0 26 24 c14 13 33 40 41 60 13 31 13 40 1 70 -8 19 -28 44
-44 55 -28 20 -40 21 -343 20 -172 0 -320 -3 -328 -6z"/>
<path d="M4606 4749 c-31 -24 -56 -69 -56 -99 0 -29 38 -84 69 -98 18 -9 113
-12 329 -12 345 0 344 0 376 81 16 39 16 43 -4 84 -11 23 -32 47 -46 53 -17 8
-122 12 -333 12 -298 0 -309 -1 -335 -21z"/>
<path d="M5569 4731 c-46 -46 -51 -86 -17 -134 41 -58 29 -56 366 -57 343 0
346 0 378 61 25 48 18 93 -20 133 l-33 36 -318 0 -317 0 -39 -39z"/>
<path d="M2578 4261 c-30 -26 -32 -34 -32 -86 1 -65 19 -90 81 -111 107 -38
203 18 203 119 0 39 -5 49 -35 76 -33 29 -41 31 -110 31 -68 0 -77 -3 -107
-29z"/>
<path d="M3113 4270 c-17 -10 -37 -28 -42 -39 -25 -46 -5 -132 34 -149 11 -5
32 -15 47 -22 21 -10 91 -12 306 -8 302 6 335 12 362 63 17 32 18 97 1 128
-24 44 -44 47 -369 47 -286 0 -309 -1 -339 -20z"/>
<path d="M4098 4275 c-29 -16 -58 -66 -58 -100 0 -30 39 -85 75 -106 27 -16
62 -18 316 -18 315 -1 339 3 378 58 13 18 21 45 21 71 0 36 -6 48 -36 76 l-35
34 -317 0 c-258 -1 -322 -4 -344 -15z"/>
<path d="M5098 4274 c-62 -33 -77 -123 -29 -171 45 -45 96 -52 382 -49 234 2
259 4 295 23 45 23 64 53 64 102 0 26 -9 42 -39 72 l-39 39 -303 0 c-249 -1
-309 -3 -331 -16z"/>
<path d="M6627 4209 c-34 -28 -37 -34 -37 -84 0 -46 4 -56 32 -84 l32 -31 236
0 c233 0 237 0 257 23 46 50 54 92 27 146 -27 54 -60 61 -298 61 l-211 0 -38
-31z"/>
<path d="M7480 4218 c-74 -43 -79 -79 -40 -283 18 -93 28 -150 39 -205 5 -30
12 -66 15 -80 3 -14 15 -83 27 -155 12 -71 26 -150 30 -175 5 -25 13 -70 19
-100 9 -54 21 -113 40 -207 5 -27 14 -73 20 -103 5 -30 14 -75 19 -100 14 -69
61 -328 71 -390 9 -56 24 -138 41 -220 5 -25 14 -72 20 -105 12 -70 26 -141
38 -205 5 -25 14 -72 20 -105 16 -95 39 -216 51 -270 12 -51 22 -101 39 -190
19 -96 22 -102 63 -124 55 -29 122 -28 162 3 17 13 47 55 66 92 19 38 37 71
40 74 3 3 57 110 120 237 63 128 120 233 126 233 6 0 33 -33 59 -72 27 -40 52
-75 55 -78 11 -9 140 -200 140 -207 0 -4 7 -15 17 -25 9 -10 21 -27 27 -37 6
-11 65 -100 131 -198 66 -98 121 -184 123 -190 3 -10 35 -13 119 -13 100 0
117 2 131 19 14 15 232 164 419 285 33 21 73 90 73 125 0 11 -37 76 -82 144
-46 68 -87 131 -93 139 -11 18 -220 316 -250 358 -92 128 -205 302 -201 309 3
5 22 11 43 14 32 5 175 30 388 68 28 5 73 14 100 19 28 6 75 13 105 17 71 8
129 41 157 89 26 45 29 84 8 123 -14 28 -73 81 -105 94 -8 4 -85 52 -170 108
-85 56 -175 113 -200 127 -25 14 -64 38 -87 53 -23 16 -46 29 -51 29 -5 0 -16
7 -23 16 -7 9 -22 20 -34 25 -11 5 -69 40 -128 79 -60 38 -111 70 -113 70 -4
0 -119 73 -176 112 -15 10 -29 18 -32 18 -2 0 -37 22 -78 48 -71 48 -263 167
-323 202 -16 9 -63 38 -103 63 -40 26 -75 47 -78 47 -2 0 -38 23 -79 50 -41
28 -77 50 -80 50 -3 0 -28 16 -55 35 -27 19 -52 35 -56 35 -3 0 -25 14 -49 30
-24 17 -46 30 -49 30 -4 0 -45 25 -91 55 -47 30 -89 55 -94 55 -5 0 -14 7 -21
15 -21 25 -113 65 -149 64 -20 0 -52 -10 -71 -21z m255 -328 c24 -16 46 -30
48 -30 6 0 213 -125 247 -150 16 -11 31 -20 34 -20 3 0 51 -29 106 -65 55 -36
105 -65 110 -65 6 0 10 -4 10 -10 0 -5 5 -10 11 -10 6 0 30 -13 54 -30 24 -16
46 -30 49 -30 3 0 20 -10 39 -22 18 -13 71 -47 118 -76 46 -29 131 -82 187
-117 57 -36 107 -65 112 -65 4 0 14 -7 21 -16 7 -9 22 -20 34 -25 11 -5 49
-27 84 -49 34 -22 65 -40 68 -40 2 0 30 -17 61 -38 51 -34 152 -97 277 -172
97 -58 215 -135 215 -141 0 -9 -139 -38 -330 -69 -30 -4 -68 -11 -85 -15 -16
-3 -52 -10 -80 -15 -27 -5 -79 -14 -115 -21 -86 -16 -114 -31 -134 -73 -28
-59 -26 -64 64 -191 30 -43 60 -85 65 -94 6 -9 34 -50 63 -91 28 -41 112 -163
186 -270 74 -107 152 -219 173 -248 21 -30 43 -61 48 -70 6 -10 23 -36 38 -59
15 -24 27 -44 27 -47 0 -3 -151 -105 -209 -141 -9 -5 -37 -25 -63 -43 -26 -17
-51 -32 -56 -32 -10 0 -72 75 -72 88 0 5 -9 17 -20 27 -11 10 -20 22 -20 26 0
4 -21 37 -47 73 -27 35 -88 124 -138 197 -49 72 -94 137 -100 143 -5 6 -30 40
-54 76 -24 36 -67 99 -96 140 -29 41 -64 93 -79 115 -38 57 -65 75 -112 75
-31 0 -46 -6 -67 -28 -15 -15 -27 -31 -27 -35 0 -3 -21 -50 -46 -104 -74 -156
-137 -291 -179 -385 -22 -48 -43 -87 -46 -88 -10 0 -17 31 -48 213 -23 132
-32 182 -57 322 -14 83 -30 170 -35 195 -20 106 -28 149 -39 210 -6 36 -16 88
-21 115 -5 28 -14 73 -19 100 -5 28 -14 73 -19 100 -5 28 -14 79 -20 115 -6
36 -18 99 -26 140 -9 41 -20 102 -26 135 -14 77 -34 181 -60 310 -5 28 -14 73
-18 100 -5 28 -14 79 -21 115 -6 36 -15 85 -19 110 -5 25 -9 50 -11 55 -9 34
10 33 65 -5z"/>
<path d="M6979 3710 c-79 -82 -150 -162 -156 -177 -15 -34 -8 -91 15 -116 9
-10 31 -25 49 -32 51 -22 84 -1 213 132 126 130 142 148 171 199 27 49 24 68
-16 109 -31 30 -42 35 -84 35 l-48 0 -144 -150z"/>
<path d="M2607 3799 c-41 -31 -61 -85 -46 -124 36 -90 56 -95 394 -95 303 0
336 5 369 52 10 13 16 43 16 73 0 45 -4 55 -33 83 l-33 32 -319 0 c-308 -1
-321 -1 -348 -21z"/>
<path d="M3606 3786 c-32 -29 -36 -39 -36 -82 0 -58 27 -96 79 -113 48 -16
551 -15 603 1 53 16 88 62 88 116 0 34 -6 48 -35 77 l-36 35 -314 0 -314 0
-35 -34z"/>
<path d="M7355 3044 c20 -15 61 -12 80 7 5 5 -12 9 -45 9 -51 -1 -54 -2 -35
-16z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

BIN
icons/icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
icons/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
icons/icon32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

58
manifest.json Normal file
View File

@ -0,0 +1,58 @@
{
"manifest_version": 3,
"name": "Dropdown Copy Helper / 下拉复制助手",
"version": "1.0.0",
"description": "A Chrome extension to copy all dropdown options from input fields on supported websites like Google and YouTube. 一个Chrome插件用于从Google和YouTube等支持的网站的输入框下拉选项中复制所有内容。",
"permissions": [
"contextMenus",
"activeTab",
"clipboardWrite",
"scripting"
],
"host_permissions": [
"https://www.google.com/*",
"https://google.com/*",
"https://www.youtube.com/*",
"https://youtube.com/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": [
"https://www.google.com/*",
"https://google.com/*",
"https://www.youtube.com/*",
"https://youtube.com/*"
],
"js": [
"content.js"
],
"css": [
"styles.css"
],
"run_at": "document_end"
}
],
"action": {
"default_popup": "popup.html",
"default_title": "Dropdown Copy Helper"
},
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"web_accessible_resources": [
{
"resources": [
"toast.html"
],
"matches": [
"<all_urls>"
]
}
]
}

154
popup.html Normal file
View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
width: 300px;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #1a73e8;
margin-bottom: 5px;
}
.subtitle {
font-size: 14px;
color: #666;
}
.content {
margin-bottom: 20px;
}
.feature {
display: flex;
align-items: center;
margin-bottom: 12px;
padding: 8px;
background: #f8f9fa;
border-radius: 6px;
}
.feature-icon {
width: 20px;
height: 20px;
margin-right: 10px;
background: #1a73e8;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.feature-text {
font-size: 13px;
color: #333;
}
.supported-sites {
margin-top: 15px;
}
.sites-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.site {
display: flex;
align-items: center;
margin-bottom: 6px;
font-size: 13px;
color: #666;
}
.site-dot {
width: 6px;
height: 6px;
background: #34a853;
border-radius: 50%;
margin-right: 8px;
}
.usage {
background: #e8f0fe;
padding: 12px;
border-radius: 6px;
margin-top: 15px;
}
.usage-title {
font-size: 13px;
font-weight: 600;
color: #1a73e8;
margin-bottom: 6px;
}
.usage-text {
font-size: 12px;
color: #333;
line-height: 1.4;
}
</style>
</head>
<body>
<div class="header">
<div class="title">Dropdown Copy Helper</div>
<div class="subtitle">下拉复制助手</div>
</div>
<div class="content">
<div class="feature">
<div class="feature-icon">📋</div>
<div class="feature-text">Right-click input fields to copy dropdown items</div>
</div>
<div class="feature">
<div class="feature-icon">🔍</div>
<div class="feature-text">Automatically detects dropdown suggestions</div>
</div>
<div class="feature">
<div class="feature-icon">📢</div>
<div class="feature-text">Shows toast notifications for copy status</div>
</div>
<div class="supported-sites">
<div class="sites-title">Supported Sites / 支持的网站:</div>
<div class="site">
<div class="site-dot"></div>
Google Search
</div>
<div class="site">
<div class="site-dot"></div>
YouTube
</div>
</div>
<div class="usage">
<div class="usage-title">How to use / 使用方法:</div>
<div class="usage-text">
1. Visit a supported website<br>
2. Click on a search input field<br>
3. Right-click and select "Copy All Dropdown Items"<br>
4. All suggestions will be copied to clipboard
</div>
</div>
</div>
</body>
</html>

98
styles.css Normal file
View File

@ -0,0 +1,98 @@
/* Styles for Dropdown Copy Helper */
/* 下拉复制助手样式 */
/* Toast notification styles */
.dropdown-copy-toast {
position: fixed !important;
top: 20px !important;
right: 20px !important;
padding: 12px 20px !important;
border-radius: 6px !important;
color: white !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
font-size: 14px !important;
font-weight: 500 !important;
z-index: 10000 !important;
box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
max-width: 300px !important;
word-wrap: break-word !important;
transition: all 0.3s ease !important;
opacity: 1 !important;
transform: translateX(0) !important;
}
.dropdown-copy-toast.success {
background-color: #4caf50 !important;
}
.dropdown-copy-toast.error {
background-color: #f44336 !important;
}
.dropdown-copy-toast.info {
background-color: #2196f3 !important;
}
/* Animation for toast appearance */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.dropdown-copy-toast {
animation: slideInRight 0.3s ease !important;
}
/* Ensure toast is above all other elements */
.dropdown-copy-toast {
position: fixed !important;
z-index: 2147483647 !important; /* Maximum z-index value */
}
/* Responsive design for mobile */
@media (max-width: 768px) {
.dropdown-copy-toast {
top: 10px !important;
right: 10px !important;
left: 10px !important;
max-width: none !important;
font-size: 13px !important;
padding: 10px 16px !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.dropdown-copy-toast {
border: 2px solid white !important;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.dropdown-copy-toast {
animation: none !important;
transition: none !important;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.dropdown-copy-toast.success {
background-color: #2e7d32 !important;
}
.dropdown-copy-toast.error {
background-color: #c62828 !important;
}
.dropdown-copy-toast.info {
background-color: #1565c0 !important;
}
}