Files
blurweb/content.js
2025-12-11 11:44:54 +08:00

714 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// content.js - 核心功能实现
(function() {
'use strict';
console.log('[BlurText] Content script loaded');
let isBlurMode = false;
let blurMode = 'element'; // 'element', 'selection', or 'area'
let blurIntensity = 10;
let blurredElements = new Set();
let hintElement = null;
let blurButton = null; // 浮动模糊按钮
let elementTooltip = null; // 元素模式悬停提示
// 区域模式相关变量
let isDrawing = false;
let startX = 0;
let startY = 0;
let drawingBox = null;
let areaOverlays = []; // 存储所有区域覆盖层
// 初始化:从存储中加载配置
chrome.storage.local.get(['blurIntensity'], (result) => {
if (result.blurIntensity) {
blurIntensity = result.blurIntensity;
console.log('[BlurText] Loaded blur intensity:', blurIntensity);
}
});
// 监听来自 popup 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('[BlurText] Received message:', request);
if (request.action === 'toggleBlurMode') {
isBlurMode = request.enabled;
blurMode = request.mode || 'element';
blurIntensity = request.intensity || 10;
console.log('[BlurText] Toggle blur mode:', isBlurMode, 'mode:', blurMode, 'intensity:', blurIntensity);
if (isBlurMode) {
enableBlurMode();
} else {
disableBlurMode();
}
} else if (request.action === 'clearAll') {
console.log('[BlurText] Clear all blurs');
clearAllBlurs();
} else if (request.action === 'updateIntensity') {
blurIntensity = request.intensity;
console.log('[BlurText] Update intensity:', blurIntensity);
updateAllBlurIntensity();
} else if (request.action === 'switchMode') {
blurMode = request.mode;
console.log('[BlurText] Switch mode:', blurMode);
updateModeUI();
}
});
// 启用模糊模式
function enableBlurMode() {
console.log('[BlurText] Enabling blur mode, mode:', blurMode);
let modeText = '元素模式 - 点击元素进行模糊';
if (blurMode === 'selection') {
modeText = '文本选择模式 - 拖动选择文字后点击模糊按钮';
} else if (blurMode === 'area') {
modeText = '区域模式 - 拖动鼠标绘制模糊区域';
}
showHint(`模糊模式已开启 - ${modeText},按 ESC 退出`);
// 根据模式添加对应的样式和事件
if (blurMode === 'element') {
document.body.classList.add('blurtext-mode');
document.addEventListener('click', handleClick, true);
document.addEventListener('mouseover', handleMouseOver, true);
document.addEventListener('mouseout', handleMouseOut, true);
} else if (blurMode === 'selection') {
// 文本选择模式不需要 crosshair 光标
document.addEventListener('mouseup', handleTextSelection, true);
} else if (blurMode === 'area') {
// 区域模式:使用 crosshair 光标并添加绘制事件
document.body.classList.add('blurtext-mode');
document.addEventListener('mousedown', handleAreaMouseDown, true);
document.addEventListener('mousemove', handleAreaMouseMove, true);
document.addEventListener('mouseup', handleAreaMouseUp, true);
}
// 键盘事件对所有模式都需要
document.addEventListener('keydown', handleKeydown, true);
console.log('[BlurText] Event listeners attached');
}
// 禁用模糊模式
function disableBlurMode() {
document.body.classList.remove('blurtext-mode');
hideHint();
hideBlurButton();
hideDrawingBox();
hideElementTooltip();
// 移除所有事件监听
document.removeEventListener('click', handleClick, true);
document.removeEventListener('keydown', handleKeydown, true);
document.removeEventListener('mouseover', handleMouseOver, true);
document.removeEventListener('mouseout', handleMouseOut, true);
document.removeEventListener('mouseup', handleTextSelection, true);
document.removeEventListener('mousedown', handleAreaMouseDown, true);
document.removeEventListener('mousemove', handleAreaMouseMove, true);
document.removeEventListener('mouseup', handleAreaMouseUp, true);
// 清除所有高亮和预览
removeAllHighlights();
}
// 更新模式UI
function updateModeUI() {
if (!isBlurMode) return;
console.log('[BlurText] Switching mode to:', blurMode);
// 先移除所有旧的事件监听和样式
document.body.classList.remove('blurtext-mode');
document.removeEventListener('click', handleClick, true);
document.removeEventListener('mouseover', handleMouseOver, true);
document.removeEventListener('mouseout', handleMouseOut, true);
document.removeEventListener('mouseup', handleTextSelection, true);
document.removeEventListener('mousedown', handleAreaMouseDown, true);
document.removeEventListener('mousemove', handleAreaMouseMove, true);
document.removeEventListener('mouseup', handleAreaMouseUp, true);
// 清除旧模式的UI元素
hideBlurButton();
removeAllHighlights();
hideDrawingBox();
hideElementTooltip();
// 根据新模式添加事件监听和样式
if (blurMode === 'element') {
document.body.classList.add('blurtext-mode');
document.addEventListener('click', handleClick, true);
document.addEventListener('mouseover', handleMouseOver, true);
document.addEventListener('mouseout', handleMouseOut, true);
showHint('已切换到元素模式 - 点击元素进行模糊', 3000);
} else if (blurMode === 'selection') {
// 文本选择模式不需要 crosshair 光标
document.addEventListener('mouseup', handleTextSelection, true);
showHint('已切换到文本选择模式 - 拖动选择文字后点击模糊按钮', 3000);
} else if (blurMode === 'area') {
// 区域模式
document.body.classList.add('blurtext-mode');
document.addEventListener('mousedown', handleAreaMouseDown, true);
document.addEventListener('mousemove', handleAreaMouseMove, true);
document.addEventListener('mouseup', handleAreaMouseUp, true);
showHint('已切换到区域模式 - 拖动鼠标绘制模糊区域', 3000);
}
console.log('[BlurText] Mode switched successfully');
}
// 处理点击事件
function handleClick(e) {
console.log('[BlurText] Click detected, isBlurMode:', isBlurMode, 'blurMode:', blurMode);
if (!isBlurMode || blurMode !== 'element') return;
// 阻止默认行为
e.preventDefault();
e.stopPropagation();
let target = e.target;
console.log('[BlurText] Target element:', target.tagName, target.className, 'text:', target.textContent?.substring(0, 20));
// 如果是提示元素,忽略
if (target.classList.contains('blurtext-hint')) {
console.log('[BlurText] Ignored hint element');
return;
}
// 切换模糊状态
toggleBlur(target);
}
// 处理键盘事件ESC 退出)
function handleKeydown(e) {
if (e.key === 'Escape' && isBlurMode) {
isBlurMode = false;
disableBlurMode();
// 通知 popup
chrome.runtime.sendMessage({ action: 'blurModeDisabled' });
}
}
// 处理文本选择(文本选择模式)
function handleTextSelection(e) {
if (!isBlurMode || blurMode !== 'selection') return;
// 避免点击模糊按钮时触发
if (e.target.classList.contains('blurtext-blur-button')) {
return;
}
// 避免点击已模糊的文本段落时触发(这些段落有自己的点击事件)
if (e.target.classList.contains('blurtext-selection-wrapped')) {
return;
}
const selection = window.getSelection();
const selectedText = selection.toString().trim();
console.log('[BlurText] Text selection:', selectedText);
if (selectedText.length > 0) {
// 显示模糊按钮
showBlurButton(selection);
} else {
// 没有选中文本,隐藏按钮
hideBlurButton();
}
}
// 显示模糊按钮
function showBlurButton(selection) {
// 移除旧按钮
hideBlurButton();
// 获取选区的位置
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
// 创建模糊按钮
blurButton = document.createElement('div');
blurButton.className = 'blurtext-blur-button';
blurButton.textContent = '🔒 模糊选中文本';
blurButton.style.position = 'fixed';
blurButton.style.left = `${rect.left + rect.width / 2}px`;
blurButton.style.top = `${rect.bottom + 10}px`;
blurButton.style.transform = 'translateX(-50%)';
blurButton.style.zIndex = '2147483647';
// 点击按钮模糊文本
blurButton.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
blurSelectedText();
});
document.body.appendChild(blurButton);
}
// 隐藏模糊按钮
function hideBlurButton() {
if (blurButton && blurButton.parentNode) {
blurButton.parentNode.removeChild(blurButton);
blurButton = null;
}
}
// 显示元素悬停提示
function showElementTooltip(element, text) {
// 移除旧提示
hideElementTooltip();
// 获取元素位置
const rect = element.getBoundingClientRect();
// 创建提示元素
elementTooltip = document.createElement('div');
elementTooltip.className = 'blurtext-element-tooltip';
elementTooltip.textContent = text;
elementTooltip.style.left = `${rect.left + rect.width / 2}px`;
elementTooltip.style.top = `${rect.top + rect.height / 2}px`;
elementTooltip.style.transform = 'translate(-50%, -50%)';
document.body.appendChild(elementTooltip);
}
// 隐藏元素悬停提示
function hideElementTooltip() {
if (elementTooltip && elementTooltip.parentNode) {
elementTooltip.parentNode.removeChild(elementTooltip);
elementTooltip = null;
}
}
// 模糊选中的文本
function blurSelectedText() {
const selection = window.getSelection();
if (!selection.rangeCount) return;
try {
const range = selection.getRangeAt(0);
// 获取选中的文本内容
const selectedText = range.toString();
// 验证选区是否符合要求
if (!selectedText.trim()) {
console.log('[BlurText] No text selected');
hideBlurButton();
return;
}
// 限制:不允许包含换行
if (selectedText.includes('\n') || selectedText.includes('\r')) {
console.log('[BlurText] Selection contains line breaks');
showSelectionError(selection, '不支持跨行选择');
hideBlurButton();
return;
}
// 限制:不允许包含多个空格(允许单个空格)
if (/\s{2,}/.test(selectedText)) {
console.log('[BlurText] Selection contains multiple spaces');
showSelectionError(selection, '不支持多个连续空格');
hideBlurButton();
return;
}
// 限制:检查是否跨越多个元素
const startContainer = range.startContainer;
const endContainer = range.endContainer;
// 如果起始和结束容器不同,则跨越了多个节点
if (startContainer !== endContainer) {
console.log('[BlurText] Selection spans multiple elements');
showSelectionError(selection, '不支持跨元素选择');
hideBlurButton();
return;
}
// 限制:只能选择文本节点
if (startContainer.nodeType !== Node.TEXT_NODE) {
console.log('[BlurText] Selection is not in a text node');
showSelectionError(selection, '请选择纯文本内容');
hideBlurButton();
return;
}
// 创建 span 包裹选中的文本
const span = document.createElement('span');
span.className = 'blurtext-blurred blurtext-selection-wrapped';
span.style.setProperty('--blur-intensity', `${blurIntensity}px`);
span.style.cursor = 'pointer';
// 添加鼠标悬停事件,显示恢复提示
span.addEventListener('mouseenter', () => {
showElementTooltip(span, '点击恢复此文本');
});
span.addEventListener('mouseleave', () => {
hideElementTooltip();
});
// 添加点击事件,允许单独恢复
span.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
hideElementTooltip(); // 点击后立即隐藏提示
unblurSelectionSpan(span);
});
// 使用 surroundContents在严格限制下应该不会失败
range.surroundContents(span);
// 添加到模糊元素集合
blurredElements.add(span);
console.log('[BlurText] Text selection blurred, total elements:', blurredElements.size);
// 清除选择
selection.removeAllRanges();
// 隐藏按钮
hideBlurButton();
} catch (error) {
console.error('[BlurText] Error blurring selection:', error);
showSelectionError(selection, '无法模糊该选区');
hideBlurButton();
}
}
// 在选区位置显示错误提示
function showSelectionError(selection, message) {
if (!selection.rangeCount) return;
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
// 创建错误提示元素
const errorTooltip = document.createElement('div');
errorTooltip.className = 'blurtext-selection-error';
errorTooltip.textContent = message;
errorTooltip.style.position = 'fixed';
errorTooltip.style.left = `${rect.left + rect.width / 2}px`;
errorTooltip.style.top = `${rect.bottom + 10}px`;
errorTooltip.style.transform = 'translateX(-50%)';
errorTooltip.style.zIndex = '2147483647';
document.body.appendChild(errorTooltip);
// 清除选择
selection.removeAllRanges();
// 2秒后自动移除
setTimeout(() => {
if (errorTooltip.parentNode) {
errorTooltip.parentNode.removeChild(errorTooltip);
}
}, 2000);
}
// 恢复单个选择模式的模糊段落
function unblurSelectionSpan(span) {
if (!span || !span.parentNode) return;
console.log('[BlurText] Unblurring selection span');
// 从集合中移除
blurredElements.delete(span);
// 获取 span 的内容(保持子节点结构)
const fragment = document.createDocumentFragment();
while (span.firstChild) {
fragment.appendChild(span.firstChild);
}
// 用原内容替换 span
span.parentNode.replaceChild(fragment, span);
console.log('[BlurText] Selection span removed, remaining elements:', blurredElements.size);
}
// 鼠标悬停高亮
function handleMouseOver(e) {
if (!isBlurMode || blurMode !== 'element') return;
const target = e.target;
if (target.classList.contains('blurtext-hint')) {
return;
}
// 如果是已模糊的元素,显示恢复提示(不添加高亮)
if (target.classList.contains('blurtext-blurred')) {
removeAllHighlights(); // 移除其他高亮
showElementTooltip(target, '点击取消模糊');
return;
}
// 移除之前的高亮和提示
removeAllHighlights();
hideElementTooltip();
// 直接高亮元素
target.classList.add('blurtext-highlight');
}
// 移除所有高亮
function removeAllHighlights() {
const highlighted = document.querySelectorAll('.blurtext-highlight');
highlighted.forEach(el => {
el.classList.remove('blurtext-highlight');
});
}
// 鼠标移出取消高亮
function handleMouseOut(e) {
if (!isBlurMode || blurMode !== 'element') return;
// 隐藏提示
hideElementTooltip();
// 延迟移除,避免在元素间移动时闪烁
setTimeout(() => {
// 检查鼠标是否还在模糊模式下的元素上
const hoveredElement = document.elementFromPoint(e.clientX, e.clientY);
if (!hoveredElement ||
hoveredElement.classList.contains('blurtext-hint') ||
hoveredElement.classList.contains('blurtext-blurred')) {
return;
}
// 如果鼠标不在任何高亮元素上,清除所有高亮
const highlighted = document.querySelector('.blurtext-highlight:hover');
if (!highlighted) {
removeAllHighlights();
}
}, 100);
}
// 切换元素模糊状态
function toggleBlur(element) {
if (blurredElements.has(element)) {
// 取消模糊
console.log('[BlurText] Removing blur from element');
element.classList.remove('blurtext-blurred');
element.style.removeProperty('--blur-intensity');
blurredElements.delete(element);
// 隐藏提示气泡(因为元素已不再模糊)
hideElementTooltip();
} else {
// 添加模糊
console.log('[BlurText] Adding blur to element, intensity:', blurIntensity);
element.classList.add('blurtext-blurred');
element.style.setProperty('--blur-intensity', `${blurIntensity}px`);
blurredElements.add(element);
}
console.log('[BlurText] Total blurred elements:', blurredElements.size);
}
// 清除所有模糊
function clearAllBlurs() {
blurredElements.forEach(element => {
element.classList.remove('blurtext-blurred');
element.style.removeProperty('--blur-intensity');
});
blurredElements.clear();
// 清除所有区域覆盖层
areaOverlays.forEach(overlay => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
});
areaOverlays = [];
// 如果在模糊模式下,显示提示
if (isBlurMode) {
showHint('已清除所有模糊效果', 2000);
}
}
// ========== 区域模式相关函数 ==========
// 处理区域模式鼠标按下
function handleAreaMouseDown(e) {
if (!isBlurMode || blurMode !== 'area') return;
// 忽略提示元素和已有的区域覆盖层
if (e.target.classList.contains('blurtext-hint') ||
e.target.classList.contains('blurtext-area-overlay') ||
e.target.classList.contains('blurtext-area-close')) {
return;
}
// 检查是否点击了关闭按钮
if (e.target.classList.contains('blurtext-area-close')) {
const overlay = e.target.parentElement;
removeAreaOverlay(overlay);
e.preventDefault();
e.stopPropagation();
return;
}
isDrawing = true;
startX = e.pageX;
startY = e.pageY;
// 创建绘制框
drawingBox = document.createElement('div');
drawingBox.className = 'blurtext-drawing-box';
drawingBox.style.position = 'absolute';
drawingBox.style.left = startX + 'px';
drawingBox.style.top = startY + 'px';
drawingBox.style.width = '0px';
drawingBox.style.height = '0px';
drawingBox.style.zIndex = '2147483646';
document.body.appendChild(drawingBox);
e.preventDefault();
e.stopPropagation();
}
// 处理区域模式鼠标移动
function handleAreaMouseMove(e) {
if (!isBlurMode || blurMode !== 'area' || !isDrawing || !drawingBox) return;
const currentX = e.pageX;
const currentY = e.pageY;
const width = Math.abs(currentX - startX);
const height = Math.abs(currentY - startY);
const left = Math.min(currentX, startX);
const top = Math.min(currentY, startY);
drawingBox.style.left = left + 'px';
drawingBox.style.top = top + 'px';
drawingBox.style.width = width + 'px';
drawingBox.style.height = height + 'px';
}
// 处理区域模式鼠标释放
function handleAreaMouseUp(e) {
if (!isBlurMode || blurMode !== 'area' || !isDrawing || !drawingBox) return;
isDrawing = false;
const width = parseInt(drawingBox.style.width);
const height = parseInt(drawingBox.style.height);
// 只有当区域足够大时才创建模糊覆盖层(至少 20x20 像素)
if (width > 20 && height > 20) {
createAreaOverlay(
parseInt(drawingBox.style.left),
parseInt(drawingBox.style.top),
width,
height
);
}
// 移除绘制框
if (drawingBox.parentNode) {
drawingBox.parentNode.removeChild(drawingBox);
}
drawingBox = null;
e.preventDefault();
e.stopPropagation();
}
// 创建区域覆盖层
function createAreaOverlay(left, top, width, height) {
const overlay = document.createElement('div');
overlay.className = 'blurtext-area-overlay';
overlay.style.position = 'absolute';
overlay.style.left = left + 'px';
overlay.style.top = top + 'px';
overlay.style.width = width + 'px';
overlay.style.height = height + 'px';
overlay.style.setProperty('--blur-intensity', `${blurIntensity}px`);
overlay.style.zIndex = '2147483645';
// 点击覆盖层恢复区域
overlay.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
removeAreaOverlay(overlay);
});
document.body.appendChild(overlay);
areaOverlays.push(overlay);
// 添加到模糊元素集合(用于统一管理强度)
blurredElements.add(overlay);
console.log('[BlurText] Area overlay created, total overlays:', areaOverlays.length);
}
// 移除区域覆盖层
function removeAreaOverlay(overlay) {
const index = areaOverlays.indexOf(overlay);
if (index > -1) {
areaOverlays.splice(index, 1);
}
blurredElements.delete(overlay);
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
console.log('[BlurText] Area overlay removed, remaining:', areaOverlays.length);
}
// 隐藏绘制框
function hideDrawingBox() {
if (drawingBox && drawingBox.parentNode) {
drawingBox.parentNode.removeChild(drawingBox);
drawingBox = null;
}
isDrawing = false;
}
// ========== 区域模式函数结束 ==========
// 更新所有已模糊元素的强度
function updateAllBlurIntensity() {
blurredElements.forEach(element => {
element.style.setProperty('--blur-intensity', `${blurIntensity}px`);
});
}
// 显示提示
function showHint(message, duration = null) {
// 移除旧提示
hideHint();
// 创建新提示
hintElement = document.createElement('div');
hintElement.className = 'blurtext-hint';
hintElement.textContent = message;
document.body.appendChild(hintElement);
// 如果指定了持续时间,自动隐藏
if (duration) {
setTimeout(() => {
hideHint();
}, duration);
}
}
// 隐藏提示
function hideHint() {
if (hintElement && hintElement.parentNode) {
hintElement.parentNode.removeChild(hintElement);
hintElement = null;
}
}
// 页面加载完成后,恢复之前的模糊状态(未来功能)
// 这里可以添加持久化存储功能
})();