feat: add smart element selection mode with shortcut support

- Added background script for handling global shortcuts (Alt+Shift+X)
- Implemented element highlighting and click-to-extract in content script
- Updated manifest to include background worker and commands
- Updated documentation with new feature usage
- Added GEMINI.md project context
This commit is contained in:
empty
2025-12-03 17:20:26 +08:00
parent 6059865523
commit 2ab8ba9c3c
6 changed files with 194 additions and 2 deletions

63
GEMINI.md Normal file
View File

@@ -0,0 +1,63 @@
# Web2MCP 项目上下文
## 项目概览
Web2MCP 是一个工具集旨在将网页内容转换为大语言模型LLM友好的格式Markdown、JSON、XML。它由两个主要组件组成
1. **浏览器扩展:** 一个 Chrome 扩展Manifest V3允许用户直接从浏览器中框选并提取内容。
2. **Python 脚本:** 用于程序化网页抓取和内容结构化的独立脚本。
## 目录结构
* `src/browser-extension/`Chrome 扩展的源代码。
* `manifest.json`扩展配置Manifest V3
* `content.js`DOM 遍历和内容提取的核心逻辑。
* `popup.html` & `popup.js`:扩展弹出窗口的用户界面。
* `scripts/`:实用脚本。
* `mcp.py`:使用 `requests``BeautifulSoup` 获取并结构化网页内容的 Python 脚本。
* `generate_icons.py`:用于生成扩展图标的辅助工具。
* `docs/`:文档。
* `EXTENSION.md`:扩展的详细功能列表和使用说明。
* `INSTALL.md`:安装指南。
## 构建与运行
### 浏览器扩展
该扩展不需要构建步骤(无 webpack/bundler。它作为原始源码扩展运行。
1. **在 Chrome 中加载:**
* 访问 `chrome://extensions/`
* 启用“开发者模式”。
* 点击“加载已解压的扩展程序”并选择 `src/browser-extension` 目录。
### Python 脚本
**先决条件:**
* Python 3.x
* 依赖项:`requests``beautifulsoup4`
**安装:**
```bash
pip install requests beautifulsoup4
```
**用法:**
```bash
# 运行抓取脚本(当前目标为硬编码的 URL
python scripts/mcp.py
```
## 主要功能
* **内容提取:** 支持标题、段落、代码块、列表、表格、图片和链接。
* **格式:** 导出为 Markdown、JSON 和 XML。
* **交互:**
* **扩展:** 支持“区域框选”(拖动选择)和“整页”提取。自动复制到剪贴板。
* **脚本:** 获取 HTML 并将其解析为结构化的 JSON 文件(默认为 `wechat_dev_seo_structured.json`)。
## 开发规范
* **扩展架构:** 使用标准 Web 技术HTML/CSS/JS无需框架。
* **Manifest 版本:** 严格遵循 V3。
* **Python 风格:** 遵循标准 Python 脚本实践。
* **文档:**`docs/` 文件夹中维护文档;`README.md` 作为项目入口点。

View File

@@ -5,6 +5,7 @@
## 功能特性 ## 功能特性
- 🎯 **区域框选提取** - 拖拽鼠标框选想要提取的区域 - 🎯 **区域框选提取** - 拖拽鼠标框选想要提取的区域
- 🔍 **元素智能识别** - 快捷键激活,自动识别并高亮网页元素,点击即可提取
- 📄 **整页提取** - 一键提取整个页面内容 - 📄 **整页提取** - 一键提取整个页面内容
- 📝 **多种输出格式** - 支持 Markdown、JSON、XML - 📝 **多种输出格式** - 支持 Markdown、JSON、XML
- 📋 **自动复制** - 提取后自动复制到剪贴板 - 📋 **自动复制** - 提取后自动复制到剪贴板
@@ -35,9 +36,15 @@
4. 如果是框选模式,拖拽鼠标选择区域 4. 如果是框选模式,拖拽鼠标选择区域
5. 提取完成后内容自动复制到剪贴板 5. 提取完成后内容自动复制到剪贴板
**智能元素提取模式:**
1. 按下快捷键(默认 `Alt+Shift+X` / Mac: `Option+Shift+X`
2. 移动鼠标,扩展会自动高亮当前的 HTML 元素
3. 点击高亮的元素即可提取该区域内容
## 快捷操作 ## 快捷操作
- **ESC** - 取消框选模式 - **Alt+Shift+X** (Mac: **Option+Shift+X**) - 开启/关闭元素智能识别模式
- **ESC** - 取消选择模式
## 输出示例 ## 输出示例

View File

@@ -0,0 +1,15 @@
chrome.commands.onCommand.addListener((command) => {
if (command === 'toggle-element-selection') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length === 0) return;
const tabId = tabs[0].id;
// 尝试发送消息如果失败可能是页面未加载content script
chrome.tabs.sendMessage(tabId, { action: 'toggleElementSelection' })
.catch(() => {
// 可以选择注入脚本或忽略
console.log('Cannot send message to tab', tabId);
});
});
}
});

