finish function, with claude and augument
This commit is contained in:
commit
d87e02004e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
claude
|
||||
|
99
INSTALLATION.md
Normal file
99
INSTALLATION.md
Normal 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
95
README.md
Normal 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
107
background.js
Normal 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
701
content.js
Normal 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
1
create-icons.js
Normal file
@ -0,0 +1 @@
|
||||
|
39
icons/README.md
Normal file
39
icons/README.md
Normal 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
BIN
icons/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 258 KiB |
182
icons/icon.svg
Normal file
182
icons/icon.svg
Normal 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
BIN
icons/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
icons/icon16.png
Normal file
BIN
icons/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/icon32.png
Normal file
BIN
icons/icon32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
icons/icon48.png
Normal file
BIN
icons/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
58
manifest.json
Normal file
58
manifest.json
Normal 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
154
popup.html
Normal 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
98
styles.css
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user