您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
选中文字后出现浮窗;浮窗因为一些操作(如复制、搜索)消失后,但选中文字依旧没有取消,鼠标再次移入选中文字的区域浮窗再次出现。
// ==UserScript== // @name 划词助手 // @namespace // @version 1.1 // @description 选中文字后出现浮窗;浮窗因为一些操作(如复制、搜索)消失后,但选中文字依旧没有取消,鼠标再次移入选中文字的区域浮窗再次出现。 // @author Myzom // @match <all_urls> // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // --- CSS Styles (from style.css) --- const styles = ` #text-selection-toolbar-userscript { position: absolute; z-index: 2147483647; background-color: rgba(30, 30, 30, 0.92); border: 1px solid rgba(80, 80, 80, 0.7); border-radius: 16px; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); padding: 4px 6px; display: flex; align-items: center; gap: 6px; opacity: 0; visibility: hidden; transition: opacity 0.1s ease-out, visibility 0.1s ease-out, transform 0.1s ease-out; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; color: #e0e0e0; font-size: 12px; line-height: 1; transform: translateY(5px); } #text-selection-toolbar-userscript.visible { opacity: 1; visibility: visible; transform: translateY(0); } #text-selection-toolbar-userscript button { background-color: transparent; border: none; color: inherit; cursor: pointer; padding: 3px 5px; font-size: 12px; border-radius: 8px; transition: background-color 0.15s ease; display: flex; align-items: center; gap: 4px; } #text-selection-toolbar-userscript button:hover { background-color: rgba(70, 70, 70, 0.8); color: white; } #text-selection-toolbar-userscript .search-icon-svg, #text-selection-toolbar-userscript .copy-icon-svg { width: 13px; height: 13px; fill: currentColor; vertical-align: middle; } #text-selection-toolbar-userscript .separator { width: 1px; height: 14px; background-color: rgba(100, 100, 100, 0.5); margin: 0 3px; } `; GM_addStyle(styles); // --- JavaScript Logic --- let toolbar; let currentSelectedText = ''; let lastSelectionRange = null; // 用于存储最后一个有效选区的Range对象 const searchEngines = [ { id: 'google', name: 'Google', url: 'https://www.google.com/search?q=%s' }, { id: 'baidu', name: '百度', url: 'https://www.baidu.com/s?wd=%s' }, { id: 'bing', name: '必应', url: 'https://www.bing.com/search?q=%s' } ]; const searchIconSVG = `<svg class="search-icon-svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`; const copyIconSVG = `<svg class="copy-icon-svg" viewBox="0 0 24 24" fill="currentColor"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`; function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function createToolbar() { if (document.getElementById('text-selection-toolbar-userscript')) { toolbar = document.getElementById('text-selection-toolbar-userscript'); return; } toolbar = document.createElement('div'); toolbar.id = 'text-selection-toolbar-userscript'; document.body.appendChild(toolbar); document.addEventListener('mousedown', (event) => { if (toolbar && toolbar.classList.contains('visible')) { if (!toolbar.contains(event.target)) { const selection = window.getSelection(); if (selection.toString().trim() === '' || selection.isCollapsed) { hideToolbar(); currentSelectedText = ''; lastSelectionRange = null; } } } }, true); } function updateToolbarContent() { if (!toolbar) return; toolbar.innerHTML = ''; const searchIconSpan = document.createElement('span'); searchIconSpan.innerHTML = searchIconSVG; toolbar.appendChild(searchIconSpan); searchEngines.forEach(engine => { const button = document.createElement('button'); button.textContent = engine.name; button.title = `使用 ${engine.name} 搜索`; button.addEventListener('click', (e) => { e.stopPropagation(); if (currentSelectedText) { const searchUrl = engine.url.replace('%s', encodeURIComponent(currentSelectedText)); window.open(searchUrl, '_blank'); hideToolbarAfterAction(); } }); toolbar.appendChild(button); }); const separator = document.createElement('div'); separator.className = 'separator'; toolbar.appendChild(separator); const copyButton = document.createElement('button'); copyButton.innerHTML = copyIconSVG + '复制'; copyButton.title = '复制选中的文本'; copyButton.addEventListener('click', (e) => { e.stopPropagation(); if (currentSelectedText) { navigator.clipboard.writeText(currentSelectedText) .then(() => { /* console.log('文本已复制!'); */ }) .catch(err => console.error('复制失败:', err)); hideToolbarAfterAction(); } }); toolbar.appendChild(copyButton); } function showToolbar(mouseClientX, mouseClientY) { if (!toolbar || !currentSelectedText) return; updateToolbarContent(); // 确保内容是最新的 toolbar.style.left = `0px`; // Reset for size calculation toolbar.style.top = `0px`; toolbar.classList.add('visible'); const toolbarRect = toolbar.getBoundingClientRect(); const toolbarWidth = toolbarRect.width; const toolbarHeight = toolbarRect.height; let idealX = window.scrollX + mouseClientX + 15; let idealY = window.scrollY + mouseClientY + 10; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; if (idealX + toolbarWidth > window.scrollX + viewportWidth - 5) { idealX = window.scrollX + mouseClientX - toolbarWidth - 15; } if (idealX < window.scrollX + 5) { idealX = window.scrollX + 5; } if (idealY + toolbarHeight > window.scrollY + viewportHeight - 5) { idealY = window.scrollY + mouseClientY - toolbarHeight - 10; } if (idealY < window.scrollY + 5) { idealY = window.scrollY + 5; } toolbar.style.left = `${idealX}px`; toolbar.style.top = `${idealY}px`; } function hideToolbar() { if (toolbar) { toolbar.classList.remove('visible'); } } function hideToolbarAfterAction() { setTimeout(() => { hideToolbar(); // currentSelectedText 和 lastSelectionRange 保持不变,以便悬停时可以再次显示 }, 100); } document.addEventListener('mouseup', (event) => { if (toolbar && toolbar.contains(event.target)) { return; } const targetTagName = event.target.tagName; if (targetTagName === 'INPUT' || targetTagName === 'TEXTAREA' || event.target.isContentEditable) { currentSelectedText = ''; lastSelectionRange = null; hideToolbar(); return; } setTimeout(() => { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (selectedText.length > 0 && selectedText.length < 1000) { currentSelectedText = selectedText; try { if (selection.rangeCount > 0) { lastSelectionRange = selection.getRangeAt(0).cloneRange(); // 存储选区范围的克隆 } else { lastSelectionRange = null; } } catch (e) { console.warn("Could not get selection range on mouseup:", e); lastSelectionRange = null; } const mouseX = event.clientX; const mouseY = event.clientY; if (!toolbar) createToolbar(); showToolbar(mouseX, mouseY); } else { currentSelectedText = ''; lastSelectionRange = null; hideToolbar(); } }, 10); }); function handleDocumentMouseMove(event) { // 条件1: 工具栏已隐藏,并且我们有之前选中文本的记录 if (!(toolbar && toolbar.classList.contains('visible')) && currentSelectedText && lastSelectionRange) { const currentActualSelection = window.getSelection(); // 条件2: 页面上没有实际的选区,或者当前选区与我们存储的不符 if (currentActualSelection.isCollapsed || currentActualSelection.rangeCount === 0 || currentActualSelection.toString().trim() !== currentSelectedText) { // 如果选区确实消失了(而不是变成了其他内容),则清除我们的跟踪状态 if (currentActualSelection.isCollapsed || currentActualSelection.rangeCount === 0) { currentSelectedText = ''; lastSelectionRange = null; } return; // 不显示工具栏 } // 条件3: 鼠标在存储的选区边界内 try { const rects = lastSelectionRange.getClientRects(); // 获取选区所有行的矩形区域 if (rects.length > 0) { for (let i = 0; i < rects.length; i++) { const rect = rects[i]; if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) { showToolbar(event.clientX, event.clientY); return; // 工具栏已显示,退出 } } } } catch (e) { // console.warn("Error checking selection bounds on mousemove:", e); // Range 可能因DOM更改而失效,清除它 lastSelectionRange = null; currentSelectedText = ''; // 如果范围无效,文本记录也应清除 } } } const debouncedMouseMoveHandler = debounce(handleDocumentMouseMove, 150); // 150ms 防抖 document.addEventListener('mousemove', debouncedMouseMoveHandler); // Initial setup createToolbar(); })();