您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically sets right-to-left (RTL) direction in ChatGPT's and Gemini's input fields when typing Arabic or Persian.
// ==UserScript== // @name Auto RTL Input // @namespace https://github.com/sinazadeh/userscripts // @match *://*.chatgpt.com/* // @match *://*.openai.com/* // @match *://gemini.google.com/* // @grant none // @version 1.0.1 // @author - // @description Automatically sets right-to-left (RTL) direction in ChatGPT's and Gemini's input fields when typing Arabic or Persian. // @run-at document-idle // @license MIT // ==/UserScript== /* jshint esversion: 11 */ (function () { 'use strict'; // This regex detects characters in the Arabic, Thaana, and other Arabic-based scripts. const isRTLText = text => /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF]/.test(text); function setDirection(el) { const text = el.innerText || el.textContent || el.value || ''; // Set direction to 'rtl' if RTL text is detected, otherwise 'ltr'. const dir = isRTLText(text.trim()) ? 'rtl' : 'ltr'; if (el.getAttribute('dir') !== dir) { el.setAttribute('dir', dir); // Also set CSS direction as backup el.style.direction = dir; } } function attachListeners(el) { if (!el || el.dataset.rtlAttached) return; // Do not attach more than once el.dataset.rtlAttached = 'true'; console.log('RTL Script: Attached to element:', el); const update = () => setDirection(el); el.addEventListener('input', update); el.addEventListener('keyup', update); el.addEventListener('keydown', update); el.addEventListener('focus', update); // Use a small timeout on paste to allow the content to render. el.addEventListener('paste', () => setTimeout(update, 10)); update(); // Run once on initial attachment } function findAndAttach() { // ChatGPT selectors (multiple fallbacks) const chatGPTSelectors = [ "div.ProseMirror[contenteditable='true']", "[data-testid='user-message'] div[contenteditable='true']", "div[contenteditable='true'][data-id]", ]; // Gemini selectors (multiple fallbacks) const geminiSelectors = [ 'div[role="textbox"][contenteditable="true"]', 'div[contenteditable="true"][data-testid]', 'div[contenteditable="true"][placeholder]', 'textarea[placeholder*="Enter a prompt"]', 'div[contenteditable="true"]', 'rich-textarea div[contenteditable="true"]', '[data-testid="input-area"] div[contenteditable="true"]', ]; const allSelectors = [...chatGPTSelectors, ...geminiSelectors]; function processElements(selector) { const elements = document.querySelectorAll(selector); for (const el of elements) { if ( el && (el.getAttribute('contenteditable') === 'true' || el.tagName.toLowerCase() === 'textarea') ) { const rect = el.getBoundingClientRect(); if (rect.width > 50 && rect.height > 20) { attachListeners(el); } } } } for (const selector of allSelectors) { processElements(selector); } // Special handling for dynamically created elements // Look for any contenteditable div that might be an input const allContentEditable = document.querySelectorAll( 'div[contenteditable="true"]', ); allContentEditable.forEach(el => { // Check if element looks like an input (has certain characteristics) const rect = el.getBoundingClientRect(); const hasInputLikeParent = el.closest('[role="textbox"]') || el.closest('form') || el.closest('[data-testid]') || el.parentElement?.className.includes('input'); if (rect.width > 100 && rect.height > 30 && hasInputLikeParent) { attachListeners(el); } }); } // More aggressive observation for SPAs const observer = new MutationObserver(mutations => { let shouldCheck = false; mutations.forEach(mutation => { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { // Check if new node contains input elements if ( node.querySelector && (node.querySelector('[contenteditable="true"]') || node.querySelector('textarea') || node.querySelector('[role="textbox"]')) ) { shouldCheck = true; } } }); } }); if (shouldCheck) { // Small delay to ensure elements are fully rendered setTimeout(findAndAttach, 100); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['contenteditable', 'role'], }); // Initial run when the script loads setTimeout(findAndAttach, 500); // Also run on page focus (for navigation) window.addEventListener('focus', () => setTimeout(findAndAttach, 200)); // Debug: Log when script loads console.log( 'RTL Auto Direction Script loaded for:', window.location.hostname, ); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址