fix: content script truncation error and mac shortcut issue

- Fixed truncated content.js file causing syntax error and missing logic
- Updated manifest.json to use Command+Shift+X for Mac shortcut
- Updated documentation
This commit is contained in:
empty
2025-12-03 22:11:57 +08:00
parent 2ab8ba9c3c
commit 6b73565918
7 changed files with 82 additions and 58 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -31,19 +31,26 @@
## 使用方法 ## 使用方法
1. 点击浏览器工具栏中的扩展图标 1. 点击浏览器工具栏中的扩展图标
2. 选择输出格式Markdown/JSON/XML 2. **选择输出格式**
- **Markdown 格式** (默认)
- **JSON 结构化**
- **XML 格式**
- **ZIP (Markdown + 图)** - *自动下载包含本地图片的压缩包*
3. 点击 **框选区域提取****提取整页内容** 3. 点击 **框选区域提取****提取整页内容**
- 如果选择了 ZIP 格式,点击这两个按钮将直接触发打包下载
4. 如果是框选模式,拖拽鼠标选择区域 4. 如果是框选模式,拖拽鼠标选择区域
5. 提取完成后内容自动复制到剪贴板 5. 提取完成后内容自动复制到剪贴板 (ZIP 格式除外)
**智能元素提取模式:** **智能元素提取模式:**
1. 按下快捷键(默认 `Alt+Shift+X` / Mac: `Option+Shift+X` 1. 按下快捷键(默认 `Alt+Shift+X` / Mac: `Option+Shift+X`
2. 移动鼠标,扩展会自动高亮当前的 HTML 元素 2. 移动鼠标,扩展会自动高亮当前的 HTML 元素
3. 点击高亮的元素即可提取该区域内容 3. 点击高亮的元素即可提取该区域内容
- *提示:如果在插件弹窗中选择了 ZIP 格式,点击元素将下载该元素的 ZIP 包*
## 快捷操作 ## 快捷操作
- **Alt+Shift+X** (Mac: **Option+Shift+X**) - 开启/关闭元素智能识别模式 - **Alt+Shift+X** (Windows/Linux)
- **Command+Shift+X** (Mac) - 开启/关闭元素智能识别模式
- **ESC** - 取消选择模式 - **ESC** - 取消选择模式
## 输出示例 ## 输出示例

View File

@@ -4,12 +4,38 @@ chrome.commands.onCommand.addListener((command) => {
if (tabs.length === 0) return; if (tabs.length === 0) return;
const tabId = tabs[0].id; const tabId = tabs[0].id;
// 尝试发送消息如果失败可能是页面未加载content script
chrome.tabs.sendMessage(tabId, { action: 'toggleElementSelection' }) chrome.tabs.sendMessage(tabId, { action: 'toggleElementSelection' })
.catch(() => { .catch(() => {
// 可以选择注入脚本或忽略
console.log('Cannot send message to tab', tabId); console.log('Cannot send message to tab', tabId);
}); });
}); });
} }
}); });
// 图片下载代理,绕过 CORS
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'fetchImage') {
fetch(request.url)
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.blob();
})
.then(blob => {
// 将 Blob 转换为 Base64 字符串返回给 content script
const reader = new FileReader();
reader.onloadend = () => {
sendResponse({ success: true, data: reader.result });
};
reader.onerror = () => {
sendResponse({ success: false, error: 'Failed to read blob' });
};
reader.readAsDataURL(blob);
})
.catch(error => {
console.error('Image fetch failed:', error);
sendResponse({ success: false, error: error.message });
});
return true; // 保持消息通道开启以进行异步响应
}
});

View File

