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
+ });
+
+})();