From 0e4e73fd076ce8a5dfee48a3b9cbb84ba70b73c3 Mon Sep 17 00:00:00 2001 From: Amir Aref Date: Thu, 30 Apr 2026 17:08:49 +0330 Subject: [PATCH] add sable-rtl fix script for tampermonkey ad-on --- tampermonkey/sable-rtl.js | 139 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tampermonkey/sable-rtl.js diff --git a/tampermonkey/sable-rtl.js b/tampermonkey/sable-rtl.js new file mode 100644 index 0000000..587536a --- /dev/null +++ b/tampermonkey/sable-rtl.js @@ -0,0 +1,139 @@ + +// ==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 + }); + +})();