antigravity-rtl 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 +54 -0
- package/Vazirmatn-Variable.woff2 +0 -0
- package/index.js +170 -0
- package/package.json +20 -0
- package/payload.js +510 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Antigravity Smart RTL
|
|
2
|
+
|
|
3
|
+
A smart and beautiful RTL (Right-to-Left) patch for the [Antigravity](https://github.com/google/antigravity) application.
|
|
4
|
+
|
|
5
|
+
This CLI tool automatically injects a sophisticated RTL engine into Antigravity, adding support for Persian (Farsi), Arabic, Hebrew, and other RTL languages, along with a sleek UI to configure fonts and settings on the fly.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Smart Auto-Direction**: Automatically detects if a paragraph is RTL or LTR and aligns it perfectly.
|
|
10
|
+
- **Force RTL Mode**: Want everything aligned to the right? Just toggle the switch.
|
|
11
|
+
- **Custom Typography**: Define different fonts for your RTL text, English text, and Code blocks!
|
|
12
|
+
- **Line Height Control**: A precise slider to adjust the line height for better readability.
|
|
13
|
+
- **Persian Keyboard Fix**: Maps `Shift + 2` to type `@` instead of `٬` on Persian keyboards.
|
|
14
|
+
- **Beautiful Settings Panel**: A floating, non-intrusive UI widget at the bottom right corner.
|
|
15
|
+
- **Vazirmatn Built-in**: Comes with the beautiful Vazirmatn variable font by default.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
You don't need to download any files. Just run the following command in your terminal:
|
|
20
|
+
|
|
21
|
+
### macOS / Linux
|
|
22
|
+
Because the tool needs to modify the Antigravity application files, you must run it with `sudo`:
|
|
23
|
+
```bash
|
|
24
|
+
sudo npx antigravity-rtl
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Windows
|
|
28
|
+
Open **PowerShell** or **Command Prompt** as **Administrator** (Right-click -> Run as Administrator), then run:
|
|
29
|
+
```bash
|
|
30
|
+
npx antigravity-rtl
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> **Note:** You must have [Node.js](https://nodejs.org) installed on your system to run this command.
|
|
34
|
+
|
|
35
|
+
## Restoring to Original (Uninstall)
|
|
36
|
+
|
|
37
|
+
If you ever want to revert Antigravity back to its original state (before the patch), simply run the command with the `--restore` flag:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
sudo npx antigravity-rtl --restore
|
|
41
|
+
```
|
|
42
|
+
*(On Windows, run without `sudo` in an Administrator terminal)*
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
This CLI tool:
|
|
47
|
+
1. Locates your Antigravity installation.
|
|
48
|
+
2. Creates a safe backup of the original `app.asar` file.
|
|
49
|
+
3. Extracts the application and safely injects the Smart RTL Engine into the core logic (`utils.js`).
|
|
50
|
+
4. Repacks the application so you can start using it immediately.
|
|
51
|
+
|
|
52
|
+
## Contributing
|
|
53
|
+
|
|
54
|
+
Feel free to open issues or submit pull requests. Let's make Antigravity accessible and beautiful for everyone!
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
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';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const { blue, cyan, green, red, yellow, bold } = picocolors;
|
|
14
|
+
|
|
15
|
+
console.log(bold(cyan('\n✨ Antigravity Smart RTL Patcher\n')));
|
|
16
|
+
|
|
17
|
+
function checkPermissions() {
|
|
18
|
+
if (os.platform() !== 'win32') {
|
|
19
|
+
if (process.getuid && process.getuid() !== 0) {
|
|
20
|
+
console.error(red('✖ Permission Denied!'));
|
|
21
|
+
console.error(yellow('Please run this command with sudo:'));
|
|
22
|
+
console.error(bold('sudo npx antigravity-rtl\n'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getDefaultPath() {
|
|
29
|
+
if (os.platform() === 'darwin') {
|
|
30
|
+
return '/Applications/Antigravity.app/Contents/Resources/app.asar';
|
|
31
|
+
} else if (os.platform() === 'win32') {
|
|
32
|
+
return path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Antigravity', 'resources', 'app.asar');
|
|
33
|
+
} else {
|
|
34
|
+
return '/opt/Antigravity/resources/app.asar';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getAsarPath() {
|
|
39
|
+
let asarPath = getDefaultPath();
|
|
40
|
+
if (fs.existsSync(asarPath)) {
|
|
41
|
+
console.log(blue(`ℹ Found Antigravity installation at:`));
|
|
42
|
+
console.log(` ${asarPath}\n`);
|
|
43
|
+
return asarPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(yellow(`⚠ Could not find Antigravity at default location.`));
|
|
47
|
+
const response = await prompts({
|
|
48
|
+
type: 'text',
|
|
49
|
+
name: 'customPath',
|
|
50
|
+
message: 'Please enter the full path to app.asar:'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!response.customPath || !fs.existsSync(response.customPath)) {
|
|
54
|
+
console.error(red('\n✖ Invalid path. Aborting.\n'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return response.customPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
const isRestore = args.includes('--restore');
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
checkPermissions();
|
|
65
|
+
const asarPath = await getAsarPath();
|
|
66
|
+
const backupPath = asarPath + '.bak';
|
|
67
|
+
|
|
68
|
+
if (isRestore) {
|
|
69
|
+
if (!fs.existsSync(backupPath)) {
|
|
70
|
+
console.error(red('✖ No backup found to restore.\n'));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const spinner = ora('Restoring original app.asar...').start();
|
|
74
|
+
try {
|
|
75
|
+
fs.copyFileSync(backupPath, asarPath);
|
|
76
|
+
spinner.succeed('Successfully restored original Antigravity!\n');
|
|
77
|
+
process.exit(0);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
spinner.fail('Failed to restore.');
|
|
80
|
+
console.error(red(e.message));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const spinner = ora('Checking permissions and backing up...').start();
|
|
86
|
+
try {
|
|
87
|
+
fs.accessSync(path.dirname(asarPath), fs.constants.W_OK);
|
|
88
|
+
if (!fs.existsSync(backupPath)) {
|
|
89
|
+
fs.copyFileSync(asarPath, backupPath);
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
spinner.fail('Permission Denied.');
|
|
93
|
+
if (os.platform() === 'win32') {
|
|
94
|
+
console.error(yellow('\nPlease run your terminal (PowerShell/CMD) as Administrator and try again.\n'));
|
|
95
|
+
} else {
|
|
96
|
+
console.error(yellow('\nPlease run this command with sudo.\n'));
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const extractDir = path.join(path.dirname(asarPath), 'app-extracted-rtl-temp');
|
|
102
|
+
spinner.text = 'Extracting app.asar (this may take a few seconds)...';
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(extractDir)) {
|
|
105
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
asar.extractAll(asarPath, extractDir);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
spinner.fail('Failed to extract ASAR.');
|
|
110
|
+
console.error(red(e.message));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
spinner.text = 'Injecting RTL features...';
|
|
115
|
+
try {
|
|
116
|
+
const utilsPath = path.join(extractDir, 'dist', 'utils.js');
|
|
117
|
+
if (!fs.existsSync(utilsPath)) {
|
|
118
|
+
throw new Error('dist/utils.js not found in ASAR. Unsupported Antigravity version.');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let utilsCode = fs.readFileSync(utilsPath, 'utf8');
|
|
122
|
+
|
|
123
|
+
if (utilsCode.includes('/* ANTIGRAVITY RTL PATCH */')) {
|
|
124
|
+
spinner.succeed('Antigravity is already patched!');
|
|
125
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
126
|
+
console.log(green('\n✨ Enjoy your RTL experience!\n'));
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const payloadPath = path.join(__dirname, 'payload.js');
|
|
131
|
+
const payload = fs.readFileSync(payloadPath, 'utf8');
|
|
132
|
+
|
|
133
|
+
const anchor = 'void win.loadURL(url);';
|
|
134
|
+
if (!utilsCode.includes(anchor)) {
|
|
135
|
+
throw new Error('Injection anchor not found. The app version might be unsupported.');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
utilsCode = utilsCode.replace(anchor, payload);
|
|
139
|
+
fs.writeFileSync(utilsPath, utilsCode);
|
|
140
|
+
|
|
141
|
+
const fontSource = path.join(__dirname, 'Vazirmatn-Variable.woff2');
|
|
142
|
+
const fontDest = path.join(extractDir, 'dist', 'Vazirmatn-Variable.woff2');
|
|
143
|
+
if (fs.existsSync(fontSource)) {
|
|
144
|
+
fs.copyFileSync(fontSource, fontDest);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
} catch (e) {
|
|
148
|
+
spinner.fail('Injection failed.');
|
|
149
|
+
console.error(red(e.message));
|
|
150
|
+
if (fs.existsSync(extractDir)) fs.rmSync(extractDir, { recursive: true, force: true });
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
spinner.text = 'Repacking app.asar (almost done)...';
|
|
155
|
+
try {
|
|
156
|
+
await asar.createPackage(extractDir, asarPath);
|
|
157
|
+
fs.rmSync(extractDir, { recursive: true, force: true });
|
|
158
|
+
spinner.succeed('Successfully patched Antigravity!');
|
|
159
|
+
console.log(green('\n✨ RTL Features have been enabled. Please restart Antigravity to see the changes.\n'));
|
|
160
|
+
} catch (e) {
|
|
161
|
+
spinner.fail('Failed to repack ASAR.');
|
|
162
|
+
console.error(red(e.message));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch(e => {
|
|
168
|
+
console.error(red('\n✖ An unexpected error occurred:'), e.message);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "antigravity-rtl",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A smart RTL (Right-to-Left) patcher with UI settings for the Antigravity application.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["antigravity", "rtl", "persian", "arabic", "hebrew", "patcher", "electron", "asar"],
|
|
10
|
+
"author": "mmnaderi",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@electron/asar": "^4.2.0",
|
|
14
|
+
"ora": "^9.4.1",
|
|
15
|
+
"picocolors": "^1.1.1",
|
|
16
|
+
"prompts": "^2.4.2"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"bin": "index.js"
|
|
20
|
+
}
|
package/payload.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
/* ANTIGRAVITY RTL PATCH */
|
|
2
|
+
win.webContents.on('console-message', (event, level, message) => {
|
|
3
|
+
if (typeof message === 'string' && message.startsWith('SAVE_RTL_CONFIG|')) {
|
|
4
|
+
try {
|
|
5
|
+
const data = message.substring(16);
|
|
6
|
+
const configPath = require('path').join(require('os').homedir(), '.antigravity-rtl.json');
|
|
7
|
+
require('fs').writeFileSync(configPath, data);
|
|
8
|
+
} catch (e) {}
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
void win.loadURL(url);
|
|
12
|
+
|
|
13
|
+
win.webContents.on('dom-ready', () => {
|
|
14
|
+
try {
|
|
15
|
+
const fontPath = require('path').join(__dirname, 'Vazirmatn-Variable.woff2');
|
|
16
|
+
const fontBase64 = require('fs').readFileSync(fontPath).toString('base64');
|
|
17
|
+
// Read config
|
|
18
|
+
let rtlConfig = { faFont: '', enFont: '', codeFont: '', lh: '1.6', isRTL: true, forceRTL: false, fixAtSign: true };
|
|
19
|
+
try {
|
|
20
|
+
const configPath = require('path').join(require('os').homedir(), '.antigravity-rtl.json');
|
|
21
|
+
if (require('fs').existsSync(configPath)) {
|
|
22
|
+
const cfg = JSON.parse(require('fs').readFileSync(configPath, 'utf8'));
|
|
23
|
+
rtlConfig = { ...rtlConfig, ...cfg };
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {}
|
|
26
|
+
|
|
27
|
+
// Unified injection for RTL Toggle, CSS, and JS
|
|
28
|
+
win.webContents.executeJavaScript(`
|
|
29
|
+
const fontBase64 = '${fontBase64}';
|
|
30
|
+
const rtlConfig = ${JSON.stringify(rtlConfig)};
|
|
31
|
+
|
|
32
|
+
// 2. Observer Logic
|
|
33
|
+
let isRTL = rtlConfig.isRTL;
|
|
34
|
+
let forceRTL = rtlConfig.forceRTL || false;
|
|
35
|
+
let fixAtSign = rtlConfig.fixAtSign !== false;
|
|
36
|
+
|
|
37
|
+
// Inject permanent widget styles
|
|
38
|
+
if (!document.getElementById('rtl-widget-style')) {
|
|
39
|
+
let widgetStyle = document.createElement('style');
|
|
40
|
+
widgetStyle.id = 'rtl-widget-style';
|
|
41
|
+
widgetStyle.innerHTML = \`
|
|
42
|
+
.rtl-tooltip {
|
|
43
|
+
visibility: hidden;
|
|
44
|
+
opacity: 0;
|
|
45
|
+
transition: opacity 0.2s ease-in-out;
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
}
|
|
48
|
+
.rtl-info-icon:hover .rtl-tooltip {
|
|
49
|
+
visibility: visible;
|
|
50
|
+
opacity: 1;
|
|
51
|
+
}
|
|
52
|
+
\`;
|
|
53
|
+
document.head.appendChild(widgetStyle);
|
|
54
|
+
}
|
|
55
|
+
const savedFaFont = rtlConfig.faFont || '';
|
|
56
|
+
const savedEnFont = rtlConfig.enFont || '';
|
|
57
|
+
const savedCodeFont = rtlConfig.codeFont || '';
|
|
58
|
+
const savedLH = rtlConfig.lh || '1.6';
|
|
59
|
+
|
|
60
|
+
// 1. Create Style Tag
|
|
61
|
+
const rtlStyle = document.createElement('style');
|
|
62
|
+
rtlStyle.id = 'antigravity-rtl-style';
|
|
63
|
+
|
|
64
|
+
const updateDynamicCSS = (faFont, enFont, codeFont, lh) => {
|
|
65
|
+
let faFontRule = '';
|
|
66
|
+
let faFontName = "'PersianOnlyFont'";
|
|
67
|
+
|
|
68
|
+
if (faFont) {
|
|
69
|
+
faFontName = "'UserPersianFont', 'PersianOnlyFont'";
|
|
70
|
+
// Remove '-Regular' or ' Regular' if user typed it, to find the base family name
|
|
71
|
+
let baseFaFont = faFont.replace(/[-\\s]?Regular$/i, '');
|
|
72
|
+
|
|
73
|
+
faFontRule = \`
|
|
74
|
+
@font-face {
|
|
75
|
+
font-family: 'UserPersianFont';
|
|
76
|
+
src: local('\${faFont}'), local('\${baseFaFont}');
|
|
77
|
+
font-weight: 400;
|
|
78
|
+
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
|
|
79
|
+
}
|
|
80
|
+
@font-face {
|
|
81
|
+
font-family: 'UserPersianFont';
|
|
82
|
+
src: local('\${baseFaFont} Bold'), local('\${baseFaFont}-Bold'), local('\${baseFaFont}Bold');
|
|
83
|
+
font-weight: 700;
|
|
84
|
+
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
|
|
85
|
+
}
|
|
86
|
+
\`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let enFontStr = enFont ? \`'\${enFont}', ui-sans-serif, system-ui, sans-serif\` : 'ui-sans-serif, system-ui, sans-serif';
|
|
90
|
+
let codeFontStr = codeFont ? \`'\${codeFont}', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace\` : 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
91
|
+
|
|
92
|
+
let forceRtlStyle = forceRTL ? \`
|
|
93
|
+
.prose > *:not(pre):not(code),
|
|
94
|
+
[data-testid="chat-message"] > *:not(pre):not(code),
|
|
95
|
+
.markdown-body > *:not(pre):not(code),
|
|
96
|
+
.leading-relaxed > *:not(pre):not(code),
|
|
97
|
+
[data-testid="user-input-step"],
|
|
98
|
+
[data-testid="user-input-step"] > *:not(pre):not(code) {
|
|
99
|
+
direction: rtl !important;
|
|
100
|
+
text-align: right !important;
|
|
101
|
+
unicode-bidi: normal !important;
|
|
102
|
+
}
|
|
103
|
+
\` : '';
|
|
104
|
+
|
|
105
|
+
rtlStyle.textContent = \`
|
|
106
|
+
\${faFontRule}
|
|
107
|
+
@font-face {
|
|
108
|
+
font-family: 'PersianOnlyFont';
|
|
109
|
+
src: url('data:font/woff2;base64,\${fontBase64}') format('woff2');
|
|
110
|
+
font-weight: 100 900;
|
|
111
|
+
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF, U+FB50-FDFF, U+FE70-FEFF;
|
|
112
|
+
}
|
|
113
|
+
:root, :host, html, body {
|
|
114
|
+
font-family: \${faFontName}, \${enFontStr}, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
|
|
115
|
+
}
|
|
116
|
+
p, h1, h2, h3, h4, h5, h6, ul, ol {
|
|
117
|
+
unicode-bidi: plaintext;
|
|
118
|
+
text-align: start;
|
|
119
|
+
}
|
|
120
|
+
.prose > *, [data-testid="chat-message"] > *, .markdown-body > * {
|
|
121
|
+
unicode-bidi: plaintext;
|
|
122
|
+
text-align: start;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
\${forceRtlStyle}
|
|
126
|
+
|
|
127
|
+
/* RTL List Padding Fix */
|
|
128
|
+
ul:not(#_)[dir="rtl"], ol:not(#_)[dir="rtl"],
|
|
129
|
+
[dir="rtl"] ul:not(#_), [dir="rtl"] ol:not(#_) {
|
|
130
|
+
padding-left: 0 !important;
|
|
131
|
+
padding-right: 1.25rem !important;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Nested RTL List Padding Fix */
|
|
135
|
+
[dir="rtl"] ul:not(#_) ul:not(#_), [dir="rtl"] ul:not(#_) ol:not(#_),
|
|
136
|
+
[dir="rtl"] ol:not(#_) ul:not(#_), [dir="rtl"] ol:not(#_) ol:not(#_),
|
|
137
|
+
ul:not(#_)[dir="rtl"] ul:not(#_), ul:not(#_)[dir="rtl"] ol:not(#_),
|
|
138
|
+
ol:not(#_)[dir="rtl"] ul:not(#_), ol:not(#_)[dir="rtl"] ol:not(#_) {
|
|
139
|
+
padding-left: 0 !important;
|
|
140
|
+
padding-right: 2.5rem !important;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Thinking Blocks (Keep LTR) */
|
|
144
|
+
.cursor-edit.text-secondary-foreground,
|
|
145
|
+
.cursor-edit.text-secondary-foreground * {
|
|
146
|
+
direction: ltr !important;
|
|
147
|
+
text-align: left !important;
|
|
148
|
+
unicode-bidi: normal !important;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Custom CSS removed to rely on Tailwind completely */
|
|
152
|
+
|
|
153
|
+
/* Code Blocks */
|
|
154
|
+
pre, code, pre *, code * {
|
|
155
|
+
unicode-bidi: normal !important;
|
|
156
|
+
direction: ltr !important;
|
|
157
|
+
text-align: left !important;
|
|
158
|
+
font-family: \${codeFontStr} !important;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* AI Response Line Height */
|
|
162
|
+
.leading-relaxed {
|
|
163
|
+
line-height: \${lh} !important;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
[contenteditable="true"], [contenteditable="true"] * {
|
|
167
|
+
unicode-bidi: normal !important;
|
|
168
|
+
text-align: start !important;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Smart Auto-Direction for Sidebar & Truncated Texts */
|
|
172
|
+
[role="navigation"][aria-label="Sidebar"] *, .truncate {
|
|
173
|
+
unicode-bidi: plaintext !important;
|
|
174
|
+
text-align: start !important;
|
|
175
|
+
}
|
|
176
|
+
/* Apply line height exclusively to chat paragraphs and input area */
|
|
177
|
+
.prose p, .prose li, .markdown-body p, [data-testid="chat-message"] p, [data-testid="chat-message"] .leading-relaxed, .leading-relaxed, [data-testid="user-input-step"], [data-testid="user-input-step"] div, [data-lexical-text="true"], [contenteditable="true"], [contenteditable="true"] p, .pointer-events-none.absolute.overflow-hidden {
|
|
178
|
+
line-height: \${lh} !important;
|
|
179
|
+
}
|
|
180
|
+
\`;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
document.head.appendChild(rtlStyle);
|
|
184
|
+
updateDynamicCSS(savedFaFont, savedEnFont, savedCodeFont, savedLH);
|
|
185
|
+
|
|
186
|
+
// 2. Input Observer Logic
|
|
187
|
+
function updateDir() {
|
|
188
|
+
if (!isRTL) return;
|
|
189
|
+
|
|
190
|
+
// Inputs
|
|
191
|
+
document.querySelectorAll('[contenteditable="true"] p, [contenteditable="true"]').forEach(el => {
|
|
192
|
+
const text = el.textContent.replace(/[\\u200B-\\u200F\\uFEFF]/g, '').trim();
|
|
193
|
+
if (text.length > 0) {
|
|
194
|
+
const isRtlText = /^[^a-zA-Z]*[\\u0591-\\u07FF\\uFB1D-\\uFDFD\\uFE70-\\uFEFC]/.test(text);
|
|
195
|
+
const newDir = isRtlText ? 'rtl' : 'ltr';
|
|
196
|
+
if (el.getAttribute('dir') !== newDir) el.setAttribute('dir', newDir);
|
|
197
|
+
} else {
|
|
198
|
+
if (el.hasAttribute('dir')) el.removeAttribute('dir');
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Chat Output & Artifact Viewer (Respects Force RTL)
|
|
203
|
+
document.querySelectorAll(\`
|
|
204
|
+
.prose > *,
|
|
205
|
+
[data-testid="chat-message"] > *,
|
|
206
|
+
.markdown-body > *,
|
|
207
|
+
.leading-relaxed > *,
|
|
208
|
+
[data-testid="user-input-step"],
|
|
209
|
+
[data-testid="user-input-step"] > *
|
|
210
|
+
\`).forEach(el => {
|
|
211
|
+
// Skip code blocks
|
|
212
|
+
if (el.tagName === 'PRE' || el.tagName === 'CODE') return;
|
|
213
|
+
|
|
214
|
+
const text = el.textContent.replace(/[\\u200B-\\u200F\\uFEFF]/g, '').trim();
|
|
215
|
+
let dir = 'auto';
|
|
216
|
+
|
|
217
|
+
if (forceRTL) {
|
|
218
|
+
dir = 'rtl';
|
|
219
|
+
} else if (text) {
|
|
220
|
+
const firstChar = text.match(/[A-Za-z\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/);
|
|
221
|
+
if (firstChar) {
|
|
222
|
+
const isPersianOrArabic = /[\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFF]/.test(firstChar[0]);
|
|
223
|
+
dir = isPersianOrArabic ? 'rtl' : 'ltr';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (el.getAttribute('dir') !== dir) {
|
|
228
|
+
el.setAttribute('dir', dir);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
document.body.addEventListener('input', updateDir, { capture: true });
|
|
233
|
+
const observer = new MutationObserver(updateDir);
|
|
234
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
235
|
+
setInterval(updateDir, 500);
|
|
236
|
+
|
|
237
|
+
// Keyboard layout fixes
|
|
238
|
+
document.addEventListener('keydown', (e) => {
|
|
239
|
+
if (!fixAtSign) return;
|
|
240
|
+
// Intercept Shift + 2 in Persian keyboard
|
|
241
|
+
if (e.code === 'Digit2' && e.shiftKey) {
|
|
242
|
+
if (e.key === '٬' || e.key === '،') {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
document.execCommand('insertText', false, '@');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}, { capture: true });
|
|
248
|
+
|
|
249
|
+
// 3. Create Floating Widget
|
|
250
|
+
const widgetWrapper = document.createElement('div');
|
|
251
|
+
widgetWrapper.innerHTML = \`
|
|
252
|
+
<div class="group fixed bottom-4 right-4 z-50" style="direction: ltr;">
|
|
253
|
+
<!-- Trigger Icon -->
|
|
254
|
+
<div class="relative w-10 h-10 flex items-center justify-center rounded-full bg-secondary text-secondary-foreground hover:text-foreground cursor-pointer opacity-80 transition-all duration-300 group-hover:opacity-0 group-hover:scale-50 group-hover:pointer-events-none">
|
|
255
|
+
<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>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<!-- Panel -->
|
|
259
|
+
<div class="absolute bottom-0 right-0 flex flex-col p-px rounded-2xl bg-card-border transition-all duration-300 text-sm origin-bottom-right scale-0 opacity-0 pointer-events-none group-hover:scale-100 group-hover:opacity-100 group-hover:pointer-events-auto w-60">
|
|
260
|
+
<div class="flex flex-col gap-2 p-3 rounded-[15px] bg-card text-card-foreground w-full h-full">
|
|
261
|
+
|
|
262
|
+
<!-- Header -->
|
|
263
|
+
<div class="text-center px-1 pb-2 mb-1 border-b border-border border-opacity-50">
|
|
264
|
+
<span class="text-base font-semibold">Antigravity Smart RTL</span>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<!-- Toggle -->
|
|
268
|
+
<div class="flex items-center justify-between gap-4 px-1">
|
|
269
|
+
<span id="rtl-toggle-label" class="font-medium text-xs opacity-80">\${isRTL ? 'Enabled' : 'Disabled'}</span>
|
|
270
|
+
<button id="rtl-toggle-btn" type="button" role="switch" aria-checked="true" class="relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out shrink-0 h-6 w-11 bg-accent cursor-pointer">
|
|
271
|
+
<span id="rtl-toggle-knob" class="inline-block transform rounded-full bg-white transition-transform duration-200 ease-in-out shadow-sm h-4 w-4 translate-x-6"></span>
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<!-- Grouped Settings -->
|
|
276
|
+
<div id="rtl-settings-wrapper" class="flex flex-col gap-2 transition-all duration-300 \${isRTL ? '' : 'opacity-40 pointer-events-none'}">
|
|
277
|
+
<!-- Force RTL Toggle -->
|
|
278
|
+
<div class="flex items-center justify-between gap-2 px-1 mt-1">
|
|
279
|
+
<div class="flex items-center">
|
|
280
|
+
<span class="font-medium text-xs opacity-80 whitespace-nowrap">Force RTL</span>
|
|
281
|
+
<div class="relative flex items-center rtl-info-icon ml-1">
|
|
282
|
+
<span class="cursor-pointer inline-flex items-center text-muted-foreground hover:text-foreground">
|
|
283
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -960 960 960" fill="currentColor" class="w-3.5 h-3.5"><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>
|
|
284
|
+
</span>
|
|
285
|
+
<!-- Tooltip Popup -->
|
|
286
|
+
<div class="rtl-tooltip absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-52 p-2 rounded shadow-md z-50 whitespace-normal text-center bg-muted border border-border text-foreground text-[11px] leading-relaxed">
|
|
287
|
+
Forces Chat and Artifact Viewer to RTL layout, even if the paragraph starts with an English word.
|
|
288
|
+
<div class="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0" style="border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid var(--border, #444);"></div>
|
|
289
|
+
<div class="absolute top-[calc(100%-1px)] left-1/2 -translate-x-1/2 w-0 h-0" style="border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid var(--muted, #333);"></div>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
<button id="rtl-force-btn" type="button" role="switch" aria-checked="\${forceRTL}" class="relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out shrink-0 h-6 w-11 \${forceRTL ? 'bg-accent' : 'bg-gray-400 bg-opacity-40'} cursor-pointer">
|
|
294
|
+
<span id="rtl-force-knob" class="inline-block transform rounded-full bg-white transition-transform duration-200 ease-in-out shadow-sm h-4 w-4 \${forceRTL ? 'translate-x-6' : 'translate-x-1'}"></span>
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<!-- Separator -->
|
|
299
|
+
<div class="h-px bg-border border-opacity-30 w-full my-1"></div>
|
|
300
|
+
|
|
301
|
+
<!-- Persian Font -->
|
|
302
|
+
<div class="flex items-center justify-between gap-2 px-1">
|
|
303
|
+
<span class="font-medium text-xs opacity-80 whitespace-nowrap" title="Persian/Arabic Font (Fallback: Vazirmatn)">FA/AR Font</span>
|
|
304
|
+
<input id="rtl-fafont-input" type="text" placeholder="Default: Vazirmatn" value="\${savedFaFont}" class="text-[11px] bg-muted border border-border px-2 py-1 rounded-md w-28 focus:outline-none !text-foreground">
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<!-- English Font -->
|
|
308
|
+
<div class="flex items-center justify-between gap-2 px-1 mt-1">
|
|
309
|
+
<span class="font-medium text-xs opacity-80 whitespace-nowrap" title="English Font">EN Font</span>
|
|
310
|
+
<input id="rtl-enfont-input" type="text" placeholder="Default: System" value="\${savedEnFont}" class="text-[11px] bg-muted border border-border px-2 py-1 rounded-md w-28 focus:outline-none !text-foreground">
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<!-- Code Font -->
|
|
314
|
+
<div class="flex items-center justify-between gap-2 px-1 mt-1">
|
|
315
|
+
<span class="font-medium text-xs opacity-80 whitespace-nowrap" title="Code Font">Code Font</span>
|
|
316
|
+
<input id="rtl-codefont-input" type="text" placeholder="Default: System" value="\${savedCodeFont}" class="text-[11px] bg-muted border border-border px-2 py-1 rounded-md w-28 focus:outline-none !text-foreground">
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<!-- Line Height -->
|
|
320
|
+
<div class="flex items-center justify-between gap-2 px-1 mt-1 mb-1">
|
|
321
|
+
<span class="font-medium text-xs opacity-80" title="Chat Line Height">Line Height</span>
|
|
322
|
+
<div class="flex items-center gap-2">
|
|
323
|
+
<input id="rtl-lh-input" type="range" min="1.2" max="2.5" step="0.1" value="\${savedLH}" class="h-1 w-20 cursor-pointer" style="accent-color: var(--vscode-button-background);">
|
|
324
|
+
<button id="rtl-lh-reset" type="button" class="opacity-50 hover:opacity-100 transition-opacity cursor-pointer" title="Reset to 1.6">
|
|
325
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
<!-- Separator -->
|
|
331
|
+
<div class="h-px bg-border border-opacity-30 w-full my-1"></div>
|
|
332
|
+
|
|
333
|
+
<!-- Fix @ Toggle -->
|
|
334
|
+
<div class="flex items-center justify-between gap-2 px-1 mb-1">
|
|
335
|
+
<div class="flex items-center">
|
|
336
|
+
<span class="font-medium text-xs opacity-80 whitespace-nowrap">Type @ with Shift+2</span>
|
|
337
|
+
<div class="relative flex items-center rtl-info-icon ml-1">
|
|
338
|
+
<span class="cursor-pointer inline-flex items-center text-muted-foreground hover:text-foreground">
|
|
339
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -960 960 960" fill="currentColor" class="w-3.5 h-3.5"><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>
|
|
340
|
+
</span>
|
|
341
|
+
<div class="rtl-tooltip absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-48 p-2 rounded shadow-md z-50 whitespace-normal text-center bg-muted border border-border text-foreground text-[11px] leading-relaxed">
|
|
342
|
+
Forces Shift+2 to type '@' instead of '٬' while using the Persian keyboard.
|
|
343
|
+
<div class="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0" style="border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid var(--border, #444);"></div>
|
|
344
|
+
<div class="absolute top-[calc(100%-1px)] left-1/2 -translate-x-1/2 w-0 h-0" style="border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid var(--muted, #333);"></div>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
<button id="rtl-at-btn" type="button" role="switch" aria-checked="\${fixAtSign}" class="relative inline-flex items-center rounded-full transition-colors duration-200 ease-in-out shrink-0 h-6 w-11 \${fixAtSign ? 'bg-accent' : 'bg-gray-400 bg-opacity-40'} cursor-pointer">
|
|
349
|
+
<span id="rtl-at-knob" class="inline-block transform rounded-full bg-white transition-transform duration-200 ease-in-out shadow-sm h-4 w-4 \${fixAtSign ? 'translate-x-6' : 'translate-x-1'}"></span>
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div class="h-px bg-card-border w-full"></div>
|
|
355
|
+
|
|
356
|
+
<!-- GitHub -->
|
|
357
|
+
<a href="https://github.com/Antigravity-RTL-Patcher" target="_blank" class="flex items-center justify-center gap-2 text-xs font-semibold hover:text-yellow-500 opacity-70 hover:opacity-100 transition-all duration-300 no-underline pt-1 pb-0.5">
|
|
358
|
+
<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>
|
|
359
|
+
Star on GitHub
|
|
360
|
+
</a>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
\`;
|
|
365
|
+
document.body.appendChild(widgetWrapper.firstElementChild);
|
|
366
|
+
|
|
367
|
+
const toggleBtn = document.getElementById('rtl-toggle-btn');
|
|
368
|
+
const toggleKnob = document.getElementById('rtl-toggle-knob');
|
|
369
|
+
const toggleLabel = document.getElementById('rtl-toggle-label');
|
|
370
|
+
const settingsWrapper = document.getElementById('rtl-settings-wrapper');
|
|
371
|
+
const faFontInput = document.getElementById('rtl-fafont-input');
|
|
372
|
+
const enFontInput = document.getElementById('rtl-enfont-input');
|
|
373
|
+
const codeFontInput = document.getElementById('rtl-codefont-input');
|
|
374
|
+
const lhInput = document.getElementById('rtl-lh-input');
|
|
375
|
+
const lhResetBtn = document.getElementById('rtl-lh-reset');
|
|
376
|
+
const forceBtn = document.getElementById('rtl-force-btn');
|
|
377
|
+
const forceKnob = document.getElementById('rtl-force-knob');
|
|
378
|
+
const atBtn = document.getElementById('rtl-at-btn');
|
|
379
|
+
const atKnob = document.getElementById('rtl-at-knob');
|
|
380
|
+
|
|
381
|
+
// Initialize Toggle State
|
|
382
|
+
if (!isRTL) {
|
|
383
|
+
toggleBtn.setAttribute('aria-checked', 'false');
|
|
384
|
+
toggleBtn.classList.remove('bg-accent');
|
|
385
|
+
toggleBtn.style.backgroundColor = 'rgba(156, 163, 175, 0.4)';
|
|
386
|
+
toggleKnob.classList.remove('translate-x-6');
|
|
387
|
+
toggleKnob.classList.add('translate-x-1');
|
|
388
|
+
if (rtlStyle.parentNode) rtlStyle.parentNode.removeChild(rtlStyle);
|
|
389
|
+
} else {
|
|
390
|
+
updateDir();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const saveConfig = () => {
|
|
394
|
+
console.log("SAVE_RTL_CONFIG|" + JSON.stringify({
|
|
395
|
+
faFont: faFontInput.value.trim(),
|
|
396
|
+
enFont: enFontInput.value.trim(),
|
|
397
|
+
codeFont: codeFontInput.value.trim(),
|
|
398
|
+
lh: lhInput.value,
|
|
399
|
+
isRTL: isRTL,
|
|
400
|
+
forceRTL: forceRTL,
|
|
401
|
+
fixAtSign: fixAtSign
|
|
402
|
+
}));
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Force RTL Event
|
|
406
|
+
forceBtn.addEventListener('click', () => {
|
|
407
|
+
forceRTL = !forceRTL;
|
|
408
|
+
saveConfig();
|
|
409
|
+
forceBtn.setAttribute('aria-checked', forceRTL);
|
|
410
|
+
|
|
411
|
+
if (forceRTL) {
|
|
412
|
+
forceBtn.classList.add('bg-accent');
|
|
413
|
+
forceBtn.classList.remove('bg-gray-400', 'bg-opacity-40');
|
|
414
|
+
forceKnob.classList.add('translate-x-6');
|
|
415
|
+
forceKnob.classList.remove('translate-x-1');
|
|
416
|
+
} else {
|
|
417
|
+
forceBtn.classList.remove('bg-accent');
|
|
418
|
+
forceBtn.classList.add('bg-gray-400', 'bg-opacity-40');
|
|
419
|
+
forceKnob.classList.add('translate-x-1');
|
|
420
|
+
forceKnob.classList.remove('translate-x-6');
|
|
421
|
+
}
|
|
422
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
423
|
+
updateDir();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// At Sign Fix Event
|
|
427
|
+
atBtn.addEventListener('click', () => {
|
|
428
|
+
fixAtSign = !fixAtSign;
|
|
429
|
+
saveConfig();
|
|
430
|
+
atBtn.setAttribute('aria-checked', fixAtSign);
|
|
431
|
+
|
|
432
|
+
if (fixAtSign) {
|
|
433
|
+
atBtn.classList.add('bg-accent');
|
|
434
|
+
atBtn.classList.remove('bg-gray-400', 'bg-opacity-40');
|
|
435
|
+
atKnob.classList.add('translate-x-6');
|
|
436
|
+
atKnob.classList.remove('translate-x-1');
|
|
437
|
+
} else {
|
|
438
|
+
atBtn.classList.remove('bg-accent');
|
|
439
|
+
atBtn.classList.add('bg-gray-400', 'bg-opacity-40');
|
|
440
|
+
atKnob.classList.add('translate-x-1');
|
|
441
|
+
atKnob.classList.remove('translate-x-6');
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Event Listeners
|
|
446
|
+
faFontInput.addEventListener('input', (e) => {
|
|
447
|
+
saveConfig();
|
|
448
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
enFontInput.addEventListener('input', (e) => {
|
|
452
|
+
saveConfig();
|
|
453
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
codeFontInput.addEventListener('input', (e) => {
|
|
457
|
+
saveConfig();
|
|
458
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
lhInput.addEventListener('input', (e) => {
|
|
462
|
+
saveConfig();
|
|
463
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
lhResetBtn.addEventListener('click', () => {
|
|
467
|
+
lhInput.value = '1.6';
|
|
468
|
+
saveConfig();
|
|
469
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Toggle Event
|
|
473
|
+
toggleBtn.addEventListener('click', () => {
|
|
474
|
+
isRTL = !isRTL;
|
|
475
|
+
saveConfig();
|
|
476
|
+
toggleBtn.setAttribute('aria-checked', isRTL);
|
|
477
|
+
|
|
478
|
+
if (isRTL) {
|
|
479
|
+
toggleLabel.innerText = 'Enabled';
|
|
480
|
+
settingsWrapper.classList.remove('opacity-40', 'pointer-events-none');
|
|
481
|
+
toggleBtn.classList.add('bg-accent');
|
|
482
|
+
toggleBtn.style.backgroundColor = '';
|
|
483
|
+
toggleKnob.classList.add('translate-x-6');
|
|
484
|
+
toggleKnob.classList.remove('translate-x-1');
|
|
485
|
+
document.head.appendChild(rtlStyle);
|
|
486
|
+
updateDir();
|
|
487
|
+
updateDynamicCSS(faFontInput.value.trim(), enFontInput.value.trim(), codeFontInput.value.trim(), lhInput.value);
|
|
488
|
+
} else {
|
|
489
|
+
toggleLabel.innerText = 'Disabled';
|
|
490
|
+
toggleBtn.classList.remove('bg-accent');
|
|
491
|
+
toggleBtn.style.backgroundColor = 'rgba(156, 163, 175, 0.4)';
|
|
492
|
+
toggleKnob.classList.remove('translate-x-6');
|
|
493
|
+
toggleKnob.classList.add('translate-x-1');
|
|
494
|
+
settingsWrapper.classList.add('opacity-40', 'pointer-events-none');
|
|
495
|
+
if (rtlStyle.parentNode) rtlStyle.parentNode.removeChild(rtlStyle);
|
|
496
|
+
document.querySelectorAll('[contenteditable="true"] p, [contenteditable="true"]').forEach(el => {
|
|
497
|
+
if (el.hasAttribute('dir')) el.removeAttribute('dir');
|
|
498
|
+
});
|
|
499
|
+
document.querySelectorAll('.prose > *, [data-testid="chat-message"] > *, .markdown-body > *, .leading-relaxed > *, [data-testid="user-input-step"], [data-testid="user-input-step"] > *').forEach(el => {
|
|
500
|
+
if (el.hasAttribute('dir')) el.removeAttribute('dir');
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
`).catch(err => console.error("Failed to inject RTL features:", err));
|
|
505
|
+
|
|
506
|
+
} catch(e) {
|
|
507
|
+
console.error("Failed to read offline font", e);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|