codex-rtl 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,122 @@
1
1
  # codex-rtl
2
2
 
3
- This is a placeholder package for `codex-rtl`.
3
+ This CLI tool automatically injects a sophisticated RTL engine into Codex, adding support for Persian (Farsi), Arabic, Hebrew, and other RTL languages, along with a sleek UI to configure fonts and settings on the fly.
4
4
 
5
- It will be developed into a tool for handling RTL (Right-to-Left) formatting/conversions or similar operations for Codex.
5
+ ## Features
6
6
 
7
- ## Installation / Usage
7
+ - **Smart Auto-Direction**: Automatically detects if a paragraph is RTL or LTR and aligns it perfectly.
8
+ - **Force RTL Mode**: Want everything aligned to the right? Just toggle the switch.
9
+ - **Custom Typography**: Define different fonts for your RTL text, English text, and Code blocks!
10
+ - **Line Height Control**: A precise slider to adjust the line height for better readability.
11
+ - **Persian Keyboard Fix**: Maps `Shift + 2` to type `@` instead of `٬` on Persian keyboards.
12
+ - **Beautiful Settings Panel**: A floating, non-intrusive UI widget at the bottom right corner.
13
+ - **Vazirmatn Built-in**: Comes with the beautiful Vazirmatn variable font by default.
14
+ - **Theme Compatibility**: Seamlessly adapts colors based on Codex's active theme, using native color variables.
8
15
 
9
- You can run this package directly via `npx`:
16
+ ## Installation
10
17
 
18
+ You don't need to download any files. Just run the following command in your terminal:
19
+
20
+ ### macOS / Linux
21
+ Because the tool needs to modify the Codex application files, you must run it with `sudo`:
22
+ ```bash
23
+ sudo npx codex-rtl
24
+ ```
25
+ > **macOS Users:** If you get a "Permission Denied" error even with sudo, ensure your terminal (e.g. Terminal, iTerm2, VS Code) has **App Management** permissions enabled in `System Settings > Privacy & Security > App Management`.
26
+
27
+ ### Windows
28
+ Open **PowerShell** or **Command Prompt** as **Administrator** (Right-click -> Run as Administrator), then run:
11
29
  ```bash
12
30
  npx codex-rtl
13
31
  ```
