Skip to content

Instantly share code, notes, and snippets.

@xMarch
Created December 12, 2025 12:57
Show Gist options
  • Select an option

  • Save xMarch/c79f98c2191b8866c3c8cd1b55d28d6f to your computer and use it in GitHub Desktop.

Select an option

Save xMarch/c79f98c2191b8866c3c8cd1b55d28d6f to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name 灰基繁轉簡支援 (huiji-zh-t2s)
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Convert Traditional Chinese to Simplified Chinese in search inputs on ff14.huijiwiki.com
// @author Gemini
// @match https://ff14.huijiwiki.com/wiki/*
// @match https://ff14.huijiwiki.com/index.php*
// @grant none
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/t2cn.js
// ==/UserScript==
(function() {
'use strict';
// 1. Setup OpenCC
const converter = OpenCC.Converter({ from: 'tw', to: 'cn' });
// 2. Selectors
const TARGET_SELECTORS = [
'#skin-root > div.skinroot-searcherwrapper > div > div > div > div.n-base-selection-label > input',
'input#ooui-php-1'
];
// 3. Create the Floating Button
const btn = document.createElement('button');
btn.textContent = '轉簡(雙擊Ctrl)';
// Initialize with no binding
btn.dataset.t2cBinding = '';
btn.style.cssText = `
z-index: 1000;
display: none;
position: absolute;
right: 2rem;
background: #3b3b3b;
padding: 0 0.25rem;
border-radius: 0.25rem;
border: none;
filter: drop-shadow(0px 0px 2px #cba971);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1.5;
top: 50%;
transform: translateY(-50%);
`;
// 4. State Management
let lastAltTime = 0;
let hideTimeout = null;
let globalInputCounter = 0; // Ensures every found input gets a unique ID
// 5. Core Conversion Logic
function convertText(inputElement) {
if (!inputElement || !inputElement.value) return;
const original = inputElement.value;
const converted = converter(original);
if (original !== converted) {
inputElement.value = converted;
// Dispatch events for frameworks (Vue/React)
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
}
}
// 6. Main Logic: Attach to Inputs
function attachListeners() {
const inputs = document.querySelectorAll(TARGET_SELECTORS.join(','));
inputs.forEach((input) => {
// A. Assign Unique ID (The "Solid Binding" Anchor)
if (!input.dataset.t2sId) {
// Assign an ID like 't2s-input-0', 't2s-input-1' based on discovery order
input.dataset.t2sId = `t2s-input-${globalInputCounter++}`;
}
// Skip if event listeners are already attached
if (input.dataset.t2sAttached) return;
input.dataset.t2sAttached = "true";
// B. Handle Focus
input.addEventListener('focus', () => {
// Clear any pending hide requests
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
// BINDING STEP: Link button to this specific input ID
btn.dataset.t2cBinding = input.dataset.t2sId;
// Move button to this input's container
if (input.parentNode) {
input.parentNode.style.position = 'relative';
input.parentNode.insertBefore(btn, input.nextSibling);
btn.style.display = 'block';
}
});
// C. Handle Blur
input.addEventListener('blur', () => {
hideTimeout = setTimeout(() => {
const active = document.activeElement;
// If we moved focus to the button itself, or another input, don't hide
if (active !== btn && (!active.tagName || active.tagName !== 'INPUT')) {
btn.style.display = 'none';
// We do NOT clear the binding here, preserving the link
// in case the user clicks the button immediately after blur.
}
}, 200);
});
});
}
// 7. Button Click Handler (Using Data Binding)
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
// RETRIEVAL STEP: Find input using the binding ID
const boundId = btn.dataset.t2cBinding;
if (boundId) {
const targetInput = document.querySelector(`input[data-t2s-id="${boundId}"]`);
if (targetInput) {
convertText(targetInput);
targetInput.focus();
}
}
});
// 8. Alt Double-Click Handler
document.addEventListener('keyup', (e) => {
if (e.key === 'Control') {
const now = Date.now();
if (now - lastAltTime < 400) {
// Use document.activeElement as source of truth for hotkey
const active = document.activeElement;
if (active && active.tagName === 'INPUT' && active.dataset.t2sId) {
convertText(active);
}
}
lastAltTime = now;
}
});
// 9. Observer
const observer = new MutationObserver(() => {
attachListeners();
});
observer.observe(document.body, { childList: true, subtree: true });
// Initial run
attachListeners();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment