// ==UserScript==
// @name BZ综合网页搜索脚本
// @namespace http://tampermonkey.net/
// @version 2.000000.0
// @description 搜索/检索单个网页并在原网页和弹窗显示多个搜索结果,支持动态搜索和显示方式选择,搜索不区分大小写,支持移动搜索容器和弹窗。
// @author 半竹
// @match *://*/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// 不使用本地存储的位置,直接使用默认位置
const defaultPosition = { top: '10px', left: '10px' };
// 创建搜索容器
const searchContainer = document.createElement('div');
searchContainer.id = 'web-search-container';
searchContainer.style.position = 'fixed';
searchContainer.style.top = defaultPosition.top;
searchContainer.style.left = defaultPosition.left;
searchContainer.style.zIndex = 9999;
searchContainer.style.backgroundColor = '#2c3e50';
searchContainer.style.padding = '10px';
searchContainer.style.borderRadius = '8px';
searchContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
searchContainer.style.display = 'flex';
searchContainer.style.flexWrap = 'wrap';
searchContainer.style.alignItems = 'center';
searchContainer.style.gap = '8px';
searchContainer.style.transition = 'all 0.3s ease';
makeDraggable(searchContainer);
// 创建显示/隐藏按钮
const toggleVisibilityButton = document.createElement('button');
toggleVisibilityButton.textContent = '隐藏';
toggleVisibilityButton.style.padding = '4px 8px';
toggleVisibilityButton.style.backgroundColor = '#95a5a6';
toggleVisibilityButton.style.color = 'white';
toggleVisibilityButton.style.border = 'none';
toggleVisibilityButton.style.borderRadius = '4px';
toggleVisibilityButton.style.cursor = 'pointer';
toggleVisibilityButton.style.fontSize = '10px';
toggleVisibilityButton.style.transition = 'background-color 0.3s ease';
toggleVisibilityButton.addEventListener('click', function () {
const isVisible = searchContainer.style.display !== 'none';
if (isVisible) {
searchContainer.style.display = 'none';
this.textContent = '显示搜索';
this.style.position = 'fixed';
this.style.top = defaultPosition.top;
this.style.left = defaultPosition.left;
this.style.zIndex = 10000;
document.body.appendChild(this);
} else {
searchContainer.style.display = 'flex';
this.textContent = '隐藏';
this.style.position = 'static';
searchContainer.appendChild(this);
}
});
toggleVisibilityButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#7f8c8d';
});
toggleVisibilityButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#95a5a6';
});
// 创建搜索输入框
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = '输入关键词,用1个分号或1~10个空格分隔';
searchInput.style.padding = '8px';
searchInput.style.border = 'none';
searchInput.style.borderRadius = '6px';
searchInput.style.fontSize = '12px';
searchInput.style.flexGrow = 1;
searchInput.style.outline = 'none';
// 创建添加关键词按钮
const addKeywordButton = document.createElement('button');
addKeywordButton.textContent = '添加关键词';
addKeywordButton.style.padding = '8px 16px';
addKeywordButton.style.backgroundColor = '#3498db';
addKeywordButton.style.color = 'white';
addKeywordButton.style.border = 'none';
addKeywordButton.style.borderRadius = '6px';
addKeywordButton.style.cursor = 'pointer';
addKeywordButton.style.fontSize = '12px';
addKeywordButton.style.transition = 'background-color 0.3s ease';
addKeywordButton.addEventListener('click', function () {
const newKeyword = prompt('请输入要添加的关键词:');
if (newKeyword) {
const currentValue = searchInput.value;
searchInput.value = currentValue? currentValue + ';' + newKeyword : newKeyword;
}
searchInput.focus();
});
addKeywordButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#2980b9';
});
addKeywordButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#3498db';
});
// 创建搜索动态数据按钮
const searchDynamicButton = document.createElement('button');
searchDynamicButton.textContent = '开启动态搜索';
searchDynamicButton.style.padding = '8px 16px';
searchDynamicButton.style.backgroundColor = '#f1c40f';
searchDynamicButton.style.color = 'white';
searchDynamicButton.style.border = 'none';
searchDynamicButton.style.borderRadius = '6px';
searchDynamicButton.style.cursor = 'pointer';
searchDynamicButton.style.fontSize = '12px';
searchDynamicButton.style.transition = 'background-color 0.3s ease';
searchDynamicButton.addEventListener('click', function () {
isDynamicSearch =!isDynamicSearch;
this.textContent = isDynamicSearch? '关闭动态搜索' : '开启动态搜索';
});
searchDynamicButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#f39c12';
});
searchDynamicButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#f1c40f';
});
// 创建搜索按钮
const searchButton = document.createElement('button');
searchButton.textContent = '搜索';
searchButton.style.padding = '8px 16px';
searchButton.style.backgroundColor = '#e74c3c';
searchButton.style.color = 'white';
searchButton.style.border = 'none';
searchButton.style.borderRadius = '6px';
searchButton.style.cursor = 'pointer';
searchButton.style.fontSize = '12px';
searchButton.style.transition = 'background-color 0.3s ease';
searchButton.addEventListener('click', function () {
// 每次搜索都是全新的,清除之前的高亮和结果
clearHighlightedSpans();
clearPopupResultsContainer();
// 根据搜索范围收集文本节点
collectTextNodesByScope();
const inputKeywords = searchInput.value.trim();
if (inputKeywords) {
// 支持分号或1~10个空格分隔关键词
let keywords = inputKeywords.split(';').map(part => part.trim()).filter(part => part!== '');
// 对每个部分再按空格分割
keywords = keywords.flatMap(part => part.split(/\s{1,10}/)).filter(keyword => keyword!== '');
const results = [];
const useRegex = regexCheckbox.checked;
textNodes.forEach(textNode => {
const text = textNode.textContent;
keywords.forEach(keyword => {
let regex;
try {
if (useRegex) {
regex = new RegExp(keyword, 'gi');
} else {
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
regex = new RegExp(escapedKeyword, 'gi');
}
let match;
while ((match = regex.exec(text))!== null) {
results.push({
match: { node: textNode, index: match.index },
keyword: keyword
});
}
} catch (error) {
console.error('正则表达式错误: ', error);
alert('正则表达式错误: ' + error.message);
return;
}
});
});
// 更新所有搜索结果
allSearchResults = results;
const displayOption = displayOptionSelect.value;
if (results.length > 0) {
if (displayOption === 'popup') {
showPopupResults(results);
} else if (displayOption === 'original') {
showOriginalResults(results);
} else if (displayOption === 'combined') {
showOriginalResults(results);
showPopupResults(results);
}
}
// 生成详细的搜索结果统计
const stats = generateSearchStats(results);
searchResultStats.innerHTML = `找到 ${results.length} 个匹配结果
${stats}`;
// 将本次搜索添加到历史记录
const searchEntry = {
keywords: keywords,
displayOption: displayOption
};
searchHistory.push(searchEntry);
localStorage.setItem('searchHistory', JSON.stringify(searchHistory));
displaySearchHistory();
}
});
searchButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#c0392b';
});
searchButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#e74c3c';
});
// 显示方式选择
const displayOptionSelect = document.createElement('select');
const originalOption = document.createElement('option');
originalOption.value = 'original';
originalOption.textContent = '在原网页显示';
const popupOption = document.createElement('option');
popupOption.value = 'popup';
popupOption.textContent = '弹窗显示';
const combinedOption = document.createElement('option');
combinedOption.value = 'combined';
combinedOption.textContent = '合并显示';
displayOptionSelect.appendChild(originalOption);
displayOptionSelect.appendChild(popupOption);
displayOptionSelect.appendChild(combinedOption);
displayOptionSelect.style.padding = '8px';
displayOptionSelect.style.border = 'none';
displayOptionSelect.style.borderRadius = '6px';
displayOptionSelect.style.fontSize = '12px';
displayOptionSelect.style.outline = 'none';
// 正则表达式搜索选项
const regexCheckbox = document.createElement('input');
regexCheckbox.type = 'checkbox';
regexCheckbox.id = 'regex-checkbox';
regexCheckbox.style.marginRight = '4px';
const regexLabel = document.createElement('label');
regexLabel.textContent = '正则表达式';
regexLabel.style.color = 'white';
regexLabel.style.fontSize = '12px';
regexLabel.style.cursor = 'pointer';
regexLabel.htmlFor = 'regex-checkbox';
const regexContainer = document.createElement('div');
regexContainer.style.display = 'flex';
regexContainer.style.alignItems = 'center';
regexContainer.appendChild(regexCheckbox);
regexContainer.appendChild(regexLabel);
// 高亮颜色选择器
const highlightColorLabel = document.createElement('label');
highlightColorLabel.textContent = '高亮颜色: ';
highlightColorLabel.style.color = 'white';
highlightColorLabel.style.fontSize = '12px';
highlightColorLabel.style.marginRight = '4px';
const highlightColorInput = document.createElement('input');
highlightColorInput.type = 'color';
highlightColorInput.value = '#FFFF00'; // 默认黄色
highlightColorInput.style.width = '40px';
highlightColorInput.style.height = '24px';
highlightColorInput.style.border = 'none';
highlightColorInput.style.borderRadius = '4px';
highlightColorInput.style.cursor = 'pointer';
const colorContainer = document.createElement('div');
colorContainer.style.display = 'flex';
colorContainer.style.alignItems = 'center';
colorContainer.appendChild(highlightColorLabel);
colorContainer.appendChild(highlightColorInput);
// 搜索结果过滤输入框
const filterInput = document.createElement('input');
filterInput.type = 'text';
filterInput.placeholder = '过滤搜索结果';
filterInput.style.padding = '8px';
filterInput.style.border = 'none';
filterInput.style.borderRadius = '6px';
filterInput.style.fontSize = '12px';
filterInput.style.width = '100%';
filterInput.style.outline = 'none';
filterInput.addEventListener('input', function () {
filterSearchResults();
});
// 创建搜索历史记录容器
const searchHistoryContainer = document.createElement('div');
searchHistoryContainer.style.width = '100%';
searchHistoryContainer.style.maxHeight = '120px';
searchHistoryContainer.style.overflowY = 'auto';
searchHistoryContainer.style.padding = '8px';
searchHistoryContainer.style.backgroundColor = '#34495e';
searchHistoryContainer.style.borderRadius = '6px';
searchHistoryContainer.style.marginTop = '8px';
searchHistoryContainer.style.color = 'white';
// 创建清除历史记录按钮
const clearHistoryButton = document.createElement('button');
clearHistoryButton.textContent = '清除历史记录';
clearHistoryButton.style.padding = '8px 16px';
clearHistoryButton.style.backgroundColor = '#95a5a6';
clearHistoryButton.style.color = 'white';
clearHistoryButton.style.border = 'none';
clearHistoryButton.style.borderRadius = '6px';
clearHistoryButton.style.cursor = 'pointer';
clearHistoryButton.style.fontSize = '12px';
clearHistoryButton.style.transition = 'background-color 0.3s ease';
clearHistoryButton.addEventListener('click', function () {
searchHistory = [];
localStorage.setItem('searchHistory', JSON.stringify(searchHistory));
displaySearchHistory();
});
clearHistoryButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#7f8c8d';
});
clearHistoryButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#95a5a6';
});
// 创建搜索结果统计容器
const searchResultStats = document.createElement('div');
searchResultStats.style.width = '100%';
searchResultStats.style.padding = '8px';
searchResultStats.style.backgroundColor = '#34495e';
searchResultStats.style.borderRadius = '6px';
searchResultStats.style.marginTop = '8px';
searchResultStats.style.color = 'white';
searchResultStats.style.fontSize = '12px';
// 历史记录筛选输入框
const historyFilterInput = document.createElement('input');
historyFilterInput.type = 'text';
historyFilterInput.placeholder = '筛选历史记录';
historyFilterInput.style.padding = '8px';
historyFilterInput.style.border = 'none';
historyFilterInput.style.borderRadius = '6px';
historyFilterInput.style.fontSize = '12px';
historyFilterInput.style.width = '100%';
historyFilterInput.style.outline = 'none';
historyFilterInput.addEventListener('input', function () {
displaySearchHistory();
});
// 复制搜索结果按钮
const copyResultsButton = document.createElement('button');
copyResultsButton.textContent = '复制搜索结果';
copyResultsButton.style.padding = '8px 16px';
copyResultsButton.style.backgroundColor = '#1abc9c';
copyResultsButton.style.color = 'white';
copyResultsButton.style.border = 'none';
copyResultsButton.style.borderRadius = '6px';
copyResultsButton.style.cursor = 'pointer';
copyResultsButton.style.fontSize = '12px';
copyResultsButton.style.transition = 'background-color 0.3s ease';
copyResultsButton.addEventListener('click', function () {
if (allSearchResults.length > 0) {
let resultText = '搜索结果:\n\n';
allSearchResults.forEach((result, index) => {
resultText += `${index + 1}. 关键词: ${result.keyword}\n`;
resultText += ` 标签名: ${result.match.node.parentElement.tagName}\n`;
resultText += ` 匹配位置: ${result.match.index}\n`;
resultText += ` 内容: ${result.match.node.textContent.substring(0, 100)}${result.match.node.textContent.length > 100 ? '...' : ''}\n\n`;
});
navigator.clipboard.writeText(resultText).then(() => {
alert('搜索结果已复制到剪贴板');
}).catch(err => {
console.error('复制失败: ', err);
});
} else {
alert('没有搜索结果可复制');
}
});
copyResultsButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#16a085';
});
copyResultsButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#1abc9c';
});
// 导出搜索结果按钮
const exportResultsButton = document.createElement('button');
exportResultsButton.textContent = '导出搜索结果';
exportResultsButton.style.padding = '8px 16px';
exportResultsButton.style.backgroundColor = '#3498db';
exportResultsButton.style.color = 'white';
exportResultsButton.style.border = 'none';
exportResultsButton.style.borderRadius = '6px';
exportResultsButton.style.cursor = 'pointer';
exportResultsButton.style.fontSize = '12px';
exportResultsButton.style.transition = 'background-color 0.3s ease';
exportResultsButton.addEventListener('click', function () {
if (allSearchResults.length > 0) {
let resultText = '搜索结果\n\n';
allSearchResults.forEach((result, index) => {
resultText += `${index + 1}. 关键词: ${result.keyword}\n`;
resultText += ` 标签名: ${result.match.node.parentElement.tagName}\n`;
resultText += ` 匹配位置: ${result.match.index}\n`;
resultText += ` 内容: ${result.match.node.textContent.substring(0, 200)}${result.match.node.textContent.length > 200 ? '...' : ''}\n\n`;
});
// 创建Blob对象
const blob = new Blob([resultText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
// 创建下载链接
const a = document.createElement('a');
a.href = url;
a.download = `搜索结果_${new Date().toISOString().slice(0, 10)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// 释放URL对象
URL.revokeObjectURL(url);
alert('搜索结果已导出为文件');
} else {
alert('没有搜索结果可导出');
}
});
exportResultsButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#2980b9';
});
exportResultsButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#3498db';
});
// 搜索结果排序选择器
const sortSelect = document.createElement('select');
sortSelect.style.padding = '8px';
sortSelect.style.border = 'none';
sortSelect.style.borderRadius = '6px';
sortSelect.style.fontSize = '12px';
sortSelect.style.outline = 'none';
const sortOptions = [
{ value: 'default', text: '默认顺序' },
{ value: 'keyword', text: '按关键词排序' },
{ value: 'position', text: '按匹配位置排序' },
{ value: 'tag', text: '按标签名排序' }
];
sortOptions.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.text;
sortSelect.appendChild(opt);
});
sortSelect.addEventListener('change', function () {
sortSearchResults();
saveSearchConfig();
});
// 搜索范围选择器
const scopeSelect = document.createElement('select');
scopeSelect.style.padding = '8px';
scopeSelect.style.border = 'none';
scopeSelect.style.borderRadius = '6px';
scopeSelect.style.fontSize = '12px';
scopeSelect.style.outline = 'none';
const scopeOptions = [
{ value: 'entire', text: '整个页面' },
{ value: 'visible', text: '可见区域' },
{ value: 'selected', text: '选中元素' }
];
scopeOptions.forEach(option => {
const opt = document.createElement('option');
opt.value = option.value;
opt.textContent = option.text;
scopeSelect.appendChild(opt);
});
// 为其他配置控件添加事件监听器,自动保存配置
displayOptionSelect.addEventListener('change', saveSearchConfig);
regexCheckbox.addEventListener('change', saveSearchConfig);
highlightColorInput.addEventListener('change', saveSearchConfig);
scopeSelect.addEventListener('change', saveSearchConfig);
// 打乱搜索结果顺序按钮
const shuffleResultsButton = document.createElement('button');
shuffleResultsButton.textContent = '打乱结果顺序';
shuffleResultsButton.style.padding = '8px 16px';
shuffleResultsButton.style.backgroundColor = '#9b59b6';
shuffleResultsButton.style.color = 'white';
shuffleResultsButton.style.border = 'none';
shuffleResultsButton.style.borderRadius = '6px';
shuffleResultsButton.style.cursor = 'pointer';
shuffleResultsButton.style.fontSize = '12px';
shuffleResultsButton.style.transition = 'background-color 0.3s ease';
shuffleResultsButton.addEventListener('click', function () {
if (allSearchResults.length > 0) {
// 清除之前的高亮和结果
clearHighlightedSpans();
// 打乱结果顺序
allSearchResults = shuffleArray(allSearchResults);
// 根据当前显示选项重新显示结果
const displayOption = displayOptionSelect.value;
if (displayOption === 'popup') {
showPopupResults(allSearchResults);
} else if (displayOption === 'original') {
showOriginalResults(allSearchResults);
} else if (displayOption === 'combined') {
showOriginalResults(allSearchResults);
showPopupResults(allSearchResults);
}
}
});
shuffleResultsButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#8e44ad';
});
shuffleResultsButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#9b59b6';
});
// 创建切换样式按钮
const toggleStyleButton = document.createElement('button');
toggleStyleButton.textContent = '切换样式';
toggleStyleButton.style.padding = '8px 16px';
toggleStyleButton.style.backgroundColor = '#d35400';
toggleStyleButton.style.color = 'white';
toggleStyleButton.style.border = 'none';
toggleStyleButton.style.borderRadius = '6px';
toggleStyleButton.style.cursor = 'pointer';
toggleStyleButton.style.fontSize = '12px';
toggleStyleButton.style.transition = 'background-color 0.3s ease';
toggleStyleButton.addEventListener('click', function () {
if (isFullScreen) {
// 恢复默认样式
searchContainer.style.top = defaultPosition.top;
searchContainer.style.left = defaultPosition.left;
searchContainer.style.width = 'auto';
searchContainer.style.height = 'auto';
searchContainer.style.padding = '10px';
searchContainer.style.flexDirection = 'row';
searchContainer.style.flexWrap = 'wrap';
searchContainer.style.alignItems = 'center';
searchContainer.style.gap = '8px';
} else {
// 切换到卡片样式
searchContainer.style.top = '10%';
searchContainer.style.left = '10%';
searchContainer.style.width = '300px';
searchContainer.style.height = 'auto';
searchContainer.style.padding = '20px';
searchContainer.style.flexDirection = 'column';
searchContainer.style.flexWrap = 'nowrap';
searchContainer.style.alignItems = 'flex-start';
searchContainer.style.gap = '4px';
}
isFullScreen =!isFullScreen;
});
toggleStyleButton.addEventListener('mouseover', function () {
this.style.backgroundColor = '#e67e22';
});
toggleStyleButton.addEventListener('mouseout', function () {
this.style.backgroundColor = '#d35400';
});
searchContainer.appendChild(searchInput);
searchContainer.appendChild(addKeywordButton);
searchContainer.appendChild(searchDynamicButton);
searchContainer.appendChild(searchButton);
searchContainer.appendChild(displayOptionSelect);
searchContainer.appendChild(regexContainer);
searchContainer.appendChild(colorContainer);
searchContainer.appendChild(sortSelect);
searchContainer.appendChild(scopeSelect);
searchContainer.appendChild(filterInput);
searchContainer.appendChild(historyFilterInput);
searchContainer.appendChild(searchHistoryContainer);
searchContainer.appendChild(clearHistoryButton);
searchContainer.appendChild(searchResultStats);
searchContainer.appendChild(copyResultsButton);
searchContainer.appendChild(exportResultsButton);
searchContainer.appendChild(shuffleResultsButton);
searchContainer.appendChild(toggleStyleButton);
searchContainer.appendChild(toggleVisibilityButton);
// 检查搜索容器是否已经存在,若不存在则添加到页面
const existingContainer = document.getElementById('web-search-container');
if (!existingContainer) {
document.body.appendChild(searchContainer);
}
// 全局变量
let textNodes = []; // 用于存储最新的文本节点
let currentlyHighlighted = null; // 记录当前高亮的元素
let searchHistory = JSON.parse(localStorage.getItem('searchHistory')) || []; // 搜索历史记录数组
let currentPage = 1; // 当前页码
const resultsPerPage = 10; // 每页显示的结果数量
let allSearchResults = []; // 存储所有搜索结果
let isDynamicSearch = false; // 是否开启动态搜索
let isFullScreen = false; // 是否切换到卡片样式
// 显示搜索历史记录
function displaySearchHistory() {
searchHistoryContainer.innerHTML = '';
const filter = historyFilterInput.value.toLowerCase();
searchHistory.forEach((entry, index) => {
const keywordsStr = entry.keywords.join(', ');
const displayStr = entry.displayOption;
const historyItemText = `${keywordsStr} (${displayStr})`;
if (historyItemText.toLowerCase().includes(filter)) {
const historyItem = document.createElement('div');
historyItem.textContent = historyItemText;
historyItem.style.padding = '6px';
historyItem.style.borderBottom = '1px solid #4e6379';
historyItem.style.cursor = 'pointer';
historyItem.addEventListener('click', () => {
searchInput.value = entry.keywords.join(';');
displayOptionSelect.value = entry.displayOption;
performSearch();
});
searchHistoryContainer.appendChild(historyItem);
}
});
}
// 收集文本节点的函数
function collectTextNodes(node) {
if (node.nodeType === Node.TEXT_NODE) {
textNodes.push(node);
} else {
const children = node.childNodes;
for (let i = 0; i < children.length; i++) {
collectTextNodes(children[i]);
}
}
}
// 根据搜索范围收集文本节点
function collectTextNodesByScope() {
textNodes = [];
const scope = scopeSelect.value;
switch (scope) {
case 'visible':
// 只收集可见区域的文本节点
const visibleElements = document.querySelectorAll('*');
visibleElements.forEach(element => {
const rect = element.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0) {
collectTextNodes(element);
}
});
break;
case 'selected':
// 只收集选中元素的文本节点
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
collectTextNodes(range.commonAncestorContainer);
} else {
// 如果没有选中元素,收集整个页面
collectTextNodes(document.body);
}
break;
default:
// 收集整个页面的文本节点
collectTextNodes(document.body);
break;
}
}
// 在原网页显示结果
function showOriginalResults(results) {
// 按文本节点分组,确保每个节点只处理一次
const nodeMap = new Map();
results.forEach((result, index) => {
const node = result.match.node;
if (!nodeMap.has(node)) {
nodeMap.set(node, []);
}
nodeMap.get(node).push({ result, index });
});
// 处理每个文本节点
nodeMap.forEach((matches, node) => {
const text = node.textContent;
let newText = text;
// 按匹配位置从后向前处理,避免替换后位置偏移
matches.sort((a, b) => b.result.match.index - a.result.match.index);
matches.forEach(({ result, index }) => {
const keyword = result.keyword;
const start = result.match.index;
const end = start + keyword.length;
// 替换匹配部分
const before = newText.substring(0, start);
const matched = newText.substring(start, end);
const after = newText.substring(end);
newText = before + `${matched}` + after;
});
// 替换节点内容
const range = document.createRange();
range.selectNodeContents(node);
const fragment = range.createContextualFragment(newText);
node.parentNode.replaceChild(fragment, node);
});
}
// 显示弹窗结果
function showPopupResults(results) {
const popup = window.open('', '搜索结果', 'width=400,height=400');
const popupBody = popup.document.body;
popupBody.style.padding = '10px';
results.forEach((result, index) => {
const p = document.createElement('p');
const node = result.match.node;
const text = node.textContent;
const originalKeyword = result.keyword;
const keyword = originalKeyword;
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(escapedKeyword, 'gi');
const newText = text.replace(regex, `${originalKeyword}`);
p.innerHTML = `${index + 1}. ${newText}`;
p.addEventListener('click', function () {
result.match.node.parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 标黄色高亮
const spans = result.match.node.parentElement.querySelectorAll('span');
spans.forEach(span => {
if (span.textContent === originalKeyword) {
span.style.backgroundColor = 'yellow';
}
});
});
p.addEventListener('mouseover', function () {
p.style.backgroundColor = '#f9f9f9';
});
p.addEventListener('mouseout', function () {
p.style.backgroundColor = 'white';
});
popupBody.appendChild(p);
});
}
// 清除原网页中高亮显示的 标签
function clearHighlightedSpans() {
const highlightedSpans = document.querySelectorAll('span[style*="background-color"]');
highlightedSpans.forEach(span => {
const parent = span.parentNode;
const textNode = document.createTextNode(span.textContent);
parent.insertBefore(textNode, span);
parent.removeChild(span);
});
}
// 清除弹窗中的搜索结果容器
function clearPopupResultsContainer() {
// 由于弹窗是通过window.open创建的新窗口,这里我们不需要特殊处理
// 每次搜索都会创建新的弹窗,旧弹窗会自动关闭或保持打开状态
}
// 获取唯一的颜色
function getUniqueColor(index) {
const baseColor = highlightColorInput.value;
// 如果用户选择了自定义颜色,则使用该颜色
if (baseColor) {
return baseColor;
}
// 否则使用默认颜色数组
const baseColors = ['#FFFF00', '#FF00FF', '#00FFFF', '#FFA500', '#00FF00'];
return baseColors[index % baseColors.length];
}
// 打乱数组顺序
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// 使元素可拖动
function makeDraggable(element) {
let isDragging = false;
let offsetX, offsetY;
element.addEventListener('mousedown', function (e) {
isDragging = true;
offsetX = e.clientX - parseInt(element.style.left);
offsetY = e.clientY - parseInt(element.style.top);
});
document.addEventListener('mousemove', function (e) {
if (isDragging) {
element.style.left = (e.clientX - offsetX) + 'px';
element.style.top = (e.clientY - offsetY) + 'px';
}
});
document.addEventListener('mouseup', function () {
isDragging = false;
});
}
// 初始化显示历史记录
displaySearchHistory();
// 加载搜索配置
loadSearchConfig();
// 初始化收集文本节点
collectTextNodesByScope();
// 过滤搜索结果
function filterSearchResults() {
const filterText = filterInput.value.toLowerCase().trim();
if (filterText) {
const filteredResults = allSearchResults.filter(result => {
return result.keyword.toLowerCase().includes(filterText) ||
result.match.node.textContent.toLowerCase().includes(filterText);
});
// 清除之前的高亮
clearHighlightedSpans();
// 根据当前显示选项重新显示过滤后的结果
const displayOption = displayOptionSelect.value;
if (filteredResults.length > 0) {
if (displayOption === 'popup') {
showPopupResults(filteredResults);
} else if (displayOption === 'original') {
showOriginalResults(filteredResults);
} else if (displayOption === 'combined') {
showOriginalResults(filteredResults);
showPopupResults(filteredResults);
}
}
// 生成详细的搜索结果统计
const stats = generateSearchStats(filteredResults);
searchResultStats.innerHTML = `找到 ${filteredResults.length} 个匹配结果 (过滤后)
${stats}`;
} else {
// 清除之前的高亮
clearHighlightedSpans();
// 显示所有结果
const displayOption = displayOptionSelect.value;
if (allSearchResults.length > 0) {
if (displayOption === 'popup') {
showPopupResults(allSearchResults);
} else if (displayOption === 'original') {
showOriginalResults(allSearchResults);
} else if (displayOption === 'combined') {
showOriginalResults(allSearchResults);
showPopupResults(allSearchResults);
}
}
// 生成详细的搜索结果统计
const stats = generateSearchStats(allSearchResults);
searchResultStats.innerHTML = `找到 ${allSearchResults.length} 个匹配结果
${stats}`;
}
}
// 排序搜索结果
function sortSearchResults() {
const sortBy = sortSelect.value;
let sortedResults = [...allSearchResults];
switch (sortBy) {
case 'keyword':
sortedResults.sort((a, b) => a.keyword.localeCompare(b.keyword));
break;
case 'position':
sortedResults.sort((a, b) => a.match.index - b.match.index);
break;
case 'tag':
sortedResults.sort((a, b) => a.match.node.parentElement.tagName.localeCompare(b.match.node.parentElement.tagName));
break;
default:
// 默认顺序,不排序
break;
}
// 清除之前的高亮
clearHighlightedSpans();
// 根据当前显示选项重新显示排序后的结果
const displayOption = displayOptionSelect.value;
if (sortedResults.length > 0) {
if (displayOption === 'popup') {
showPopupResults(sortedResults);
} else if (displayOption === 'original') {
showOriginalResults(sortedResults);
} else if (displayOption === 'combined') {
showOriginalResults(sortedResults);
showPopupResults(sortedResults);
}
}
}
// 生成搜索结果统计信息
function generateSearchStats(results) {
if (results.length === 0) {
return '无匹配结果';
}
// 统计每个关键词的匹配次数
const keywordStats = {};
// 统计每个标签的匹配次数
const tagStats = {};
results.forEach(result => {
// 统计关键词
if (!keywordStats[result.keyword]) {
keywordStats[result.keyword] = 0;
}
keywordStats[result.keyword]++;
// 统计标签
const tag = result.match.node.parentElement.tagName;
if (!tagStats[tag]) {
tagStats[tag] = 0;
}
tagStats[tag]++;
});
// 生成统计信息
let stats = '';
// 关键词统计
stats += '关键词统计:
';
Object.entries(keywordStats).forEach(([keyword, count]) => {
stats += `• ${keyword}: ${count} 次
`;
});
// 标签统计
stats += '标签统计:
';
Object.entries(tagStats).forEach(([tag, count]) => {
stats += `• ${tag}: ${count} 次
`;
});
return stats;
}
// 保存搜索配置
function saveSearchConfig() {
const config = {
displayOption: displayOptionSelect.value,
useRegex: regexCheckbox.checked,
highlightColor: highlightColorInput.value,
sortBy: sortSelect.value,
searchScope: scopeSelect.value
};
localStorage.setItem('searchConfig', JSON.stringify(config));
}
// 加载搜索配置
function loadSearchConfig() {
const config = JSON.parse(localStorage.getItem('searchConfig'));
if (config) {
if (config.displayOption) {
displayOptionSelect.value = config.displayOption;
}
if (config.useRegex !== undefined) {
regexCheckbox.checked = config.useRegex;
}
if (config.highlightColor) {
highlightColorInput.value = config.highlightColor;
}
if (config.sortBy) {
sortSelect.value = config.sortBy;
}
if (config.searchScope) {
scopeSelect.value = config.searchScope;
}
}
}
// 执行搜索函数
function performSearch() {
// 清除之前的高亮和结果
clearHighlightedSpans();
clearPopupResultsContainer();
// 执行搜索
searchButton.click();
}
// 键盘快捷键支持
document.addEventListener('keydown', function (e) {
// Ctrl+F - 聚焦搜索输入框
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
searchInput.focus();
}
// Enter - 执行搜索(当搜索输入框聚焦时)
if (e.key === 'Enter' && document.activeElement === searchInput) {
e.preventDefault();
performSearch();
}
// Esc - 清除搜索输入
if (e.key === 'Escape' && document.activeElement === searchInput) {
searchInput.value = '';
}
// Ctrl+Shift+C - 复制搜索结果
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
e.preventDefault();
copyResultsButton.click();
}
// Ctrl+Shift+E - 导出搜索结果
if (e.ctrlKey && e.shiftKey && e.key === 'E') {
e.preventDefault();
exportResultsButton.click();
}
});
})();