32
+
33
+ > **Note:** You must have [Node.js](https://nodejs.org) installed on your system to run this command.
34
+
35
+ > [!WARNING]
36
+ > **App Updates:** Since updating the Codex application overwrites its internal files, the RTL patch will be removed. You will need to run the installation command again after each update to re-apply the patch.
37
+
38
+ ## Restoring to Original (Uninstall)
39
+
40
+ If you ever want to revert Codex back to its original state (before the patch), simply run the command with the `--restore` flag:
41
+
42
+ ```bash
43
+ sudo npx codex-rtl --restore
44
+ ```
45
+ *(On Windows, run without `sudo` in an Administrator terminal)*
46
+
47
+ ## How it works
48
+
49
+ This CLI tool:
50
+ 1. Locates your Codex installation.
51
+ 2. Creates a safe backup of the original `app.asar` file.
52
+ 3. Extracts the application and safely injects the Smart RTL Engine into the core logic.
53
+ 4. Repacks the application so you can start using it immediately.
54
+
55
+ ## Contributing
56
+
57
+ Feel free to open issues or submit pull requests. Let's make Codex accessible and beautiful for everyone!
58
+
59
+ ---
60
+
61
+ <div dir="rtl">
62
+
63
+ # پروژه Codex Smart RTL
64
+
65
+ یک پچِ هوشمند و زیبا برای پشتیبانی از زبان‌های راست‌به‌چپ (RTL) در نرم‌افزار Codex.
66
+
67
+ این ابزارِ خط فرمان (CLI) به صورت کاملاً خودکار یک موتور پیشرفتهٔ RTL را به هستهٔ برنامهٔ Codex تزریق می‌کند تا از زبان‌های فارسی، عربی و عبری به بهترین شکل پشتیبانی شود. همچنین یک پنل تنظیماتِ (UI) برای تغییر زندهٔ فونت‌ها در اختیار شما قرار می‌دهد.
68
+
69
+ ## امکانات
70
+
71
+ - **راست‌چین هوشمند (Smart Auto-Direction)**: سیستم به طور خودکار تشخیص می‌دهد که پاراگراف شما با حرف انگلیسی شروع شده یا فارسی، و چیدمان را بر همان اساس تنظیم می‌کند.
72
+ - **حالت راست‌چینِ اجباری (Force RTL Mode)**: دوست دارید همه چیز (حتی پیام‌های انگلیسی) کاملاً در سمت راست قرار بگیرند؟ فقط کافیست سوئیچ را روشن کنید!
73
+ - **تنظیماتِ پیشرفتهِ فونت**: می‌توانید برای متون فارسی، متون انگلیسی و کدهای برنامه‌نویسیِ داخل چت، فونت‌های کاملاً جداگانه‌ای تعریف کنید.
74
+ - **کنترل فاصلهٔ خطوط (Line Height)**: با استفاده از اسلایدر می‌توانید فاصلهٔ خطوط را برای خوانایی بهتر متن تنظیم کنید.
75
+ - **حل مشکل کیبورد فارسی**: این ابزار کلید ترکیبی `Shift + 2` روی کیبورد فارسی را اصلاح می‌کند تا به جای «٬» علامت `@` تایپ شود.
76
+ - **پنل تنظیمات زیبا**: تمام این تنظیمات در یک ویجتِ کوچک، مدرن و شناور در پایینِ صفحه قرار گرفته‌اند.
77
+ - **فونت وزیرمتن**: فونت زیبای Vazirmatn Variable به صورت پیش‌فرض در این افزونه گنجانده شده است.
78
+ - **همگام‌سازی خودکار با تم (Theme Compatibility)**: هماهنگی و تغییر پویای رنگ سوییچ‌های پنل با تغییر تم رنگی Codex به صورت کاملاً بومی.
79
+
80
+ ## آموزش نصب
81
+
82
+ بدون نیاز به دانلود هیچ فایلی، فقط کافیست دستور زیر را در ترمینال سیستم خود اجرا کنید:
83
+
84
+ ### در مک (macOS) و لینوکس
85
+ از آنجایی که این ابزار قرار است فایل‌های سیستمی Codex را ویرایش کند، باید حتماً دسترسی `sudo` داشته باشد:
86
+ ```bash
87
+ sudo npx codex-rtl
88
+ ```
89
+ > **کاربران مک (macOS):** اگر با وجود استفاده از sudo باز هم خطای Permission Denied دریافت کردید، باید به ترمینال خود (مثل Terminal، iTerm2 یا VS Code) دسترسی **App Management** بدهید. برای این کار به مسیر `System Settings > Privacy & Security > App Management` بروید و دسترسی ترمینال خود را فعال کنید.
90
+
91
+ ### در ویندوز
92
+ برنامهٔ **PowerShell** یا **Command Prompt** را در حالت **Administrator** (راست‌کلیک -> Run as Administrator) باز کنید و دستور زیر را بنویسید:
93
+ ```bash
94
+ npx codex-rtl
95
+ ```
96
+
97
+ > **نکته:** برای اجرای این دستور باید حتماً [Node.js](https://nodejs.org) روی سیستم شما نصب باشد.
98
+
99
+ > [!WARNING]
100
+ > **به‌روزرسانی برنامه:** از آنجا که آپدیت کردنِ برنامهٔ Codex کدهای آن را بازنویسی می‌کند، پچِ اعمال‌شده از بین خواهد رفت و لازم است پس از هر بار آپدیت، دستور نصب را مجدداً اجرا کنید.
101
+
102
+ ## بازگردانی به حالت اولیه (Uninstall)
103
+
104
+ اگر زمانی خواستید Codex را به حالتِ اولیه (قبل از نصب این پچ) برگردانید، فقط کافیست دستور بالا را با فلگ `--restore` اجرا کنید:
105
+
106
+ ```bash
107
+ sudo npx codex-rtl --restore
108
+ ```
109
+ *(کاربران ویندوز این دستور را بدون `sudo` و در یک ترمینال ادمین اجرا کنند)*
110
+
111
+ ## این ابزار چگونه کار می‌کند؟
112
+
113
+ 1. ابزار به صورت خودکار محل نصب Codex را روی سیستم شما پیدا می‌کند.
114
+ 2. یک نسخهٔ پشتیبانِ امن از فایل اوریجینالِ `app.asar` تهیه می‌کند.
115
+ 3. فایل را استخراج کرده و کدهای موتورِ RTL را به ایمن‌ترین شکل ممکن به هستهٔ برنامه تزریق می‌کند.
116
+ 4. در نهایت برنامه را مجدداً بسته‌بندی می‌کند تا بتوانید بلافاصله از آن لذت ببرید.
117
+
118
+ ## مشارکت در توسعه
119
+
120
+ با کمال میل از نظرات، گزارشِ باگ‌ها و Pull Request های شما استقبال می‌شود.
121
+
122
+ </div>
Binary file
package/bin/index.js CHANGED
@@ -1,3 +1,254 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ import picocolors from 'picocolors';
7
+ import ora from 'ora';
8
+ import prompts from 'prompts';
9
+ import * as asar from '@electron/asar';
2
10
 
3
- console.log("codex-rtl has been successfully reserved on npm. Stay tuned for future releases!");
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const { blue, cyan, green, red, yellow, bold } = picocolors;
14
+
15
+ const pkgPath = path.join(__dirname, '..', 'package.json');
16
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
17
+ console.log(bold(cyan(`\n✨ Codex Smart RTL Patcher v${pkg.version}\n`)));
18
+
19
+ function getDefaultPath() {
20
+ if (os.platform() === 'darwin') {
21
+ return '/Applications/Codex.app/Contents/Resources/app.asar';
22
+ } else if (os.platform() === 'win32') {
23
+ return path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Codex', 'resources', 'app.asar');
24
+ } else {
25
+ return '/opt/Codex/resources/app.asar';
26
+ }
27
+ }
28
+
29
+ async function getAsarPath() {
30
+ let asarPath = getDefaultPath();
31
+ if (fs.existsSync(asarPath)) {
32
+ console.log(blue(`ℹ Found Codex installation at:`));
33
+ console.log(` ${asarPath}\n`);
34
+ return asarPath;
35
+ }
36
+
37
+ console.log(yellow(`⚠ Could not find Codex at default location.`));
38
+ const response = await prompts({
39
+ type: 'text',
40
+ name: 'customPath',
41
+ message: 'Please enter the full path to app.asar:'
42
+ });
43
+
44
+ if (!response.customPath || !fs.existsSync(response.customPath)) {
45
+ console.error(red('\n✖ Invalid path. Aborting.\n'));
46
+ process.exit(1);
47
+ }
48
+ return response.customPath;
49
+ }
50
+
51
+ const args = process.argv.slice(2);
52
+ const isRestore = args.includes('--restore');
53
+
54
+ async function main() {
55
+ const asarPath = await getAsarPath();
56
+ const backupPath = asarPath + '.bak';
57
+
58
+ if (isRestore) {
59
+ if (!fs.existsSync(backupPath)) {
60
+ console.error(red('✖ No backup found to restore.\n'));
61
+ process.exit(1);
62
+ }
63
+ const spinner = ora('Restoring original app.asar...').start();
64
+ try {
65
+ fs.copyFileSync(backupPath, asarPath);
66
+ spinner.succeed('Successfully restored original Codex!\n');
67
+ process.exit(0);
68
+ } catch (e) {
69
+ spinner.fail('Failed to restore.');
70
+ console.error(red(e.message));
71
+ process.exit(1);
72
+ }
73
+ }
74
+
75
+ const spinner = ora('Checking permissions and backing up...').start();
76
+ try {
77
+ fs.accessSync(path.dirname(asarPath), fs.constants.W_OK);
78
+ if (!fs.existsSync(backupPath)) {
79
+ fs.copyFileSync(asarPath, backupPath);
80
+ }
81
+ } catch (e) {
82
+ spinner.fail('Permission Denied.');
83
+ console.error(red('\nSystem Error: ' + e.message));
84
+ if (os.platform() === 'win32') {
85
+ console.error(yellow('\nPlease run your terminal (PowerShell/CMD) as Administrator and try again.\n'));
86
+ } else if (os.platform() === 'darwin') {
87
+ console.error(yellow('\nPlease ensure you run this command with sudo (if required).'));
88
+ console.error(yellow('On macOS, you might also need to give your terminal "App Management" permission.'));
89
+ console.error(yellow('Go to: System Settings > Privacy & Security > App Management'));
90
+ console.error(yellow('And enable the toggle for your terminal, then try again.\n'));
91
+ } else {
92
+ console.error(yellow('\nPlease run this command with sudo.\n'));
93
+ }
94
+ process.exit(1);
95
+ }
96
+
97
+ const extractDir = path.join(path.dirname(asarPath), 'app-extracted-codex-rtl-temp');
98
+ spinner.text = 'Extracting app.asar (this may take a few seconds)...';
99
+ try {
100
+ if (fs.existsSync(extractDir)) {
101
+ fs.rmSync(extractDir, { recursive: true, force: true });
102
+ }
103
+ asar.extractAll(asarPath, extractDir);
104
+ } catch (e) {
105
+ spinner.fail('Failed to extract ASAR.');
106
+ console.error(red(e.message));
107
+ process.exit(1);
108
+ }
109
+
110
+ spinner.text = 'Injecting RTL features and enabling DevTools...';
111
+ try {
112
+ const buildDir = path.join(extractDir, '.vite', 'build');
113
+ if (!fs.existsSync(buildDir)) {
114
+ throw new Error('.vite/build not found in ASAR. Unsupported Codex version.');
115
+ }
116
+
117
+ // 1. Find main-*.js
118
+ const files = fs.readdirSync(buildDir);
119
+ const mainFiles = files.filter(f => f.startsWith('main-') && f.endsWith('.js'));
120
+
121
+ for (const mainFile of mainFiles) {
122
+ const mainJsPath = path.join(buildDir, mainFile);
123
+ let mainCode = fs.readFileSync(mainJsPath, 'utf8');
124
+
125
+ // Force devTools: true
126
+ const devtoolsRegex = /devTools:\s*this\.options\.allowDevtools/g;
127
+ if (devtoolsRegex.test(mainCode)) {
128
+ mainCode = mainCode.replace(devtoolsRegex, 'devTools:true');
129
+ fs.writeFileSync(mainJsPath, mainCode, 'utf8');
130
+ }
131
+ }
132
+
133
+ // 2. Inject RTL Loader into bootstrap.js
134
+ const bootstrapPath = path.join(buildDir, 'bootstrap.js');
135
+ if (!fs.existsSync(bootstrapPath)) {
136
+ throw new Error('.vite/build/bootstrap.js not found in ASAR. Unsupported Codex version.');
137
+ }
138
+
139
+ let bootstrapCode = fs.readFileSync(bootstrapPath, 'utf8');
140
+
141
+ if (bootstrapCode.includes('/* CODEX RTL PATCH START */')) {
142
+ spinner.succeed('Codex is already patched!');
143
+ fs.rmSync(extractDir, { recursive: true, force: true });
144
+ console.log(green('\n✨ Enjoy your RTL experience!\n'));
145
+ process.exit(0);
146
+ }
147
+
148
+ const loaderCode = `
149
+ /* CODEX RTL PATCH START */
150
+ try {
151
+ const { app } = require('electron');
152
+ app.on('browser-window-created', (event, win) => {
153
+ // DevTools Shortcut Handler (F12, Cmd+Option+I, Ctrl+Shift+I)
154
+ win.webContents.on('before-input-event', (ev, input) => {
155
+ const isShortcut = input.key === 'F12' ||
156
+ (input.control && input.shift && input.key.toLowerCase() === 'i') ||
157
+ (input.meta && input.alt && input.key.toLowerCase() === 'i');
158
+ if (isShortcut && input.type === 'keyDown') {
159
+ try {
160
+ win.webContents.toggleDevTools();
161
+ ev.preventDefault();
162
+ } catch (e) {}
163
+ }
164
+ });
165
+
166
+ win.webContents.on('console-message', (ev, level, message) => {
167
+ if (typeof message === 'string' && message.startsWith('SAVE_RTL_CONFIG|')) {
168
+ try {
169
+ const data = message.substring(16);
170
+ const configPath = require('path').join(require('os').homedir(), '.codex-rtl.json');
171
+ require('fs').writeFileSync(configPath, data);
172
+ } catch (e) {}
173
+ }
174
+ });
175
+
176
+ win.webContents.on('dom-ready', () => {
177
+ try {
178
+ const path = require('path');
179
+ const fs = require('fs');
180
+ const fontPath = path.join(__dirname, 'Vazirmatn-Variable.woff2');
181
+ const payloadPath = path.join(__dirname, 'payload.js');
182
+ if (!fs.existsSync(fontPath) || !fs.existsSync(payloadPath)) return;
183
+
184
+ const fontBase64 = fs.readFileSync(fontPath).toString('base64');
185
+ let payload = fs.readFileSync(payloadPath, 'utf8');
186
+
187
+ // Read config
188
+ let rtlConfig = { faFont: '', enFont: '', codeFont: '', lh: '1.6', isRTL: true, forceRTL: false, fixAtSign: true };
189
+ try {
190
+ const configPath = path.join(require('os').homedir(), '.codex-rtl.json');
191
+ if (fs.existsSync(configPath)) {
192
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
193
+ rtlConfig = { ...rtlConfig, ...cfg };
194
+ }
195
+ } catch (e) {}
196
+
197
+ // Replace placeholders in payload
198
+ payload = payload.replace('__FONT_BASE64__', fontBase64);
199
+ payload = payload.replace('__RTL_CONFIG__', JSON.stringify(rtlConfig));
200
+
201
+ win.webContents.executeJavaScript(payload).catch(err => console.error("Failed to inject RTL:", err));
202
+ } catch (e) {
203
+ console.error("Failed to read RTL patch assets:", e);
204
+ }
205
+ });
206
+ });
207
+ } catch(e) {
208
+ console.error("RTL patch initialization failed:", e);
209
+ }
210
+ /* CODEX RTL PATCH END */
211
+ `;
212
+
213
+ // Append loader to bootstrap.js
214
+ bootstrapCode += '\n' + loaderCode;
215
+ fs.writeFileSync(bootstrapPath, bootstrapCode, 'utf8');
216
+
217
+ // 3. Copy font file
218
+ const fontSource = path.join(__dirname, 'Vazirmatn-Variable.woff2');
219
+ const fontDest = path.join(buildDir, 'Vazirmatn-Variable.woff2');
220
+ if (fs.existsSync(fontSource)) {
221
+ fs.copyFileSync(fontSource, fontDest);
222
+ }
223
+
224
+ // 4. Copy payload file
225
+ const payloadSource = path.join(__dirname, 'payload.js');
226
+ const payloadDest = path.join(buildDir, 'payload.js');
227
+ if (fs.existsSync(payloadSource)) {
228
+ fs.copyFileSync(payloadSource, payloadDest);
229
+ }
230
+
231
+ } catch (e) {
232
+ spinner.fail('Injection failed.');
233
+ console.error(red(e.message));
234
+ if (fs.existsSync(extractDir)) fs.rmSync(extractDir, { recursive: true, force: true });
235
+ process.exit(1);
236
+ }
237
+
238
+ spinner.text = 'Repacking app.asar (almost done)...';
239
+ try {
240
+ await asar.createPackage(extractDir, asarPath);
241
+ fs.rmSync(extractDir, { recursive: true, force: true });
242
+ spinner.succeed('Successfully patched Codex!');
243
+ console.log(green('\n✨ RTL Features and DevTools have been enabled. Please restart Codex to see the changes.\n'));
244
+ } catch (e) {
245
+ spinner.fail('Failed to repack ASAR.');
246
+ console.error(red(e.message));
247
+ process.exit(1);
248
+ }
249
+ }
250
+
251
+ main().catch(e => {
252
+ console.error(red('\n✖ An unexpected error occurred:'), e.message);
253
+ process.exit(1);
254
+ });
package/bin/payload.js ADDED
@@ -0,0 +1,454 @@
1
+ const fontBase64 = '__FONT_BASE64__';
2
+ const rtlConfig = __RTL_CONFIG__;
3
+
4
+ let isRTL = rtlConfig.isRTL !== false;
5
+ let forceRTL = rtlConfig.forceRTL === true;
6
+ let fixAtSign = rtlConfig.fixAtSign !== false;
7
+ let savedFaFont = rtlConfig.faFont || '';
8
+ let savedEnFont = rtlConfig.enFont || '';
9
+ let savedCodeFont = rtlConfig.codeFont || '';
10
+ let savedLH = rtlConfig.lh || '1.6';
11
+
12
+ if (!document.getElementById('rtl-widget-style')) {
13
+ let widgetStyle = document.createElement('style');
14
+ widgetStyle.id = 'rtl-widget-style';
15
+ widgetStyle.innerHTML = `
16
+ .rtl-widget-container {
17
+ position: fixed;
18
+ bottom: 16px;
19
+ right: 16px;
20
+ z-index: 99999;
21
+ direction: ltr;
22
+ font-family: inherit;
23
+ }
24
+ .rtl-widget-trigger {
25
+ width: 40px;
26
+ height: 40px;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ border-radius: 9999px;
31
+ background-color: var(--scrollbar-thumb, #555);
32
+ color: #fff;
33
+ cursor: pointer;
34
+ opacity: 0.8;
35
+ transition: all 0.3s ease-in-out;
36
+ }
37
+ .rtl-widget-trigger:hover {
38
+ transform: scale(1.1);
39
+ opacity: 1;
40
+ }
41
+ .rtl-widget-container:hover .rtl-widget-trigger {
42
+ opacity: 0;
43
+ transform: scale(0.5);
44
+ pointer-events: none;
45
+ }
46
+ .rtl-widget-panel {
47
+ position: absolute;
48
+ bottom: 0;
49
+ right: 0;
50
+ width: 270px;
51
+ transform: scale(0);
52
+ transform-origin: bottom right;
53
+ opacity: 0;
54
+ pointer-events: none;
55
+ transition: all 0.3s ease-in-out;
56
+ }
57
+ .rtl-widget-container:hover .rtl-widget-panel {
58
+ transform: scale(1);
59
+ opacity: 1;
60
+ pointer-events: auto;
61
+ }
62
+ .rtl-tooltip {
63
+ visibility: hidden;
64
+ opacity: 0;
65
+ transition: opacity 0.15s ease-in-out;
66
+ pointer-events: none;
67
+ position: absolute;
68
+ bottom: 100%;
69
+ left: 50%;
70
+ transform: translateX(-50%);
71
+ margin-bottom: 8px;
72
+ width: 200px;
73
+ padding: 8px;
74
+ border-radius: 8px;
75
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
76
+ z-index: 999999 !important;
77
+ white-space: normal;
78
+ text-align: center;
79
+ background-color: #202123 !important;
80
+ background-color: light-dark(#ffffff, #202123) !important;
81
+ border: 1px solid #4d4d4d !important;
82
+ border: 1px solid light-dark(#e5e7eb, #4d4d4d) !important;
83
+ color: var(--color-token-foreground, #fff) !important;
84
+ font-size: 11px;
85
+ line-height: 1.4;
86
+ }
87
+ .rtl-tooltip::after {
88
+ content: "";
89
+ position: absolute;
90
+ top: 100%;
91
+ left: 50%;
92
+ transform: translateX(-50%);
93
+ border-width: 5px;
94
+ border-style: solid;
95
+ border-color: #4d4d4d transparent transparent transparent !important;
96
+ border-color: light-dark(#e5e7eb, #4d4d4d) transparent transparent transparent !important;
97
+ }
98
+ .rtl-info-icon {
99
+ position: relative;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ z-index: 50;
104
+ opacity: 0.5;
105
+ transition: opacity 0.15s ease-in-out;
106
+ }
107
+ .rtl-info-icon:hover {
108
+ opacity: 1;
109
+ }
110
+ .rtl-info-icon:hover .rtl-tooltip {
111
+ visibility: visible;
112
+ opacity: 1;
113
+ }
114
+ .rtl-github-link {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ gap: 8px;
119
+ font-size: 12px;
120
+ font-weight: 600;
121
+ opacity: 0.7;
122
+ text-decoration: none;
123
+ padding-top: 4px;
124
+ padding-bottom: 2px;
125
+ transition: all 0.15s ease-in-out;
126
+ color: var(--color-token-foreground, #fff) !important;
127
+ }
128
+ .rtl-github-link:hover {
129
+ opacity: 1;
130
+ color: #ffd700 !important; /* Gold color */
131
+ }
132
+ `;
133
+ document.head.appendChild(widgetStyle);
134
+ }
135
+
136
+ const rtlStyle = document.createElement('style');
137
+ rtlStyle.id = 'codex-rtl-style';
138
+
139
+ const updateDynamicCSS = () => {
140
+ if (!isRTL) {
141
+ if (rtlStyle.parentNode) rtlStyle.parentNode.removeChild(rtlStyle);
142
+ return;
143
+ }
144
+
145
+ let faFontRule = '';
146
+ let faFontName = "'PersianOnlyFont'";
147
+
148
+ if (savedFaFont) {
149
+ faFontName = "'UserPersianFont', 'PersianOnlyFont'";
150
+ let baseFaFont = savedFaFont.replace(/[-\s]?Regular$/i, '');
151
+ faFontRule = `
152
+ @font-face {
153
+ font-family: 'UserPersianFont';
154
+ src: local('${savedFaFont}'), local('${baseFaFont}');
155
+ font-weight: 400;
156
+ unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
157
+ }
158
+ @font-face {
159
+ font-family: 'UserPersianFont';
160
+ src: local('${baseFaFont} Bold'), local('${baseFaFont}-Bold'), local('${baseFaFont}Bold');
161
+ font-weight: 700;
162
+ unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
163
+ }
164
+ `;
165
+ }
166
+
167
+ let enFontStr = savedEnFont ? `'${savedEnFont}', ui-sans-serif, system-ui, sans-serif` : 'ui-sans-serif, system-ui, sans-serif';
168
+ let codeFontStr = savedCodeFont ? `'${savedCodeFont}', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` : 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
169
+
170
+ let forceRtlStyle = forceRTL ? `
171
+ /* Force RTL layout for all text components excluding the widget panel */
172
+ p:not(.rtl-widget-container *),
173
+ li:not(.rtl-widget-container *),
174
+ h1:not(.rtl-widget-container *),
175
+ h2:not(.rtl-widget-container *),
176
+ h3:not(.rtl-widget-container *),
177
+ h4:not(.rtl-widget-container *),
178
+ h5:not(.rtl-widget-container *),
179
+ h6:not(.rtl-widget-container *),
180
+ textarea:not(.rtl-widget-container *),
181
+ [contenteditable="true"]:not(.rtl-widget-container *),
182
+ [contenteditable="true"] p:not(.rtl-widget-container *),
183
+ [data-lexical-text="true"]:not(.rtl-widget-container *) {
184
+ direction: rtl !important;
185
+ text-align: right !important;
186
+ unicode-bidi: isolate !important;
187
+ }
188
+ ` : '';
189
+
190
+ rtlStyle.textContent = `
191
+ ${faFontRule}
192
+ @font-face {
193
+ font-family: 'PersianOnlyFont';
194
+ src: url('data:font/woff2;base64,${fontBase64}') format('woff2');
195
+ font-weight: 100 900;
196
+ unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
197
+ }
198
+
199
+ :root, :host, html, body {
200
+ font-family: ${faFontName}, ${enFontStr}, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
201
+ --diffs-font-family: ${codeFontStr} !important;
202
+ --diffs-font-fallback: ${codeFontStr} !important;
203
+ }
204
+
205
+ /* Smart RTL using CSS unicode-bidi plaintext */
206
+ p, h1, h2, h3, h4, h5, h6, li, span, div, [role="presentation"] {
207
+ unicode-bidi: plaintext !important;
208
+ text-align: start !important;
209
+ }
210
+
211
+ /* Explicitly keep our widget panel LTR and left-aligned */
212
+ .rtl-widget-container, .rtl-widget-container * {
213
+ direction: ltr !important;
214
+ text-align: left !important;
215
+ unicode-bidi: isolate !important;
216
+ }
217
+
218
+ /* Force RTL rules if enabled */
219
+ ${forceRtlStyle}
220
+
221
+ /* Ensure code blocks are kept LTR and get the code font applied */
222
+ pre:not(.rtl-widget-container *),
223
+ code:not(.rtl-widget-container *),
224
+ pre:not(.rtl-widget-container *) *,
225
+ code:not(.rtl-widget-container *) *,
226
+ pre:not(.rtl-widget-container *) span,
227
+ code:not(.rtl-widget-container *) span,
228
+ [data-line] span {
229
+ unicode-bidi: isolate !important;
230
+ direction: ltr !important;
231
+ text-align: left !important;
232
+ font-family: ${codeFontStr} !important;
233
+ }
234
+
235
+ /* Text input fields and lexical text editors */
236
+ textarea:not(.rtl-widget-container *),
237
+ [contenteditable="true"]:not(.rtl-widget-container *),
238
+ [contenteditable="true"] p:not(.rtl-widget-container *),
239
+ [data-lexical-text="true"]:not(.rtl-widget-container *) {
240
+ unicode-bidi: plaintext !important;
241
+ text-align: start !important;
242
+ }
243
+
244
+ /* Adjust list padding when elements render as RTL */
245
+ ul:not(#_)[dir="rtl"], ol:not(#_)[dir="rtl"],
246
+ [dir="rtl"] ul:not(#_), [dir="rtl"] ol:not(#_) {
247
+ padding-left: 0 !important;
248
+ padding-right: 1.25rem !important;
249
+ }
250
+
251
+ /* Custom Line Height */
252
+ p:not(.rtl-widget-container *),
253
+ li:not(.rtl-widget-container *),
254
+ h1:not(.rtl-widget-container *),
255
+ h2:not(.rtl-widget-container *),
256
+ h3:not(.rtl-widget-container *),
257
+ [contenteditable="true"] p:not(.rtl-widget-container *),
258
+ [data-lexical-text="true"]:not(.rtl-widget-container *) {
259
+ line-height: ${savedLH} !important;
260
+ }
261
+ `;
262
+
263
+ if (!rtlStyle.parentNode) {
264
+ document.head.appendChild(rtlStyle);
265
+ }
266
+ };
267
+
268
+ // Initial apply
269
+ updateDynamicCSS();
270
+
271
+ // Global Shift+2 keyboard handler for @
272
+ document.addEventListener('keydown', (e) => {
273
+ if (!fixAtSign) return;
274
+ if (e.code === 'Digit2' && e.shiftKey) {
275
+ if (e.key === '٬' || e.key === '،') {
276
+ e.preventDefault();
277
+ document.execCommand('insertText', false, '@');
278
+ }
279
+ }
280
+ }, { capture: true });
281
+
282
+ // Floating widget for Codex RTL
283
+ const widgetWrapper = document.createElement('div');
284
+ widgetWrapper.className = 'rtl-widget-container';
285
+ widgetWrapper.innerHTML = `
286
+ <div class="rtl-widget-trigger">
287
+ <svg height="20" width="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M2 12h20"></path><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
288
+ </div>
289
+
290
+ <div class="rtl-widget-panel">
291
+ <div class="relative flex max-h-full min-h-0 flex-col rounded-3xl bg-token-dropdown-background pt-3 border border-token-border-default shadow-md">
292
+ <div class="flex flex-col gap-2 px-3 pb-3 pt-0 w-full h-full" style="display: flex; flex-direction: column; gap: 8px;">
293
+
294
+ <div class="border-b border-token-border-default pb-2 mb-1 text-center" style="text-align: center !important;">
295
+ <div class="electron:heading-lg heading-base truncate text-center" style="color: var(--color-token-foreground); display: flex !important; justify-content: center !important; text-align: center; width: 100%;">Codex Smart RTL</div>
296
+ </div>
297
+
298
+ <div class="flex items-center justify-between gap-4 px-1" style="display: flex; justify-content: space-between; align-items: center;">
299
+ <span id="rtl-toggle-label" class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">${isRTL ? 'Enabled' : 'Disabled'}</span>
300
+ <button id="rtl-toggle-btn" type="button" class="relative inline-flex items-center rounded-full transition-colors duration-200 h-6 w-11" style="background-color: ${isRTL ? 'var(--color-token-charts-blue, #339cff)' : '#555'}; border: none; cursor: pointer; height: 24px; width: 44px; border-radius: 9999px; position: relative;">
301
+ <span id="rtl-toggle-knob" class="inline-block transform rounded-full bg-white transition-transform h-4 w-4" style="margin-left: 4px; transform: ${isRTL ? 'translateX(20px)' : 'translateX(0)'}; transition: transform 0.2s; height: 16px; width: 16px; border-radius: 9999px; background: #fff; display: block;"></span>
302
+ </button>
303
+ </div>
304
+
305
+ <div id="rtl-settings-wrapper" class="flex flex-col gap-2 transition-all duration-300" style="position: relative; z-index: 10; opacity: ${isRTL ? '1' : '0.4'}; pointer-events: ${isRTL ? 'auto' : 'none'}; display: flex; flex-direction: column; gap: 8px; transition: opacity 0.3s;">
306
+ <div class="flex items-center justify-between gap-2 px-1 mt-1" style="display: flex; justify-content: space-between; align-items: center;">
307
+ <div style="display: flex; align-items: center; gap: 4px;">
308
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">Force RTL</span>
309
+ <div class="rtl-info-icon" style="color: var(--color-token-foreground); cursor: pointer; margin-left: 2px;">
310
+ <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 -960 960 960" fill="currentColor"><path d="M450-290h60V-520H450v230Zm52.92-307.75q9.38-9.29 9.38-23.02t-9.29-23.02T480-653.07t-23.02,9.29t-9.29,23.02t9.38,23.02T480-588.46t22.92-9.29ZM480.07-100q-78.84,0-148.2-29.92T211.18-211.13T129.93-331.76T100-479.93t29.92-148.2t81.21-120.68t120.63-81.25T479.93-860t148.2,29.92t120.68,81.21t81.25,120.63T860-480.07t-29.92,148.2T748.87-211.18T628.24-129.93T480.07-100ZM480-160q134,0 227-93t93-227T707-707T480-800T253-707T160-480t93,227t227,93Zm0-320Z"></path></svg>
311
+ <div class="rtl-tooltip">Forces RTL layout on all elements, even if the text starts with English characters.</div>
312
+ </div>
313
+ </div>
314
+ <button id="rtl-force-btn" type="button" class="relative inline-flex items-center rounded-full transition-colors duration-200 h-6 w-11" style="background-color: ${forceRTL ? 'var(--color-token-charts-blue, #339cff)' : '#555'}; border: none; cursor: pointer; height: 24px; width: 44px; border-radius: 9999px; position: relative;">
315
+ <span id="rtl-force-knob" class="inline-block transform rounded-full bg-white transition-transform h-4 w-4" style="margin-left: 4px; transform: ${forceRTL ? 'translateX(20px)' : 'translateX(0)'}; transition: transform 0.2s; height: 16px; width: 16px; border-radius: 9999px; background: #fff; display: block;"></span>
316
+ </button>
317
+ </div>
318
+
319
+ <div class="h-px bg-token-border-default w-full"></div>
320
+
321
+ <div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
322
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">FA/AR Font</span>
323
+ <input id="rtl-fafont-input" type="text" placeholder="Default: Vazirmatn" value="${savedFaFont}" class="focus-visible:ring-token-focus h-7 w-full max-w-[8.5rem] rounded-lg border border-token-border bg-token-input-background px-2 text-xs text-token-text-primary shadow-sm outline-none focus-visible:ring-2 max-sm:max-w-none" spellcheck="false">
324
+ </div>
325
+
326
+ <div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
327
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">EN Font</span>
328
+ <input id="rtl-enfont-input" type="text" placeholder="Default: System" value="${savedEnFont}" class="focus-visible:ring-token-focus h-7 w-full max-w-[8.5rem] rounded-lg border border-token-border bg-token-input-background px-2 text-xs text-token-text-primary shadow-sm outline-none focus-visible:ring-2 max-sm:max-w-none" spellcheck="false">
329
+ </div>
330
+
331
+ <div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
332
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">Code Font</span>
333
+ <input id="rtl-codefont-input" type="text" placeholder="Default: System" value="${savedCodeFont}" class="focus-visible:ring-token-focus h-7 w-full max-w-[8.5rem] rounded-lg border border-token-border bg-token-input-background px-2 text-xs text-token-text-primary shadow-sm outline-none focus-visible:ring-2 max-sm:max-w-none" spellcheck="false">
334
+ </div>
335
+
336
+ <div style="display: flex; justify-content: space-between; align-items: center; gap: 8px; height: 28px;">
337
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">Line Height</span>
338
+ <div style="display: flex; align-items: center; gap: 8px;">
339
+ <input id="rtl-lh-input" type="range" min="1.2" max="2.5" step="0.1" value="${savedLH}" style="width: 80px; cursor: pointer; accent-color: var(--color-token-charts-blue, #339cff);">
340
+ <button id="rtl-lh-reset" type="button" class="text-token-text-primary opacity-50 hover:opacity-100 transition-opacity cursor-pointer text-sm" style="background: none; border: none; padding: 0;">↺</button>
341
+ </div>
342
+ </div>
343
+
344
+ <div class="h-px bg-token-border-default w-full"></div>
345
+
346
+ <div style="display: flex; justify-content: space-between; align-items: center; gap: 8px;">
347
+ <div style="display: flex; align-items: center; gap: 4px;">
348
+ <span class="font-medium text-xs" style="font-size: 12px; color: var(--color-token-foreground);">Type @ with Shift+2</span>
349
+ <div class="rtl-info-icon" style="color: var(--color-token-foreground); cursor: pointer; margin-left: 2px;">
350
+ <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 -960 960 960" fill="currentColor"><path d="M450-290h60V-520H450v230Zm52.92-307.75q9.38-9.29 9.38-23.02t-9.29-23.02T480-653.07t-23.02,9.29t-9.29,23.02t9.38,23.02T480-588.46t22.92-9.29ZM480.07-100q-78.84,0-148.2-29.92T211.18-211.13T129.93-331.76T100-479.93t29.92-148.2t81.21-120.68t120.63-81.25T479.93-860t148.2,29.92t120.68,81.21t81.25,120.63T860-480.07t-29.92,148.2T748.87-211.18T628.24-129.93T480.07-100ZM480-160q134,0 227-93t93-227T707-707T480-800T253-707T160-480t93,227t227,93Zm0-320Z"></path></svg>
351
+ <div class="rtl-tooltip">Automatically converts '٬' to '@' when you press Shift+2 on a Persian keyboard layout.</div>
352
+ </div>
353
+ </div>
354
+ <button id="rtl-at-btn" type="button" class="relative inline-flex items-center rounded-full transition-colors duration-200 h-6 w-11" style="background-color: ${fixAtSign ? 'var(--color-token-charts-blue, #339cff)' : '#555'}; border: none; cursor: pointer; height: 24px; width: 44px; border-radius: 9999px; position: relative;">
355
+ <span id="rtl-at-knob" class="inline-block transform rounded-full bg-white transition-transform h-4 w-4" style="margin-left: 4px; transform: ${fixAtSign ? 'translateX(20px)' : 'translateX(0)'}; transition: transform 0.2s; height: 16px; width: 16px; border-radius: 9999px; background: #fff; display: block;"></span>
356
+ </button>
357
+ </div>
358
+
359
+ <div class="h-px bg-token-border-default w-full"></div>
360
+
361
+ <!-- GitHub -->
362
+ <a href="https://github.com/mmnaderi/codex-rtl" target="_blank" class="rtl-github-link">
363
+ <svg height="14" width="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path></svg>
364
+ Star on GitHub
365
+ </a>
366
+ </div>
367
+ </div>
368
+ </div>
369
+ </div>
370
+ `;
371
+ document.body.appendChild(widgetWrapper);
372
+
373
+ const toggleBtn = document.getElementById('rtl-toggle-btn');
374
+ const toggleKnob = document.getElementById('rtl-toggle-knob');
375
+ const toggleLabel = document.getElementById('rtl-toggle-label');
376
+ const settingsWrapper = document.getElementById('rtl-settings-wrapper');
377
+ const forceBtn = document.getElementById('rtl-force-btn');
378
+ const forceKnob = document.getElementById('rtl-force-knob');
379
+ const atBtn = document.getElementById('rtl-at-btn');
380
+ const atKnob = document.getElementById('rtl-at-knob');
381
+ const faFontInput = document.getElementById('rtl-fafont-input');
382
+ const enFontInput = document.getElementById('rtl-enfont-input');
383
+ const codeFontInput = document.getElementById('rtl-codefont-input');
384
+ const lhInput = document.getElementById('rtl-lh-input');
385
+ const lhResetBtn = document.getElementById('rtl-lh-reset');
386
+
387
+ const saveConfig = () => {
388
+ console.log("SAVE_RTL_CONFIG|" + JSON.stringify({
389
+ isRTL: isRTL,
390
+ forceRTL: forceRTL,
391
+ fixAtSign: fixAtSign,
392
+ faFont: faFontInput.value.trim(),
393
+ enFont: enFontInput.value.trim(),
394
+ codeFont: codeFontInput.value.trim(),
395
+ lh: lhInput.value
396
+ }));
397
+ };
398
+
399
+ toggleBtn.addEventListener('click', () => {
400
+ isRTL = !isRTL;
401
+ saveConfig();
402
+ toggleLabel.innerText = isRTL ? 'Enabled' : 'Disabled';
403
+ settingsWrapper.style.opacity = isRTL ? '1' : '0.4';
404
+ settingsWrapper.style.pointerEvents = isRTL ? 'auto' : 'none';
405
+ toggleKnob.style.transform = isRTL ? 'translateX(20px)' : 'translateX(0)';
406
+ toggleBtn.style.backgroundColor = isRTL ? 'var(--color-token-charts-blue, #339cff)' : '#555';
407
+ updateDynamicCSS();
408
+ });
409
+
410
+ forceBtn.addEventListener('click', () => {
411
+ forceRTL = !forceRTL;
412
+ saveConfig();
413
+ forceKnob.style.transform = forceRTL ? 'translateX(20px)' : 'translateX(0)';
414
+ forceBtn.style.backgroundColor = forceRTL ? 'var(--color-token-charts-blue, #339cff)' : '#555';
415
+ updateDynamicCSS();
416
+ });
417
+
418
+ atBtn.addEventListener('click', () => {
419
+ fixAtSign = !fixAtSign;
420
+ saveConfig();
421
+ atKnob.style.transform = fixAtSign ? 'translateX(20px)' : 'translateX(0)';
422
+ atBtn.style.backgroundColor = fixAtSign ? 'var(--color-token-charts-blue, #339cff)' : '#555';
423
+ });
424
+
425
+ faFontInput.addEventListener('input', () => {
426
+ savedFaFont = faFontInput.value.trim();
427
+ saveConfig();
428
+ updateDynamicCSS();
429
+ });
430
+
431
+ enFontInput.addEventListener('input', () => {
432
+ savedEnFont = enFontInput.value.trim();
433
+ saveConfig();
434
+ updateDynamicCSS();
435
+ });
436
+
437
+ codeFontInput.addEventListener('input', () => {
438
+ savedCodeFont = codeFontInput.value.trim();
439
+ saveConfig();
440
+ updateDynamicCSS();
441
+ });
442
+
443
+ lhInput.addEventListener('input', () => {
444
+ savedLH = lhInput.value;
445
+ saveConfig();
446
+ updateDynamicCSS();
447
+ });
448
+
449
+ lhResetBtn.addEventListener('click', () => {
450
+ lhInput.value = '1.6';
451
+ savedLH = '1.6';
452
+ saveConfig();
453
+ updateDynamicCSS();
454
+ });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "codex-rtl",
3
- "version": "0.0.1",
4
- "description": "Reserved name for codex-rtl command-line tool",
5
- "main": "index.js",
3
+ "version": "1.0.0",
4
+ "description": "A smart RTL (Right-to-Left) patcher with UI settings for the Codex application.",
5
+ "main": "./bin/index.js",
6
6
  "bin": {
7
7
  "codex-rtl": "./bin/index.js"
8
8
  },
@@ -19,12 +19,25 @@
19
19
  },
20
20
  "keywords": [
21
21
  "codex",
22
- "rtl"
22
+ "rtl",
23
+ "persian",
24
+ "arabic",
25
+ "hebrew",
26
+ "patcher",
27
+ "electron",
28
+ "asar"
23
29
  ],
24
- "author": "",
30
+ "author": "mmnaderi <mmnaderi.ir@gmail.com>",
25
31
  "license": "MIT",
26
32
  "bugs": {
27
33
  "url": "https://github.com/mmnaderi/codex-rtl/issues"
28
34
  },
29
- "homepage": "https://github.com/mmnaderi/codex-rtl#readme"
35
+ "homepage": "https://github.com/mmnaderi/codex-rtl#readme",
36
+ "dependencies": {
37
+ "@electron/asar": "^4.2.0",
38
+ "ora": "^9.4.1",
39
+ "picocolors": "^1.1.1",
40
+ "prompts": "^2.4.2"
41
+ },
42
+ "type": "module"
30
43
  }