// ==UserScript== // @name 网页内容智能采集器 - Markdown记录 // @namespace http://tampermonkey.net/ // @version 1.9.0 // @description 悬浮图标采集网页元素,支持批量翻页采集(忽略禁用状态,支持动态加载内容),自定义快捷键等。 // @author Assistant // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @require https://cdn.jsdelivr.net/npm/turndown@7.1.2/dist/turndown.js // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/574815/%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E6%99%BA%E8%83%BD%E9%87%87%E9%9B%86%E5%99%A8%20-%20Markdown%E8%AE%B0%E5%BD%95.user.js // @updateURL https://update.greasyfork.icu/scripts/574815/%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E6%99%BA%E8%83%BD%E9%87%87%E9%9B%86%E5%99%A8%20-%20Markdown%E8%AE%B0%E5%BD%95.meta.js // ==/UserScript== (function() { 'use strict'; const BUTTON_POSITION = 'top-left'; const STORAGE_KEY = 'SmartContentCollector'; const EXPORT_SETTINGS_KEY = 'ExportSettings'; const HOTKEY_SETTINGS_KEY = 'HotkeySettings'; const BATCH_ACTIVE_KEY = 'BatchActive'; const BATCH_NEXT_SELECTOR_KEY = 'BatchNextSelector'; let collectorData = { selectors: [], records: [] }; let exportSettings = { includeStats: true, includeTitle: true, includeUrl: true, includeTime: true }; let hotkeySettings = { key: 'E', ctrl: true, shift: true, alt: false, meta: false }; let batchResumed = false; let batchStopRequested = false; let batchWaiting = false; // 防止并发等待 // ------------------------------ 数据持久化 ------------------------------ function loadData() { const stored = GM_getValue(STORAGE_KEY, null); if (stored) { try { const parsed = JSON.parse(stored); collectorData.selectors = Array.isArray(parsed.selectors) ? parsed.selectors : []; collectorData.records = Array.isArray(parsed.records) ? parsed.records : []; } catch(e) { console.error(e); } } const settingsStored = GM_getValue(EXPORT_SETTINGS_KEY, null); if (settingsStored) { try { const parsed = JSON.parse(settingsStored); exportSettings = { ...exportSettings, ...parsed }; } catch(e) {} } const hotkeyStored = GM_getValue(HOTKEY_SETTINGS_KEY, null); if (hotkeyStored) { try { const parsed = JSON.parse(hotkeyStored); hotkeySettings = { ...hotkeySettings, ...parsed }; } catch(e) {} } else { saveHotkeySettings(); } } function saveData() { GM_setValue(STORAGE_KEY, JSON.stringify({ selectors: collectorData.selectors, records: collectorData.records })); } function saveExportSettings() { GM_setValue(EXPORT_SETTINGS_KEY, JSON.stringify(exportSettings)); } function saveHotkeySettings() { GM_setValue(HOTKEY_SETTINGS_KEY, JSON.stringify(hotkeySettings)); } function setBatchActive(active, selector = '') { GM_setValue(BATCH_ACTIVE_KEY, active); if (selector) GM_setValue(BATCH_NEXT_SELECTOR_KEY, selector); if (!active) GM_deleteValue(BATCH_NEXT_SELECTOR_KEY); } function isBatchActive() { return GM_getValue(BATCH_ACTIVE_KEY, false); } function getBatchNextSelector() { return GM_getValue(BATCH_NEXT_SELECTOR_KEY, ''); } // ------------------------------ 快捷键 ------------------------------ let hotkeyListenerAttached = false; function attachHotkeyListener() { if (hotkeyListenerAttached) document.removeEventListener('keydown', hotkeyHandler); document.addEventListener('keydown', hotkeyHandler); hotkeyListenerAttached = true; } function hotkeyHandler(e) { if (hotkeySettings.ctrl !== e.ctrlKey) return; if (hotkeySettings.shift !== e.shiftKey) return; if (hotkeySettings.alt !== e.altKey) return; if (hotkeySettings.meta !== e.metaKey) return; const pressedKey = e.key.toUpperCase(); if (pressedKey !== hotkeySettings.key.toUpperCase()) return; e.preventDefault(); e.stopPropagation(); if (selectionActive) { showToast('请先完成当前元素选择或取消'); return; } if (collectorData.selectors && collectorData.selectors.length > 0) { const md = collectBySelectors(); if (md) { if (addRecord(md)) showToast('✅ 采集成功,已添加记录'); else showToast('❌ 采集内容为空'); } else { showToast('⚠️ 当前页面未找到匹配的元素,请尝试重新选择采集元素'); } } else { startSelectionMode(false); } } document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isBatchActive()) { stopBatchCollect(); showToast('⏹️ 批量采集已手动停止'); } }); // ------------------------------ Markdown 转换 ------------------------------ let turndownService = null; function initTurndown() { if (typeof TurndownService === 'undefined') { console.warn('Turndown未加载,使用简化转换'); turndownService = null; return; } let defaultBlockElements = []; try { if (TurndownService.defaultOptions && TurndownService.defaultOptions.blockElements) { defaultBlockElements = TurndownService.defaultOptions.blockElements; } else { defaultBlockElements = ['address','article','aside','blockquote','body','br','caption','center','col','colgroup','dd','details','dialog','dir','div','dl','dt','fieldset','figcaption','figure','footer','form','header','hgroup','hr','html','legend','listing','main','marquee','menu','nav','ol','p','pre','section','summary','table','tbody','td','tfoot','th','thead','tr','ul']; } } catch(e) { defaultBlockElements = ['div','p','h1','h2','h3','h4','h5','h6','blockquote','pre','ul','ol','li','table']; } const blockElements = defaultBlockElements.filter(tag => tag !== 'div'); turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', emDelimiter: '*', blockElements: blockElements }); turndownService.addRule('inlineDiv', { filter: 'div', replacement: (content) => content }); turndownService.addRule('images', { filter: 'img', replacement: (content, node) => { const alt = node.getAttribute('alt') || node.getAttribute('title') || ''; const src = node.getAttribute('src') || ''; return ``; } }); } function isExcludedElement(el) { if (!el) return true; const tag = el.tagName.toLowerCase(); if (tag === 'button') return true; if (tag === 'input') { const type = el.getAttribute('type') || ''; if (['button', 'submit', 'reset', 'image'].includes(type)) return true; } const role = el.getAttribute('role'); if (role === 'button' || role === 'link' || role === 'menuitem') return true; const style = window.getComputedStyle(el); if (style.cursor === 'pointer' && (el.onclick || el.getAttribute('onclick') || el.hasAttribute('data-click'))) return true; return false; } function isElementSelectable(el) { if (!el) return false; if (isExcludedElement(el)) return false; return true; } function elementsToMarkdown(elements) { if (!elements || elements.length === 0) return ''; if (!turndownService) initTurndown(); let mdParts = []; for (let el of elements) { if (!el) continue; const clone = el.cloneNode(true); const buttons = clone.querySelectorAll('button, input[type="button"], input[type="submit"], input[type="reset"], [role="button"]'); buttons.forEach(btn => btn.remove()); let html = clone.outerHTML; if (turndownService) { try { let converted = turndownService.turndown(html); converted = converted.replace(/\n{3,}/g, '\n\n'); if (converted.trim()) mdParts.push(converted); } catch(e) { mdParts.push(clone.innerText || clone.textContent || ''); } } else { let text = clone.innerText || clone.textContent || ''; let imgs = clone.querySelectorAll('img'); let imgMd = ''; for (let img of imgs) { let src = img.getAttribute('src') || ''; let alt = img.getAttribute('alt') || ''; imgMd += ` `; } mdParts.push((imgMd + text).trim()); } } let result = mdParts.join('\n\n'); result = result.replace(/\n{3,}/g, '\n\n'); return result; } function collectBySelectors() { if (!collectorData.selectors || collectorData.selectors.length === 0) return null; const collectedElements = []; for (let selector of collectorData.selectors) { try { const elements = document.querySelectorAll(selector); if (elements && elements.length > 0) { for (let el of elements) { if (!isElementSelectable(el)) continue; collectedElements.push(el); } } } catch(e) { console.warn('选择器无效:', selector); } } if (collectedElements.length === 0) return null; return elementsToMarkdown(collectedElements); } function addRecord(markdownContent) { if (!markdownContent || markdownContent.trim() === '') { alert('采集内容为空,未生成记录'); return false; } const record = { id: Date.now() + '_' + Math.random().toString(36).substr(2, 6), timestamp: new Date().toISOString(), url: window.location.href, title: document.title, markdown: markdownContent.trim() }; collectorData.records.push(record); saveData(); return true; } function clearAllRecords() { if (confirm('确定要清空所有已采集的内容记录吗?')) { collectorData.records = []; saveData(); alert('已清空所有记录'); } } function reselectElements() { collectorData.selectors = []; saveData(); startSelectionMode(true); } // ------------------------------ 批量采集(支持动态加载)----------------------------- let batchPageCount = 0; async function waitForContentReload() { return new Promise((resolve, reject) => { const selectors = collectorData.selectors; if (!selectors.length) { reject('没有采集选择器'); return; } let attempts = 0; const maxAttempts = 30; // 30秒超时 const interval = setInterval(() => { attempts++; // 检查任一选择器是否存在且内容非空(防止加载空状态) let found = false; for (let sel of selectors) { const el = document.querySelector(sel); if (el && el.innerText && el.innerText.trim().length > 0) { found = true; break; } } if (found) { clearInterval(interval); resolve(); } else if (attempts >= maxAttempts) { clearInterval(interval); reject('等待内容加载超时'); } }, 1000); }); } async function collectCurrentPageAndNext() { if (!isBatchActive()) return; if (batchStopRequested) { stopBatchCollect(); return; } if (batchWaiting) return; // 防止并发 batchWaiting = true; // 采集当前页内容 const md = collectBySelectors(); if (md) { addRecord(md); batchPageCount++; showToast(`✅ 已采集第 ${batchPageCount} 页`, 1500); } else { showToast('⚠️ 当前页未找到内容元素,批量采集终止'); stopBatchCollect(); batchWaiting = false; return; } // 查找下一页按钮 const nextSelector = getBatchNextSelector(); if (!nextSelector) { stopBatchCollect(); batchWaiting = false; return; } const nextBtn = document.querySelector(nextSelector); if (!nextBtn) { showToast('🏁 下一页按钮已消失,批量采集完成'); stopBatchCollect(); batchWaiting = false; return; } // 点击下一页 nextBtn.click(); showToast(`⏳ 等待第 ${batchPageCount + 1} 页内容加载...`, 1500); // 等待内容重新加载 try { await waitForContentReload(); } catch (err) { showToast('❌ 内容加载超时,批量采集停止'); stopBatchCollect(); batchWaiting = false; return; } batchWaiting = false; // 递归继续 if (isBatchActive() && !batchStopRequested) { collectCurrentPageAndNext(); } else { if (!isBatchActive()) showToast('🏁 批量采集已完成'); } } function stopBatchCollect() { setBatchActive(false); batchStopRequested = false; batchPageCount = 0; batchWaiting = false; showToast('⏹️ 批量采集已停止'); } // 选择下一页按钮 let batchSelectActive = false; function startBatchSelectMode() { if (batchSelectActive || selectionActive) { showToast('请先完成当前的选择操作'); return; } batchSelectActive = true; const overlay = document.createElement('div'); overlay.id = 'batch-select-overlay'; Object.assign(overlay.style, { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0,0,0,0.2)', zIndex: '999990', pointerEvents: 'none', cursor: 'crosshair' }); document.body.appendChild(overlay); let hoverBtn = null; const highlight = document.createElement('div'); Object.assign(highlight.style, { position: 'absolute', border: '2px solid #ff9800', backgroundColor: 'rgba(255,152,0,0.2)', pointerEvents: 'none', zIndex: '999992', boxSizing: 'border-box' }); document.body.appendChild(highlight); const onMouseMove = (e) => { if (!batchSelectActive) return; const target = document.elementFromPoint(e.clientX, e.clientY); if (!target) return; const btn = target.closest('button, input[type="button"], input[type="submit"], a[role="button"], [role="button"], .pagination-next, .next-page, [aria-label="下一页"]'); if (btn && btn !== floatBtn && !floatBtn.contains(btn)) { if (hoverBtn !== btn) { hoverBtn = btn; const rect = btn.getBoundingClientRect(); highlight.style.left = rect.left + window.scrollX + 'px'; highlight.style.top = rect.top + window.scrollY + 'px'; highlight.style.width = rect.width + 'px'; highlight.style.height = rect.height + 'px'; highlight.style.display = 'block'; } } else { highlight.style.display = 'none'; hoverBtn = null; } }; const onClick = (e) => { if (!batchSelectActive) return; const target = document.elementFromPoint(e.clientX, e.clientY); if (!target) return; const btn = target.closest('button, input[type="button"], input[type="submit"], a[role="button"], [role="button"], .pagination-next, .next-page, [aria-label="下一页"]'); if (btn && btn !== floatBtn && !floatBtn.contains(btn)) { e.preventDefault(); e.stopPropagation(); const selector = getUniqueSelector(btn); setBatchActive(true, selector); batchSelectActive = false; highlight.remove(); overlay.remove(); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('click', onClick, true); batchPageCount = 0; batchStopRequested = false; showToast(`已选择下一页按钮,开始批量采集...`); collectCurrentPageAndNext(); } else { showToast('请点击有效的下一页按钮'); } }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('click', onClick, true); const escHandler = (e) => { if (e.key === 'Escape' && batchSelectActive) { batchSelectActive = false; highlight.remove(); overlay.remove(); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('click', onClick, true); document.removeEventListener('keydown', escHandler); showToast('已取消批量采集选择'); } }; document.addEventListener('keydown', escHandler); } // ------------------------------ 悬浮按钮 UI ------------------------------ let floatBtn = null; let contextMenu = null; let selectionActive = false; let selectedElements = []; let selectionToolbar = null; let globalOverlay = null; let highlightDiv = null; function createFloatButton() { if (floatBtn && floatBtn.parentNode) floatBtn.remove(); floatBtn = document.createElement('div'); floatBtn.id = 'smart-collector-float-btn'; floatBtn.innerHTML = '📥 内容采集'; const style = { position: 'fixed', zIndex: '9999999', backgroundColor: '#4a6cf7', color: 'white', padding: '10px 16px', borderRadius: '40px', fontSize: '14px', fontWeight: 'bold', fontFamily: 'system-ui, sans-serif', boxShadow: '0 4px 12px rgba(0,0,0,0.2)', cursor: 'pointer', userSelect: 'none', transition: 'all 0.2s', border: 'none', textAlign: 'center', backdropFilter: 'blur(4px)', background: 'linear-gradient(135deg, #4a6cf7, #6d8aff)' }; if (BUTTON_POSITION === 'top-left') { style.top = '20px'; style.left = '20px'; } else { style.bottom = '20px'; style.right = '20px'; } Object.assign(floatBtn.style, style); floatBtn.addEventListener('mouseenter', () => { floatBtn.style.transform = 'scale(1.02)'; floatBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.25)'; }); floatBtn.addEventListener('mouseleave', () => { floatBtn.style.transform = 'scale(1)'; }); floatBtn.addEventListener('click', (e) => { e.stopPropagation(); if (selectionActive) { alert('请先完成当前元素选择或取消'); return; } if (collectorData.selectors && collectorData.selectors.length > 0) { const md = collectBySelectors(); if (md) { if (addRecord(md)) showToast('✅ 采集成功,已添加记录'); else showToast('❌ 采集内容为空'); } else { showToast('⚠️ 当前页面未找到匹配的元素,请尝试重新选择采集元素'); } } else { startSelectionMode(false); } }); floatBtn.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); showContextMenu(e.clientX, e.clientY); }); document.body.appendChild(floatBtn); console.log('智能采集器:按钮已创建'); } function showContextMenu(x, y) { if (contextMenu) contextMenu.remove(); contextMenu = document.createElement('div'); contextMenu.id = 'smart-collector-context-menu'; Object.assign(contextMenu.style, { position: 'fixed', left: x + 'px', top: y + 'px', zIndex: '10000000', backgroundColor: '#fff', borderRadius: '12px', boxShadow: '0 4px 20px rgba(0,0,0,0.2)', border: '1px solid #e2e8f0', padding: '8px 0', minWidth: '180px', fontFamily: 'system-ui, sans-serif', fontSize: '14px', color: '#1e293b' }); const menuItems = [ { label: '🔄 重新选择采集元素', action: () => { reselectElements(); hideContextMenu(); } }, { label: '🗑️ 清空已采集内容', action: () => { clearAllRecords(); hideContextMenu(); } }, { label: '📋 管理记录/合并导出', action: () => { showRecordsManager(); hideContextMenu(); } }, { label: '⌨️ 快捷键配置', action: () => { showHotkeyConfig(); hideContextMenu(); } }, { label: '📄 批量采集 (选择下一页按钮)', action: () => { startBatchSelectMode(); hideContextMenu(); } }, { label: '⏹️ 停止批量采集(快捷键Esc)', action: () => { if(isBatchActive()) stopBatchCollect(); else showToast('当前没有进行中的批量采集'); hideContextMenu(); } } ]; menuItems.forEach(item => { const menuItem = document.createElement('div'); menuItem.textContent = item.label; Object.assign(menuItem.style, { padding: '8px 16px', cursor: 'pointer', transition: 'background 0.1s', whiteSpace: 'nowrap' }); menuItem.addEventListener('mouseenter', () => menuItem.style.backgroundColor = '#f1f5f9'); menuItem.addEventListener('mouseleave', () => menuItem.style.backgroundColor = 'transparent'); menuItem.addEventListener('click', (e) => { e.stopPropagation(); item.action(); }); contextMenu.appendChild(menuItem); }); document.body.appendChild(contextMenu); const closeMenu = (e) => { if (contextMenu && !contextMenu.contains(e.target)) hideContextMenu(); }; setTimeout(() => document.addEventListener('click', closeMenu, { once: true }), 0); } function hideContextMenu() { if (contextMenu) { contextMenu.remove(); contextMenu = null; } } // ------------------------------ 普通元素选择模式 ------------------------------ function getUniqueSelector(element) { if (!element) return ''; if (element.id) return `#${CSS.escape(element.id)}`; let path = []; let current = element; while (current && current !== document.body) { let tag = current.tagName.toLowerCase(); let siblings = current.parentNode ? Array.from(current.parentNode.children).filter(c => c.tagName === current.tagName) : []; let nth = siblings.length > 1 ? `:nth-of-type(${siblings.indexOf(current)+1})` : ''; let classSelector = ''; if (current.className && typeof current.className === 'string') { let classes = current.className.trim().split(/\s+/).filter(c => c); if (classes.length) classSelector = '.' + classes.map(c => CSS.escape(c)).join('.'); } let selector = tag + classSelector + nth; path.unshift(selector); current = current.parentNode; if (current === document.body) break; } return path.join(' > '); } function startSelectionMode(isReselect = false) { if (selectionActive) return; selectionActive = true; selectedElements = []; if (globalOverlay) globalOverlay.remove(); globalOverlay = document.createElement('div'); Object.assign(globalOverlay.style, { position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0,0,0,0.2)', zIndex: '999990', pointerEvents: 'none', cursor: 'crosshair' }); document.body.appendChild(globalOverlay); function createHighlight() { if (highlightDiv) highlightDiv.remove(); highlightDiv = document.createElement('div'); Object.assign(highlightDiv.style, { position: 'absolute', border: '2px solid #4a6cf7', backgroundColor: 'rgba(74,108,247,0.2)', pointerEvents: 'none', zIndex: '999992', boxSizing: 'border-box', transition: 'all 0.05s' }); document.body.appendChild(highlightDiv); } function updateHighlight(el) { if (!el || !highlightDiv) return; const rect = el.getBoundingClientRect(); highlightDiv.style.left = rect.left + window.scrollX + 'px'; highlightDiv.style.top = rect.top + window.scrollY + 'px'; highlightDiv.style.width = rect.width + 'px'; highlightDiv.style.height = rect.height + 'px'; highlightDiv.style.display = 'block'; } function hideHighlight() { if (highlightDiv) highlightDiv.style.display = 'none'; } createHighlight(); let hoverElement = null; const onMouseMove = (e) => { if (!selectionActive) return; const target = document.elementFromPoint(e.clientX, e.clientY); if (!target) return; let collectable = target.closest('div, section, article, p, img, h1, h2, h3, h4, h5, h6, span, a, li, td, th, figure, main, header, footer, blockquote, pre, code, table'); if (collectable && isElementSelectable(collectable) && collectable !== floatBtn && !floatBtn.contains(collectable)) { if (hoverElement !== collectable) { hoverElement = collectable; updateHighlight(hoverElement); } } else { hideHighlight(); hoverElement = null; } }; const onClickSelect = (e) => { if (!selectionActive) return; if (selectionToolbar && selectionToolbar.contains(e.target)) return; const target = document.elementFromPoint(e.clientX, e.clientY); if (!target) return; let collectable = target.closest('div, section, article, p, img, h1, h2, h3, h4, h5, h6, span, a, li, td, th, figure, main, header, footer, blockquote, pre, code, table'); if (!collectable || !isElementSelectable(collectable)) return; if (collectable === floatBtn || floatBtn.contains(collectable)) return; e.preventDefault(); e.stopPropagation(); const index = selectedElements.indexOf(collectable); if (index === -1) { selectedElements.push(collectable); highlightSelected(collectable, true); showToast(`已选中 ${selectedElements.length} 个元素`, 1000); } else { selectedElements.splice(index, 1); highlightSelected(collectable, false); showToast(`已取消选中,当前 ${selectedElements.length} 个元素`, 1000); } }; function highlightSelected(el, add) { if (add) { el.style.setProperty('smart-collector-outline', '3px solid #ff9800', 'important'); el.style.outline = '3px solid #ff9800'; } else { el.style.removeProperty('smart-collector-outline'); el.style.outline = ''; } } function createToolbar() { if (selectionToolbar) selectionToolbar.remove(); selectionToolbar = document.createElement('div'); selectionToolbar.id = 'selector-toolbar'; const toolbarStyle = { position: 'fixed', zIndex: '9999999', backgroundColor: '#1e293b', color: 'white', borderRadius: '32px', padding: '8px 16px', display: 'flex', gap: '12px', fontFamily: 'system-ui', boxShadow: '0 4px 12px rgba(0,0,0,0.3)', pointerEvents: 'auto' }; if (BUTTON_POSITION === 'top-left') { toolbarStyle.top = '80px'; toolbarStyle.left = '20px'; } else { toolbarStyle.bottom = '90px'; toolbarStyle.right = '20px'; } Object.assign(selectionToolbar.style, toolbarStyle); const countSpan = document.createElement('span'); countSpan.id = 'selected-count'; countSpan.textContent = `已选: 0`; const confirmBtn = document.createElement('button'); confirmBtn.textContent = '✅ 完成采集'; confirmBtn.style.cssText = 'background:#4a6cf7; border:none; color:white; border-radius:20px; padding:4px 12px; cursor:pointer;'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '❌ 取消'; cancelBtn.style.cssText = 'background:#64748b; border:none; color:white; border-radius:20px; padding:4px 12px; cursor:pointer;'; selectionToolbar.appendChild(countSpan); selectionToolbar.appendChild(confirmBtn); selectionToolbar.appendChild(cancelBtn); document.body.appendChild(selectionToolbar); const updateCount = () => { countSpan.textContent = `已选: ${selectedElements.length}`; }; const interval = setInterval(updateCount, 200); confirmBtn.onclick = () => { clearInterval(interval); if (selectedElements.length === 0) { alert('请至少选择一个元素'); return; } const newSelectors = selectedElements.map(el => getUniqueSelector(el)); collectorData.selectors = newSelectors; saveData(); const markdown = elementsToMarkdown(selectedElements); if (markdown) { addRecord(markdown); showToast(`采集完成,已添加记录,共 ${selectedElements.length} 个元素`); } else { showToast('内容为空,未添加记录'); } exitSelectionMode(); }; cancelBtn.onclick = () => { clearInterval(interval); exitSelectionMode(); showToast('已取消选择'); }; } createToolbar(); window.addEventListener('mousemove', onMouseMove); window.addEventListener('click', onClickSelect, true); window.__selectionCleanup = () => { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('click', onClickSelect, true); if (highlightDiv) highlightDiv.remove(); if (selectionToolbar) selectionToolbar.remove(); if (globalOverlay) globalOverlay.remove(); selectedElements.forEach(el => { el.style.removeProperty('smart-collector-outline'); el.style.outline = ''; }); selectionActive = false; selectedElements = []; hoverElement = null; }; } function exitSelectionMode() { if (window.__selectionCleanup) { window.__selectionCleanup(); window.__selectionCleanup = null; } selectionActive = false; } let toastTimer = null; function showToast(msg, duration = 2000) { let toast = document.getElementById('smart-collector-toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'smart-collector-toast'; Object.assign(toast.style, { position: 'fixed', bottom: '80px', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.8)', color: 'white', padding: '8px 16px', borderRadius: '24px', fontSize: '14px', zIndex: '10000000', fontFamily: 'system-ui', whiteSpace: 'nowrap', pointerEvents: 'none' }); document.body.appendChild(toast); } toast.textContent = msg; toast.style.opacity = '1'; if (toastTimer) clearTimeout(toastTimer); toastTimer = setTimeout(() => { toast.style.opacity = '0'; }, duration); } // ------------------------------ 记录管理 + 导出设置 ------------------------------ let managerModal = null; function showRecordsManager() { if (managerModal) managerModal.remove(); managerModal = document.createElement('div'); Object.assign(managerModal.style, { position: 'fixed', top: '10%', left: '15%', width: '70%', maxHeight: '80%', backgroundColor: '#fff', borderRadius: '20px', boxShadow: '0 20px 40px rgba(0,0,0,0.3)', zIndex: '1000010', display: 'flex', flexDirection: 'column', overflow: 'hidden', fontFamily: 'system-ui', color: '#1e293b', border: '1px solid #e2e8f0' }); const header = document.createElement('div'); header.style.cssText = 'padding:16px 20px; border-bottom:1px solid #e2e8f0; display:flex; justify-content:space-between; align-items:center; background:#f8fafc'; header.innerHTML = '📚 采集记录管理'; const body = document.createElement('div'); body.style.cssText = 'flex:1; overflow-y:auto; padding:16px;'; const footer = document.createElement('div'); footer.style.cssText = 'padding:12px 20px; border-top:1px solid #e2e8f0; display:flex; justify-content:space-between; align-items:center; background:#f8fafc'; const leftGroup = document.createElement('div'); const selectAllBtn = document.createElement('button'); selectAllBtn.textContent = '✅ 全选 / 取消全选'; selectAllBtn.style.cssText = 'background:#3b82f6; color:white; border:none; border-radius:30px; padding:6px 16px; cursor:pointer; font-weight:bold; margin-right:12px'; leftGroup.appendChild(selectAllBtn); const settingsBtn = document.createElement('button'); settingsBtn.textContent = '⚙️ 导出设置'; settingsBtn.style.cssText = 'background:#64748b; color:white; border:none; border-radius:30px; padding:6px 16px; cursor:pointer; font-weight:bold'; leftGroup.appendChild(settingsBtn); const exportBtn = document.createElement('button'); exportBtn.textContent = '📎 合并导出选中项'; exportBtn.style.cssText = 'background:#4a6cf7; color:white; border:none; border-radius:30px; padding:8px 20px; cursor:pointer; font-weight:bold'; const deleteAllBtn = document.createElement('button'); deleteAllBtn.textContent = '🗑️ 清空所有记录'; deleteAllBtn.style.cssText = 'background:#ef4444; color:white; border:none; border-radius:30px; padding:8px 20px; cursor:pointer; margin-left:12px'; footer.appendChild(leftGroup); footer.appendChild(exportBtn); footer.appendChild(deleteAllBtn); managerModal.appendChild(header); managerModal.appendChild(body); managerModal.appendChild(footer); document.body.appendChild(managerModal); let selectedRecordIds = new Set(); function showExportSettingsModal() { const modal = document.createElement('div'); Object.assign(modal.style, { position: 'fixed', top: '30%', left: '35%', width: '300px', backgroundColor: '#fff', borderRadius: '16px', boxShadow: '0 10px 25px rgba(0,0,0,0.2)', zIndex: '1000020', padding: '20px', fontFamily: 'system-ui' }); modal.innerHTML = `
点击下方输入框,按下组合键(支持 Ctrl、Shift、Alt、Cmd)