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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
        }
    });

})();