您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【回归本源】放弃复杂的祖先查找,回归简单直接的元素处理,确保LI和它的父容器OL/UL都被正确设置RTL。
// ==UserScript== // @name 【v7.6 · 回归初心最终版】全局字体与RTL自适应 // @namespace http://tampermonkey.net/ // @version 7.6 // @license MIT // @description 【回归本源】放弃复杂的祖先查找,回归简单直接的元素处理,确保LI和它的父容器OL/UL都被正确设置RTL。 // @author YourName & AI Assistant // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-start // ==/UserScript== (function() { 'use strict'; // --- 配置部分 --- const DEFAULTS = { fontName: 'ukij ekran', enableFontReplace: true, enableRtl: true, excludedSites: '', debounceTime: 150 }; const config = { fontName: GM_getValue('fontName', DEFAULTS.fontName), enableFontReplace: GM_getValue('enableFontReplace', DEFAULTS.enableFontReplace), enableRtl: GM_getValue('enableRtl', DEFAULTS.enableRtl), excludedSites: GM_getValue('excludedSites', DEFAULTS.excludedSites).split(',').map(s => s.trim()).filter(Boolean), debounceTime: GM_getValue('debounceTime', DEFAULTS.debounceTime) }; if (config.excludedSites.some(site => window.location.hostname.includes(site))) return; const newFont = `"${config.fontName}"`; const rtlLangRegex = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/; // --- 样式注入 (Shadow DOM 穿透) --- const styleContent = `:host, * { font-family: ${newFont}, sans-serif !important; }`; function applyStyles(root) { if (!config.enableFontReplace || root.querySelector('.gm-font-style')) return; try { const style = document.createElement('style'); style.className = 'gm-font-style'; style.textContent = styleContent; root.appendChild(style); } catch (e) {} } const originalAttachShadow = Element.prototype.attachShadow; Element.prototype.attachShadow = function (options) { const shadowRoot = originalAttachShadow.call(this, options); applyStyles(shadowRoot); return shadowRoot; }; // --- 【RTL 核心逻辑 · 回归初心版】 --- function runFullScan(scopeNode) { if (!config.enableRtl || !scopeNode) return; const elementsToProcess = new Set(); const walker = document.createTreeWalker(scopeNode, NodeFilter.SHOW_TEXT, { acceptNode: node => rtlLangRegex.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }); while(walker.nextNode()) { let el = walker.currentNode.parentElement; if (el && !el.hasAttribute('dir')) { // 1. 添加文本的直接父元素 elementsToProcess.add(el); // 2. 如果是列表项,也添加其父列表容器 if (el.tagName === 'LI') { const listParent = el.parentElement; if (listParent && (listParent.tagName === 'UL' || listParent.tagName === 'OL')) { elementsToProcess.add(listParent); } } } } // 3. 一次性处理所有收集到的元素 elementsToProcess.forEach(el => { el.dir = 'rtl'; }); // 递归处理 Shadow DOM scopeNode.querySelectorAll('*').forEach(el => { if (el.shadowRoot) runFullScan(el.shadowRoot); }); } // --- 【输入框智能切换逻辑】 --- function handleActiveInput() { if (!config.enableRtl) return; const activeEl = document.activeElement; if (activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) { const text = activeEl.isContentEditable ? activeEl.textContent : activeEl.value; if (rtlLangRegex.test(text)) { activeEl.dir = 'rtl'; } else { activeEl.removeAttribute('dir'); } } } // --- 工具函数与事件监听 --- function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } const observer = new MutationObserver(debounce(() => { handleActiveInput(); runFullScan(document.body); }, config.debounceTime)); function init() { applyStyles(document.head); document.querySelectorAll('*').forEach(el => { if (el.shadowRoot) applyStyles(el.shadowRoot); }); if (document.body) { runFullScan(document.body); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); } document.addEventListener('keyup', handleActiveInput, true); } // --- 设置菜单 (完整版) --- function showSettingsDialog() { if (document.getElementById('gm-font-config-container')) return; const dialogHTML = ` <div id="gm-font-config-overlay"></div> <div id="gm-font-config-dialog"> <h2>全局字体与RTL配置</h2> <div class="gm-config-form"> <div class="gm-form-group"> <label for="gm-font-name">要应用的字体名称:</label> <input type="text" id="gm-font-name" placeholder="例如: ukij ekran"> </div> <div class="gm-form-group gm-checkbox-group"> <input type="checkbox" id="gm-enable-font-replace"> <label for="gm-enable-font-replace">启用全局字体替换</label> </div> <div class="gm-form-group gm-checkbox-group"> <input type="checkbox" id="gm-enable-rtl"> <label for="gm-enable-rtl">启用维吾尔文自动RTL</label> </div> <div class="gm-form-group"> <label for="gm-excluded-sites">排除列表 (网站域名, 用逗号分隔):</label> <textarea id="gm-excluded-sites" rows="3"></textarea> </div> </div> <div class="gm-dialog-buttons"> <button id="gm-save-btn" class="gm-btn gm-btn-primary">保存并刷新</button> <button id="gm-cancel-btn" class="gm-btn">取消</button> </div> </div>`; const dialogCSS = ` #gm-font-config-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.6); z-index: 999999998; } #gm-font-config-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 450px; max-width: 90%; background-color: #fff; color: #333; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); z-index: 999999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; padding: 20px 25px; box-sizing: border-box; } #gm-font-config-dialog h2 { margin: 0 0 20px; font-size: 20px; text-align: center; color: #1a1a1a; } .gm-form-group { margin-bottom: 15px; } .gm-form-group label { display: block; margin-bottom: 5px; font-weight: 500; font-size: 14px; } .gm-form-group input[type="text"], .gm-form-group textarea { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box; } .gm-form-group textarea { resize: vertical; } .gm-checkbox-group { display: flex; align-items: center; } .gm-checkbox-group input[type="checkbox"] { margin-right: 10px; width: 16px; height: 16px; } .gm-checkbox-group label { margin-bottom: 0; } .gm-dialog-buttons { margin-top: 25px; text-align: right; } .gm-btn { padding: 10px 18px; border: none; border-radius: 5px; font-size: 14px; font-weight: 500; cursor: pointer; margin-left: 10px; transition: background-color 0.2s; } .gm-btn-primary { background-color: #007bff; color: white; } .gm-btn-primary:hover { background-color: #0056b3; } .gm-btn:not(.gm-btn-primary) { background-color: #e0e0e0; color: #333; } .gm-btn:not(.gm-btn-primary):hover { background-color: #c7c7c7; }`; const container = document.createElement('div'); container.id = 'gm-font-config-container'; container.innerHTML = `<style>${dialogCSS}</style>${dialogHTML}`; (document.body || document.documentElement).appendChild(container); document.getElementById('gm-font-name').value = config.fontName; document.getElementById('gm-enable-font-replace').checked = config.enableFontReplace; document.getElementById('gm-enable-rtl').checked = config.enableRtl; document.getElementById('gm-excluded-sites').value = config.excludedSites.join(', '); const closeDialog = () => { document.removeEventListener('keydown', handleEscKey); container.remove(); }; const handleEscKey = (e) => { if (e.key === 'Escape') closeDialog(); }; document.addEventListener('keydown', handleEscKey); document.getElementById('gm-save-btn').addEventListener('click', () => { GM_setValue('fontName', document.getElementById('gm-font-name').value); GM_setValue('enableFontReplace', document.getElementById('gm-enable-font-replace').checked); GM_setValue('enableRtl', document.getElementById('gm-enable-rtl').checked); GM_setValue('excludedSites', document.getElementById('gm-excluded-sites').value); closeDialog(); alert('配置已保存!页面即将刷新以应用新设置。'); window.location.reload(); }); document.getElementById('gm-cancel-btn').addEventListener('click', closeDialog); document.getElementById('gm-font-config-overlay').addEventListener('click', closeDialog); } // --- 脚本入口 --- GM_registerMenuCommand('⚙️ 配置脚本 (v7.6 回归初心版)', showSettingsDialog); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();