View File

@@ -17,6 +17,16 @@
pointer-events: none; pointer-events: none;
} }
.llm-extractor-element-highlight {
position: fixed;
border: 2px solid #ff4757;
background: rgba(255, 71, 87, 0.1);
z-index: 999999;
pointer-events: none;
transition: all 0.1s ease-out;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5);
}
.llm-extractor-hint { .llm-extractor-hint {
position: fixed; position: fixed;
top: 20px; top: 20px;

View File

@@ -18,10 +18,85 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
copyToClipboard(formatted); copyToClipboard(formatted);
saveToStorage(formatted); saveToStorage(formatted);
sendResponse({ success: true }); sendResponse({ success: true });
} else if (request.action === 'toggleElementSelection') {
// 如果已经在选择模式,则退出
if (isSelecting) {
cleanup();
} else {
// 默认格式或者读取存储的格式? 这里暂时用默认markdown
// 理想情况应该从 storage 读取,这里简化处理
chrome.storage.local.get('format', (res) => {
currentFormat = res.format || 'markdown';
startElementSelectionMode();
});
}
sendResponse({ success: true });
} }
return true; return true;
}); });
// === 元素选择模式 ===
let highlightBox = null;
let lastTarget = null;
function startElementSelectionMode() {
isSelecting = true;
// 创建提示
const hint = document.createElement('div');
hint.className = 'llm-extractor-hint';
hint.textContent = '移动鼠标选择元素点击提取ESC 取消';
document.body.appendChild(hint);
// 创建高亮框
highlightBox = document.createElement('div');
highlightBox.className = 'llm-extractor-element-highlight';
highlightBox.style.display = 'none';
document.body.appendChild(highlightBox);
// 绑定事件
document.addEventListener('mousemove', onElementMouseMove, true);
document.addEventListener('click', onElementClick, true);
document.addEventListener('keydown', onKeyDown);
}
function onElementMouseMove(e) {
if (!isSelecting) return;
const target = document.elementFromPoint(e.clientX, e.clientY);
if (target && target !== lastTarget && target !== highlightBox && !target.classList.contains('llm-extractor-hint')) {
lastTarget = target;
const rect = target.getBoundingClientRect();
highlightBox.style.display = 'block';
highlightBox.style.left = rect.left + 'px';
highlightBox.style.top = rect.top + 'px';
highlightBox.style.width = rect.width + 'px';
highlightBox.style.height = rect.height + 'px';
}
}
function onElementClick(e) {
if (!isSelecting) return;
e.preventDefault();
e.stopPropagation();
if (lastTarget) {
// 提取内容
const content = extractContent(lastTarget);
const formatted = formatContent(content, currentFormat);
copyToClipboard(formatted);
saveToStorage(formatted);
showNotification('✅ 元素内容已提取');
}
cleanup();
}
// === 框选模式 ===
// 开始框选模式 // 开始框选模式
function startSelectionMode() { function startSelectionMode() {
isSelecting = true; isSelecting = true;
@@ -111,12 +186,18 @@ function onKeyDown(e) {
function cleanup() { function cleanup() {
isSelecting = false; isSelecting = false;
lastTarget = null;
if (selectionBox) { if (selectionBox) {
selectionBox.remove(); selectionBox.remove();
selectionBox = null; selectionBox = null;
} }
if (highlightBox) {
highlightBox.remove();
highlightBox = null;
}
if (overlay) { if (overlay) {
overlay.remove(); overlay.remove();
overlay = null; overlay = null;
@@ -128,6 +209,10 @@ function cleanup() {
document.removeEventListener('mousedown', onMouseDown); document.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp); document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onElementMouseMove, true);
document.removeEventListener('click', onElementClick, true);
document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keydown', onKeyDown);
} }

View File

@@ -17,6 +17,18 @@
"128": "icons/icon128.png" "128": "icons/icon128.png"
} }
}, },
"background": {
"service_worker": "background.js"
},
"commands": {
"toggle-element-selection": {
"suggested_key": {
"default": "Alt+Shift+X",
"mac": "Alt+Shift+X"
},
"description": "Toggle Element Selection Mode"
}
},
"content_scripts": [ "content_scripts": [
{ {
"matches": ["<all_urls>"], "matches": ["<all_urls>"],