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 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
        }
    });

})();