// ==UserScript== // @name CogniRead // @namespace http://tampermonkey.net/ // @version 1.0 // @description 增强型阅读脚本,能够加粗部分单词,并高亮显示词汇表中的特定词汇,同时通过美观的悬浮框展示定义。可通过Alt+B(Windows/Linux)或Control+B(Mac)进行切换。 // @match *://*/* // @grant none // @license MIT // @author codeboy // @icon https://cdn.icon-icons.com/icons2/609/PNG/512/book-glasses_icon-icons.com_56355.png // @downloadURL none // ==/UserScript== (function () { 'use strict'; let cogniEnabled = false; let wordData = {}; // Word list URLs const wordListSources = { 'TOEFL': 'https://raw.githubusercontent.com/CodeBoy2006/english-wordlists/master/TOEFL_abridged.txt', 'GRE': 'https://raw.githubusercontent.com/CodeBoy2006/english-wordlists/master/GRE_abridged.txt', 'OALD8': 'https://raw.githubusercontent.com/CodeBoy2006/english-wordlists/master/OALD8_abridged_edited.txt', 'TEM-8': 'https://raw.githubusercontent.com/CodeBoy2006/english-wordlists/refs/heads/master/%E8%8B%B1%E8%AF%AD%E4%B8%93%E4%B8%9A%E6%98%9F%E6%A0%87%E5%85%AB%E7%BA%A7%E8%AF%8D%E6%B1%87.txt' }; // Load word lists async function fetchWordLists() { for (let [listName, url] of Object.entries(wordListSources)) { try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const text = await response.text(); wordData[listName] = processWordList(text); console.log(`Loaded ${listName} wordlist with ${Object.keys(wordData[listName]).length} words.`); } catch (error) { console.error(`Failed to load ${listName} wordlist:`, error); } } } // Process word list text function processWordList(text) { const lines = text.split('\n'); const dictionary = {}; lines.forEach(line => { const [word, ...rest] = line.split(/\s+/); if (word) { dictionary[word.toLowerCase()] = rest.join(' '); } }); return dictionary; } // Common suffixes const suffixes = ['s', 'es', 'ed', 'ing', 'er', 'est', 'ly']; // Match word in lists function matchWordInLists(word) { word = word.toLowerCase(); // Direct match for (let list in wordData) { if (wordData[list].hasOwnProperty(word)) { return { word, list, definition: wordData[list][word] }; } } // Match after removing common suffixes for (let suffix of suffixes) { if (word.endsWith(suffix)) { let stem = word.slice(0, -suffix.length); for (let list in wordData) { if (wordData[list].hasOwnProperty(stem)) { return { word: stem, list, definition: wordData[list][stem] }; } } } } // Special cases if (word.endsWith('ies')) { let stem = word.slice(0, -3) + 'y'; for (let list in wordData) { if (wordData[list].hasOwnProperty(stem)) { return { word: stem, list, definition: wordData[list][stem] }; } } } if (word.endsWith('ves')) { let stem = word.slice(0, -3) + 'f'; for (let list in wordData) { if (wordData[list].hasOwnProperty(stem)) { return { word: stem, list, definition: wordData[list][stem] }; } } } return null; } function isWordInData(word) { return matchWordInLists(word) !== null; } // Style word function styleWord(word) { const match = word.match(/^([\w'-]+)([^\w'-]*)$/); if (!match) return word; const [, cleanWord, punctuation] = match; const wordLower = cleanWord.toLowerCase(); const isHighlighted = isWordInData(cleanWord); let boldLength = Math.ceil(cleanWord.length / 2); let formattedWord = `${cleanWord.slice(0, boldLength)}${cleanWord.slice(boldLength)}`; if (isHighlighted) { formattedWord = `${formattedWord}`; } else { formattedWord = `${formattedWord}`; } return formattedWord + punctuation; } // Handle text node function handleTextNode(textNode) { const words = textNode.textContent.split(/(\s+)/); const formattedText = words.map(word => word.trim() ? styleWord(word) : word).join(''); const span = document.createElement('span'); span.innerHTML = formattedText; textNode.replaceWith(span); } // Traverse and style text function traverseAndStyleText(node) { if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.trim().length > 0) { handleTextNode(node); } } else if (node.nodeType === Node.ELEMENT_NODE) { if (!['SCRIPT', 'STYLE', 'TEXTAREA'].includes(node.tagName)) { Array.from(node.childNodes).forEach(traverseAndStyleText); } } } // Initialize tooltip function initTooltip() { let tooltip = document.createElement('div'); tooltip.id = 'cogniread-tooltip'; tooltip.style.cssText = ` position: fixed; background: #ffffff; border: none; border-radius: 8px; padding: 12px 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); display: none; z-index: 9999; max-width: 300px; font-size: 14px; line-height: 1.6; color: #333333; pointer-events: none; transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out; opacity: 0; transform: translateY(10px); `; document.body.appendChild(tooltip); return tooltip; } // Display tooltip function displayTooltip(word, event) { const tooltip = document.getElementById('cogniread-tooltip'); if (!tooltip) return; const matchedWord = matchWordInLists(word); if (matchedWord) { const { word: matchedWordText, list: sourceList, definition } = matchedWord; tooltip.innerHTML = ` ${word} ${word !== matchedWordText ? `(${matchedWordText})` : ''}
${definition}
Source: ${sourceList} `; tooltip.style.display = 'block'; requestAnimationFrame(() => { adjustTooltipPosition(tooltip, event); tooltip.style.opacity = '1'; tooltip.style.transform = 'translateY(0)'; }); } } function removeTooltip() { const tooltip = document.getElementById('cogniread-tooltip'); if (tooltip) { tooltip.style.opacity = '0'; tooltip.style.transform = 'translateY(10px)'; setTimeout(() => { if (tooltip.style.opacity === '0') { tooltip.style.display = 'none'; } }, 200); } } function adjustTooltipPosition(tooltip, event) { const tooltipRect = tooltip.getBoundingClientRect(); let left = event.clientX + 10; let top = event.clientY + 10; if ((left + tooltipRect.width) > window.innerWidth) { left = event.clientX - tooltipRect.width - 10; } if ((top + tooltipRect.height) > window.innerHeight) { top = event.clientY - tooltipRect.height - 10; } tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; } const updateTooltipPositionDebounced = debounce((tooltip, event) => { adjustTooltipPosition(tooltip, event); }, 10); function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Inject CSS styles function addStyles() { const style = document.createElement('style'); style.innerHTML = ` .cogni-word b { color: #000080; } .cogni-highlight { background-color: yellow; cursor: pointer; } #cogniread-tooltip { font-family: Arial, sans-serif; } `; document.head.appendChild(style); } // Enable CogniRead async function enableCogniRead() { console.log("Applying CogniRead..."); await fetchWordLists(); addStyles(); traverseAndStyleText(document.body); initTooltip(); console.log("CogniRead is now active."); } // Toggle CogniRead function toggleCogniRead() { cogniEnabled = !cogniEnabled; if (cogniEnabled) { enableCogniRead(); } else { location.reload(); } } // Set keyboard shortcut const isMacOS = navigator.platform.toUpperCase().indexOf('MAC') >= 0; document.addEventListener('keydown', function(e) { if ((isMacOS && e.ctrlKey && e.key.toLowerCase() === 'b') || (!isMacOS && e.altKey && e.key.toLowerCase() === 'b')) { e.preventDefault(); toggleCogniRead(); } }); // Event delegation for tooltip display document.body.addEventListener('mouseover', function(e) { const target = e.target.closest('.cogni-highlight'); if (target) { const word = target.getAttribute('data-word'); displayTooltip(word, e); } }); document.body.addEventListener('mousemove', function(e) { const tooltip = document.getElementById('cogniread-tooltip'); if (tooltip && tooltip.style.display !== 'none') { updateTooltipPositionDebounced(tooltip, e); } }); document.body.addEventListener('mouseout', function(e) { if (!e.relatedTarget || !e.relatedTarget.closest('.cogni-highlight')) { removeTooltip(); } }); window.addEventListener('scroll', debounce(function() { const tooltip = document.getElementById('cogniread-tooltip'); if (tooltip && tooltip.style.display !== 'none') { removeTooltip(); } }, 100)); console.log("CogniRead script loaded. Use Ctrl+B (Mac) or Alt+B (Windows/Linux) to toggle."); })();