@@ -14,17 +14,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
} else if (request.action === 'extractFullPage') { } else if (request.action === 'extractFullPage') {
currentFormat = request.format || 'markdown'; currentFormat = request.format || 'markdown';
const content = extractContent(document.body); const content = extractContent(document.body);
const formatted = formatContent(content, currentFormat); handleOutput(content, currentFormat);
copyToClipboard(formatted);
saveToStorage(formatted);
sendResponse({ success: true }); sendResponse({ success: true });
} else if (request.action === 'toggleElementSelection') { } else if (request.action === 'toggleElementSelection') {
// 如果已经在选择模式,则退出 // 如果已经在选择模式,则退出
if (isSelecting) { if (isSelecting) {
cleanup(); cleanup();
} else { } else {
// 默认格式或者读取存储的格式? 这里暂时用默认markdown
// 理想情况应该从 storage 读取,这里简化处理
chrome.storage.local.get('format', (res) => { chrome.storage.local.get('format', (res) => {
currentFormat = res.format || 'markdown'; currentFormat = res.format || 'markdown';
startElementSelectionMode(); startElementSelectionMode();
@@ -35,6 +31,24 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
return true; return true;
}); });
// === 统一输出处理 ===
function handleOutput(content, format) {
if (format === 'zip') {
handleZipDownload(content);
} else {
const formatted = formatContent(content, format);
copyToClipboard(formatted);
saveToStorage(formatted);
if (currentFormat !== 'zip') {
// 简单的检查,避免误报
if (content && content.length > 0) {
// 这里的通知逻辑由调用者处理,或者已经统一了
}
}
}
}
// === 元素选择模式 === // === 元素选择模式 ===
let highlightBox = null; let highlightBox = null;
let lastTarget = null; let lastTarget = null;
@@ -86,12 +100,12 @@ function onElementClick(e) {
if (lastTarget) { if (lastTarget) {
// 提取内容 // 提取内容
const content = extractContent(lastTarget); const content = extractContent(lastTarget);
const formatted = formatContent(content, currentFormat); handleOutput(content, currentFormat);
copyToClipboard(formatted); if (currentFormat !== 'zip') {
saveToStorage(formatted);
showNotification('✅ 元素内容已提取'); showNotification('✅ 元素内容已提取');
} }
}
cleanup(); cleanup();
} }
@@ -166,11 +180,11 @@ function onMouseUp(e) {
if (elements.length > 0) { if (elements.length > 0) {
// 提取内容 // 提取内容
const content = extractFromElements(elements); const content = extractFromElements(elements);
const formatted = formatContent(content, currentFormat); handleOutput(content, currentFormat);
copyToClipboard(formatted); if (currentFormat !== 'zip') {
saveToStorage(formatted);
showNotification('✅ 内容已提取并复制到剪贴板'); showNotification('✅ 内容已提取并复制到剪贴板');
}
} else { } else {
showNotification('❌ 未选中任何内容'); showNotification('❌ 未选中任何内容');
} }
@@ -523,41 +537,4 @@ function escapeXML(str) {
.replace(/&/g, '&') .replace(/&/g, '&')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
.replace(/"/g, '&quot;') .replace(/
.replace(/'/g, '&apos;');
}
// 复制到剪贴板
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
// 降级方案
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
}
// 保存到 storage
function saveToStorage(text) {
chrome.storage.local.set({ lastExtraction: text });
}
// 显示通知
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'llm-extractor-notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 300);
}, 2000);
}

13
src/browser-extension/lib/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -24,7 +24,7 @@
"toggle-element-selection": { "toggle-element-selection": {
"suggested_key": { "suggested_key": {
"default": "Alt+Shift+X", "default": "Alt+Shift+X",
"mac": "Alt+Shift+X" "mac": "Command+Shift+X"
}, },
"description": "Toggle Element Selection Mode" "description": "Toggle Element Selection Mode"
} }
@@ -32,7 +32,7 @@
"content_scripts": [ "content_scripts": [
{ {
"matches": ["<all_urls>"], "matches": ["<all_urls>"],
"js": ["content.js"], "js": ["lib/jszip.min.js", "content.js"],
"css": ["content.css"] "css": ["content.css"]
} }
], ],

View File

@@ -113,6 +113,7 @@
<option value="markdown">Markdown 格式</option> <option value="markdown">Markdown 格式</option>
<option value="json">JSON 结构化</option> <option value="json">JSON 结构化</option>
<option value="xml">XML 格式</option> <option value="xml">XML 格式</option>
<option value="zip">ZIP (Markdown + 图)</option>
</select> </select>
<button id="selectBtn" class="btn btn-primary"> <button id="selectBtn" class="btn btn-primary">