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