// ==UserScript== // @name Sable RTL // @author Amir Aref // @version 0.1.0 // @description Fixes the direction of RTL languages in Sable Matrix Client // @match https://cinny.khce.ir/* // @match https://sable.khce.ir/* // @grant none // ==/UserScript== // room meesages list timeline //
; (function () { "use strict"; const RTL_REGEX = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/; const MESSAGE_LIST_SELECTORS = [ '[class*="RoomTimeline_messageList__"]' ]; const MESSAGE_ITEM_SELECTORS = [ '[class*="RoomMessage_message"]', '[class*="layout_MessageBase__"]' ]; const styleId = 'sable-rtl-fix-style'; if (!document.getElementById(styleId)) { const style = document.createElement('style'); style.id = styleId; style.textContent = ` .sable-rtl-applied { direction: rtl !important; unicode-bidi: plaintext !important; text-align: right !important; } `; document.head.appendChild(style); } function matchesAnySelector(elem, selectors) { if (!elem || !selectors.length) return false; const combined = selectors.join(','); return elem.matches(combined); } function closestSelector(elem, selectors) { if (!elem || !selectors.length) return null; const combined = selectors.join(','); return elem.closest(combined); } function isInMessageList(elem) { return Boolean(closestSelector(elem, MESSAGE_LIST_SELECTORS)); } function applyRTLToElement(elem) { if (!elem || elem.nodeType !== Node.ELEMENT_NODE) return; if (elem.classList.contains('sable-rtl-applied')) return; elem.classList.add('sable-rtl-applied'); elem.setAttribute('dir', 'auto'); } function applyRTLToTextNode(textNode) { if (!textNode || textNode.nodeType !== Node.TEXT_NODE) return; const elem = textNode.parentElement; if (!elem) return; if (!isInMessageList(elem)) return; const txt = textNode.textContent || ""; if (RTL_REGEX.test(txt)) { const messageItem = closestSelector(elem, MESSAGE_ITEM_SELECTORS); if (messageItem) applyRTLToElement(messageItem); applyRTLToElement(elem); } } function applyRTLToNode(node) { if (!node) return; if (node.nodeType === Node.TEXT_NODE) { applyRTLToTextNode(node); return; } if (node.nodeType !== Node.ELEMENT_NODE) return; const elem = node; if (!isInMessageList(elem)) return; const walker = document.createTreeWalker(elem, NodeFilter.SHOW_TEXT, null, false); let cur; while ((cur = walker.nextNode())) { applyRTLToTextNode(cur); } } function initialScan(root) { if (!root) root = document.body; const messageLists = root.querySelectorAll(MESSAGE_LIST_SELECTORS.join(',')); if (!messageLists.length) return; for (const list of messageLists) { const walker = document.createTreeWalker(list, NodeFilter.SHOW_TEXT, null, false); let cur; while ((cur = walker.nextNode())) { applyRTLToNode(cur); } } } initialScan(document.body); const observer = new MutationObserver((mutations) => { for (const mut of mutations) { if (mut.type === 'childList') { for (const nd of mut.addedNodes) { if (nd.nodeType === Node.ELEMENT_NODE) { const element = nd; if (matchesAnySelector(element, MESSAGE_LIST_SELECTORS)) { initialScan(element); continue; } if (isInMessageList(element)) { initialScan(element); applyRTLToNode(element); } } else { applyRTLToNode(nd); } } } else if (mut.type === 'characterData') { applyRTLToNode(mut.target); } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); })();