Greasy Fork

来自缓存

Greasy Fork is available in English.

CogniRead

增强型阅读脚本,能够加粗部分单词,并高亮显示词汇表中的特定词汇,同时通过美观的悬浮框展示定义。可通过Alt+B(Windows/Linux)或Control+B(Mac)进行切换。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
// ==/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 = `<b>${cleanWord.slice(0, boldLength)}</b>${cleanWord.slice(boldLength)}`;

        if (isHighlighted) {
            formattedWord = `<span class="cogni-highlight" data-word="${wordLower}">${formattedWord}</span>`;
        } else {
            formattedWord = `<span class="cogni-word">${formattedWord}</span>`;
        }

        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 = `
                <strong>${word}</strong> ${word !== matchedWordText ? `(${matchedWordText})` : ''}<br>
                ${definition}<br>
                <small>Source: ${sourceList}</small>
            `;
            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.");
})();