Superscript Pinyin for Chinese Characters with API

Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut.

当前为 2024-08-19 提交的版本,查看 最新版本

// ==UserScript==
// @name         Superscript Pinyin for Chinese Characters with API
// @namespace    http://tampermonkey.net/
// @version      2.4
// @description  Adds superscript pinyin to all Chinese characters on a webpage using an external API with accurate processing and placement. Includes a toggle shortcut.
// @author       Louis
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Cache for storing pinyin results to avoid duplicate API calls
    const pinyinCache = new Map();

    // Variable to track whether pinyin superscript is enabled or disabled
    let pinyinEnabled = false;
    let mutationObserver = null;

    // Expanded cache with common Chinese characters and their pinyin
    const initialCache = {
        '一': 'yī', '二': 'èr', '三': 'sān', '四': 'sì', '五': 'wǔ',
        '六': 'liù', '七': 'qī', '八': 'bā', '九': 'jiǔ', '十': 'shí',
        '百': 'bǎi', '千': 'qiān', '万': 'wàn', '亿': 'yì', '人': 'rén',
        '大': 'dà', '中': 'zhōng', '小': 'xiǎo', '国': 'guó', '上': 'shàng',
        '下': 'xià', '年': 'nián', '天': 'tiān', '时': 'shí', '日': 'rì',
        '月': 'yuè', '水': 'shuǐ', '火': 'huǒ', '木': 'mù', '土': 'tǔ',
        '金': 'jīn', '气': 'qì', '风': 'fēng', '雨': 'yǔ', '雪': 'xuě',
        '语': 'yǔ', '字': 'zì', '书': 'shū', '文': 'wén', '好': 'hǎo',
        '爱': 'ài', '学': 'xué', '生': 'shēng', '死': 'sǐ', '走': 'zǒu',
        '跑': 'pǎo', '吃': 'chī', '喝': 'hē', '睡': 'shuì', '看': 'kàn',
        '听': 'tīng', '说': 'shuō', '问': 'wèn', '答': 'dá', '行': 'xíng',
        '见': 'jiàn', '做': 'zuò', '能': 'néng', '会': 'huì', '对': 'duì',
        '错': 'cuò', '是': 'shì', '不': 'bù', '这': 'zhè', '那': 'nà',
        '来': 'lái', '去': 'qù', '看': 'kàn', '听': 'tīng', '写': 'xiě',
        '读': 'dú', '唱': 'chàng', '跳': 'tiào', '坐': 'zuò', '站': 'zhàn',
        '走': 'zǒu', '跑': 'pǎo', '买': 'mǎi', '卖': 'mài', '开': 'kāi',
        '关': 'guān', '进': 'jìn', '出': 'chū', '打': 'dǎ', '开': 'kāi',
        '关': 'guān', '等': 'děng', '急': 'jí', '笑': 'xiào', '哭': 'kū'
    };

    // Pre-fill the cache with initial data
    Object.entries(initialCache).forEach(([char, pinyin]) => pinyinCache.set(char, pinyin));

    // Function to make a request to the pinyin API
    async function fetchPinyin(char) {
        if (pinyinCache.has(char)) {
            return pinyinCache.get(char);
        }

        try {
            const response = await fetch(`https://12.yvelin.net/pinyin.php?text=${encodeURIComponent(char)}`);
            const result = await response.json();
            const pinyin = result && result.pinyin ? result.pinyin : '';
            pinyinCache.set(char, pinyin);
            return pinyin;
        } catch (e) {
            console.error(`Failed to fetch pinyin for character: "${char}"`, e);
            return '';
        }
    }

    // Function to process a single text node and add superscript pinyin
    async function processTextNode(node) {
        const originalText = node.nodeValue;
        const chineseChars = originalText.match(/[\u4e00-\u9fff]/g);

        if (chineseChars) {
            let newHTML = originalText;
            let charPromises = [];

            // Collect promises to fetch pinyin for each unique Chinese character
            let uniqueChars = Array.from(new Set(chineseChars));
            uniqueChars.forEach(char => {
                charPromises.push(
                    fetchPinyin(char)
                );
            });

            // Wait for all pinyin fetching promises to complete
            await Promise.all(charPromises);

            // Reconstruct the text node with superscripts
            uniqueChars.forEach(char => {
                const pinyin = pinyinCache.get(char);
                if (pinyin) {
                    const superscriptHtml = `<sup data-pinyin="${pinyin}" style="font-size: small; vertical-align: super; color: #888;">${pinyin}</sup>${char}`;
                    newHTML = newHTML.split(char).join(superscriptHtml);
                }
            });

            // Replace the original text node with updated HTML
            const parent = node.parentNode;
            const tempSpan = document.createElement('span');
            tempSpan.innerHTML = newHTML;
            parent.replaceChild(tempSpan, node);
        }
    }

    // Function to remove all superscripts from the document
    function removeSuperscripts() {
        const superscripts = document.querySelectorAll('sup[data-pinyin]');
        superscripts.forEach(sup => sup.remove());

        // Remove all temporary span elements
        const spans = document.querySelectorAll('span');
        spans.forEach(span => {
            if (span.childNodes.length === 1 && span.firstChild.nodeType === Node.TEXT_NODE) {
                span.parentNode.replaceChild(span.firstChild, span);
            }
        });
    }

    // Function to process all text nodes in the document
    async function addPinyinSuperscript() {
        if (!pinyinEnabled) return;

        removeSuperscripts(); // Remove existing superscripts

        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        let node;
        let nodesToProcess = [];

        // Collect all text nodes
        while (node = walker.nextNode()) {
            nodesToProcess.push(node);
        }

        // Process each collected text node
        for (const textNode of nodesToProcess) {
            await processTextNode(textNode);
        }

        // Handle dynamically added content
        if (mutationObserver) {
            mutationObserver.disconnect();
        }

        mutationObserver = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(addedNode => {
                    if (addedNode.nodeType === Node.ELEMENT_NODE) {
                        // Remove existing superscripts in the new content
                        removeSuperscripts();

                        const walker = document.createTreeWalker(addedNode, NodeFilter.SHOW_TEXT, null, false);
                        let node;
                        while (node = walker.nextNode()) {
                            processTextNode(node);
                        }
                    }
                });
            });
        });

        mutationObserver.observe(document.body, { childList: true, subtree: true });

        console.log(`Finished processing all nodes.`);
    }

    // Function to toggle the pinyin superscript on or off
    function togglePinyin() {
        pinyinEnabled = !pinyinEnabled;
        if (pinyinEnabled) {
            console.log("Pinyin superscript enabled.");
            addPinyinSuperscript();
        } else {
            console.log("Pinyin superscript disabled.");
            removeSuperscripts();
            if (mutationObserver) {
                mutationObserver.disconnect();
                mutationObserver = null;
            }
        }
    }

    // Add keyboard shortcut: Ctrl+Shift+P to toggle pinyin
    window.addEventListener('keydown', (event) => {
        if (event.ctrlKey && event.shiftKey && event.key === 'P') {
            event.preventDefault();
            togglePinyin();
        }
    });

    // Run the function after page load
    window.addEventListener('load', () => {
        if (pinyinEnabled) {
            addPinyinSuperscript();
        }
    });

})();