antigravity-rtl-patcher 2.1.0 → 2.2.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 +45 -91
- package/bin/cli.js +8 -11
- package/package.json +2 -2
- package/payload/preload-inject.js +351 -0
- package/src/patcher.js +85 -175
- package/src/paths.js +25 -51
- package/payload/rtl-engine.js +0 -461
- package/payload/styles.css +0 -234
package/README.md
CHANGED
|
@@ -1,126 +1,80 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Antigravity RTL Patcher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Smart RTL and Persian/Arabic typography support for the [Antigravity](https://antigravity.dev) desktop app. Patches the Electron runtime to detect right-to-left text and apply proper styling automatically.
|
|
4
4
|
|
|
5
|
-

|
|
9
|
-
|
|
10
|
-
**Intelligent RTL & Persian Typography Engine for Antigravity**
|
|
11
|
-
|
|
12
|
-
*One command. Full right-to-left support. Toggle on/off anytime.*
|
|
13
|
-
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 🧬 How It Works
|
|
19
|
-
|
|
20
|
-
Antigravity is built on Electron. This CLI tool directly patches the app's core workbench files to inject a smart **MutationObserver** engine that:
|
|
21
|
-
|
|
22
|
-
1. 🔍 **Watches** the DOM for new chat messages and AI responses in real-time
|
|
23
|
-
2. 🧠 **Detects** Persian/Arabic/Hebrew text using Unicode analysis with ratio-based scoring
|
|
24
|
-
3. 🎨 **Applies** `direction: rtl` and `unicode-bidi: plaintext` only where needed
|
|
25
|
-
4. ✍️ **Injects** the beautiful **Vazirmatn** font for perfect Persian/Arabic readability
|
|
26
|
-
5. 🔒 **Isolates** English code blocks inside RTL text so they don't break
|
|
27
|
-
6. 🎛️ **Toggle Panel** — Enable/disable RTL support anytime via the in-app panel
|
|
5
|
+
[](https://www.npmjs.com/package/antigravity-rtl-patcher)
|
|
6
|
+
[](https://www.npmjs.com/package/antigravity-rtl-patcher)
|
|
7
|
+
[](LICENSE)
|
|
28
8
|
|
|
29
9
|
---
|
|
30
10
|
|
|
31
|
-
##
|
|
11
|
+
## Install
|
|
32
12
|
|
|
33
13
|
```bash
|
|
34
|
-
# Linux / macOS
|
|
35
14
|
npx antigravity-rtl-patcher patch
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If Antigravity is installed in a system directory, use `sudo`:
|
|
36
18
|
|
|
37
|
-
|
|
19
|
+
```bash
|
|
38
20
|
sudo npx antigravity-rtl-patcher patch
|
|
39
21
|
```
|
|
40
22
|
|
|
23
|
+
Restart Antigravity after patching.
|
|
24
|
+
|
|
25
|
+
## Uninstall
|
|
26
|
+
|
|
41
27
|
```bash
|
|
42
|
-
|
|
43
|
-
npx antigravity-rtl-patcher patch
|
|
28
|
+
npx antigravity-rtl-patcher restore
|
|
44
29
|
```
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
---
|
|
31
|
+
This restores the original `app.asar` from the backup created during patching.
|
|
49
32
|
|
|
50
|
-
##
|
|
33
|
+
## Commands
|
|
51
34
|
|
|
52
|
-
|
|
35
|
+
| Command | Description |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `agy-rtl patch` | Apply RTL patch |
|
|
38
|
+
| `agy-rtl restore` | Remove patch, restore original |
|
|
39
|
+
| `agy-rtl status` | Show current patch status |
|
|
40
|
+
| `agy-rtl patch --path /custom/path` | Patch a custom installation |
|
|
53
41
|
|
|
54
|
-
|
|
55
|
-
- Settings are saved automatically and persist across restarts
|
|
56
|
-
- Works on every page — enable/disable applies everywhere
|
|
42
|
+
## How it works
|
|
57
43
|
|
|
58
|
-
|
|
44
|
+
Antigravity is an Electron app. The patcher extracts `app.asar`, appends an RTL engine to the preload script, and repacks the archive. The engine uses a `MutationObserver` to detect Persian, Arabic, and Hebrew text in real-time and applies `direction: rtl` where needed.
|
|
59
45
|
|
|
60
|
-
|
|
46
|
+
After patching, an RTL icon appears at the bottom of the Antigravity window. Click it to open a settings panel where you can enable or disable RTL support. The setting persists across restarts via `localStorage`.
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|---------|-------------|
|
|
64
|
-
| `agy-rtl patch` | Inject RTL support into Antigravity |
|
|
65
|
-
| `agy-rtl restore` | Remove patch and restore original files |
|
|
66
|
-
| `agy-rtl status` | Check if Antigravity is currently patched |
|
|
67
|
-
| `agy-rtl patch --path /custom/path` | Patch a custom installation directory |
|
|
48
|
+
### What the engine does
|
|
68
49
|
|
|
69
|
-
|
|
50
|
+
- Detects RTL text using Unicode range analysis with ratio-based scoring
|
|
51
|
+
- Applies directional styling only to elements that contain RTL text
|
|
52
|
+
- Injects the Vazirmatn font for improved Persian/Arabic readability
|
|
53
|
+
- Preserves LTR direction for code blocks inside RTL text
|
|
54
|
+
- Provides a toggle UI to enable/disable at any time
|
|
70
55
|
|
|
71
|
-
##
|
|
56
|
+
## After Antigravity updates
|
|
72
57
|
|
|
73
|
-
|
|
58
|
+
Updates may overwrite the patch. Run the patch command again:
|
|
74
59
|
|
|
75
60
|
```bash
|
|
76
61
|
npx antigravity-rtl-patcher patch
|
|
77
62
|
```
|
|
78
63
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## 🏗️ Architecture
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
antigravity-rtl-patcher/
|
|
85
|
-
├── bin/
|
|
86
|
-
│ └── cli.js # CLI entry point (commander-based)
|
|
87
|
-
├── src/
|
|
88
|
-
│ ├── patcher.js # Core logic: backup, inject, repack
|
|
89
|
-
│ ├── paths.js # OS-aware installation path detection
|
|
90
|
-
│ └── utils.js # Permission checks & helpers
|
|
91
|
-
├── payload/
|
|
92
|
-
│ ├── rtl-engine.js # MutationObserver RTL engine + Toggle UI
|
|
93
|
-
│ └── styles.css # Vazirmatn font + RTL + Panel styles
|
|
94
|
-
├── package.json
|
|
95
|
-
└── README.md
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Supported Installation Types
|
|
99
|
-
|
|
100
|
-
| Type | Description | Method |
|
|
101
|
-
|------|-------------|--------|
|
|
102
|
-
| **Unpacked** | App with `resources/app/` directory | Direct file modification |
|
|
103
|
-
| **ASAR** | App bundled as `app.asar` | Extract → Patch → Repack |
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## 🔐 Permissions
|
|
64
|
+
## Supported platforms
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
66
|
+
| Platform | Typical install locations |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Linux | `/opt/antigravity`, `~/Downloads/Antigravity-x64` |
|
|
69
|
+
| macOS | `/Applications/Antigravity.app` |
|
|
70
|
+
| Windows | `%LOCALAPPDATA%\Programs\Antigravity` |
|
|
113
71
|
|
|
114
|
-
|
|
72
|
+
Custom paths are supported via `--path`.
|
|
115
73
|
|
|
116
|
-
|
|
74
|
+
## Contributing
|
|
117
75
|
|
|
118
|
-
|
|
119
|
-
2. Create your feature branch (`git checkout -b feature/amazing`)
|
|
120
|
-
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
121
|
-
4. Push to the branch (`git push origin feature/amazing`)
|
|
122
|
-
5. Open a Pull Request
|
|
76
|
+
Issues and pull requests are welcome at [github.com/VaFa1726/antigravity-rtl-patcher](https://github.com/VaFa1726/antigravity-rtl-patcher).
|
|
123
77
|
|
|
124
|
-
##
|
|
78
|
+
## License
|
|
125
79
|
|
|
126
|
-
|
|
80
|
+
MIT
|
package/bin/cli.js
CHANGED
|
@@ -5,16 +5,13 @@ const chalk = require('chalk');
|
|
|
5
5
|
const { patch, restore, status } = require('../src/patcher');
|
|
6
6
|
|
|
7
7
|
const BANNER = `
|
|
8
|
-
${chalk.cyan('
|
|
9
|
-
${chalk.cyan('║')} ${chalk.bold.white('🌌 Antigravity RTL Patcher')} ${chalk.gray('v2.1.0')} ${chalk.cyan('║')}
|
|
10
|
-
${chalk.cyan('║')} ${chalk.gray('Intelligent RTL support for Antigravity')} ${chalk.cyan('║')}
|
|
11
|
-
${chalk.cyan('╚══════════════════════════════════════════════╝')}
|
|
8
|
+
${chalk.cyan('Antigravity Smart RTL Patcher')} ${chalk.gray('v2.2.0')}
|
|
12
9
|
`;
|
|
13
10
|
|
|
14
11
|
program
|
|
15
12
|
.name('agy-rtl')
|
|
16
|
-
.description('RTL
|
|
17
|
-
.version('2.
|
|
13
|
+
.description('RTL patcher for Antigravity')
|
|
14
|
+
.version('2.2.0');
|
|
18
15
|
|
|
19
16
|
program
|
|
20
17
|
.command('patch')
|
|
@@ -25,35 +22,35 @@ program
|
|
|
25
22
|
try {
|
|
26
23
|
await patch(options.path);
|
|
27
24
|
} catch (err) {
|
|
28
|
-
console.error(chalk.red('\
|
|
25
|
+
console.error(chalk.red('\nPatch failed:'), err.message);
|
|
29
26
|
process.exit(1);
|
|
30
27
|
}
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
program
|
|
34
31
|
.command('restore')
|
|
35
|
-
.description('
|
|
32
|
+
.description('Remove RTL patch and restore Antigravity')
|
|
36
33
|
.option('-p, --path <path>', 'Custom path to Antigravity installation')
|
|
37
34
|
.action(async (options) => {
|
|
38
35
|
console.log(BANNER);
|
|
39
36
|
try {
|
|
40
37
|
await restore(options.path);
|
|
41
38
|
} catch (err) {
|
|
42
|
-
console.error(chalk.red('\
|
|
39
|
+
console.error(chalk.red('\nRestore failed:'), err.message);
|
|
43
40
|
process.exit(1);
|
|
44
41
|
}
|
|
45
42
|
});
|
|
46
43
|
|
|
47
44
|
program
|
|
48
45
|
.command('status')
|
|
49
|
-
.description('Check
|
|
46
|
+
.description('Check current patch status')
|
|
50
47
|
.option('-p, --path <path>', 'Custom path to Antigravity installation')
|
|
51
48
|
.action(async (options) => {
|
|
52
49
|
console.log(BANNER);
|
|
53
50
|
try {
|
|
54
51
|
await status(options.path);
|
|
55
52
|
} catch (err) {
|
|
56
|
-
console.error(chalk.red('\
|
|
53
|
+
console.error(chalk.red('\nStatus check failed:'), err.message);
|
|
57
54
|
process.exit(1);
|
|
58
55
|
}
|
|
59
56
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-rtl-patcher",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Smart RTL and Persian/Arabic typography patcher for Antigravity",
|
|
5
5
|
"main": "src/patcher.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"antigravity-rtl-patcher": "./bin/cli.js",
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGY RTL — Preload Injection Snippet
|
|
3
|
+
* Appended to Antigravity's preload.js at patch time.
|
|
4
|
+
* Injects the RTL engine + toggle panel into every renderer page.
|
|
5
|
+
*
|
|
6
|
+
* MARKER: __AGY_RTL_INJECTED__
|
|
7
|
+
*/
|
|
8
|
+
(function _agyRtlPreloadInit() {
|
|
9
|
+
// Only run in renderer context
|
|
10
|
+
if (typeof document === 'undefined') {
|
|
11
|
+
// We're in the preload context before DOM exists.
|
|
12
|
+
// Schedule injection for when the page actually loads.
|
|
13
|
+
const { webFrame } = require('electron');
|
|
14
|
+
|
|
15
|
+
webFrame.executeJavaScript(`(${_agyRtlRendererMain.toString()})();`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
_agyRtlRendererMain();
|
|
19
|
+
})();
|
|
20
|
+
|
|
21
|
+
function _agyRtlRendererMain() {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
// Prevent double injection
|
|
25
|
+
if (window.__AGY_RTL_LOADED__) return;
|
|
26
|
+
window.__AGY_RTL_LOADED__ = true;
|
|
27
|
+
|
|
28
|
+
// ─── Configuration ───────────────────────────────────────
|
|
29
|
+
var STORAGE_KEY = 'agy-rtl-enabled';
|
|
30
|
+
var PANEL_ID = 'agy-rtl-panel';
|
|
31
|
+
var TOGGLE_BTN_ID = 'agy-rtl-toggle-btn';
|
|
32
|
+
var STATUS_ICON_ID = 'agy-rtl-status-icon';
|
|
33
|
+
var GITHUB_URL = 'https://github.com/VaFa1726/antigravity-rtl-patcher';
|
|
34
|
+
|
|
35
|
+
// RTL detection
|
|
36
|
+
var RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
|
37
|
+
var RTL_CHAR_REGEX = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/g;
|
|
38
|
+
var MIN_RTL_RATIO = 0.3;
|
|
39
|
+
var PROCESSED_ATTR = 'data-agy-rtl';
|
|
40
|
+
|
|
41
|
+
var TARGET_SELECTORS = [
|
|
42
|
+
'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'td', 'th',
|
|
43
|
+
'.chat-response-content p', '.chat-response-content li',
|
|
44
|
+
'.chat-response-content h1', '.chat-response-content h2',
|
|
45
|
+
'.chat-response-content h3', '.chat-response-content h4',
|
|
46
|
+
'.chat-response-content blockquote',
|
|
47
|
+
'.markdown-body p', '.markdown-body li',
|
|
48
|
+
'.markdown-body h1', '.markdown-body h2',
|
|
49
|
+
'.markdown-body h3', '.markdown-body blockquote',
|
|
50
|
+
'.rendered-markdown p', '.rendered-markdown li',
|
|
51
|
+
'.rendered-markdown h1', '.rendered-markdown h2',
|
|
52
|
+
'.rendered-markdown h3', '.rendered-markdown blockquote',
|
|
53
|
+
'.monaco-hover-content p', '.suggest-details p',
|
|
54
|
+
'.notification-toast-container p',
|
|
55
|
+
].join(', ');
|
|
56
|
+
|
|
57
|
+
// ─── State ───────────────────────────────────────────────
|
|
58
|
+
var isEnabled = getStoredState();
|
|
59
|
+
var observer = null;
|
|
60
|
+
var panelVisible = false;
|
|
61
|
+
|
|
62
|
+
// ─── CSS ─────────────────────────────────────────────────
|
|
63
|
+
var CSS = [
|
|
64
|
+
'@import url("https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap");',
|
|
65
|
+
'[data-agy-rtl="true"]{font-family:"Vazirmatn",system-ui,-apple-system,"Segoe UI",sans-serif!important;line-height:1.85!important;word-spacing:.02em}',
|
|
66
|
+
'[data-agy-rtl="true"] code,[data-agy-rtl="true"] pre,[data-agy-rtl="true"] .monaco-tokenized-source{direction:ltr!important;text-align:left!important;unicode-bidi:isolate!important;font-family:"Cascadia Code","Fira Code","JetBrains Mono",Consolas,monospace!important;display:inline-block}',
|
|
67
|
+
'[data-agy-rtl="true"] code{margin:0 3px;padding:1px 5px;border-radius:3px}',
|
|
68
|
+
'[data-agy-rtl="true"] li{list-style-position:inside}',
|
|
69
|
+
// Status icon
|
|
70
|
+
'.agy-rtl-status-icon{position:fixed;bottom:0;right:60px;z-index:99999;display:flex;align-items:center;justify-content:center;width:32px;height:22px;cursor:pointer;color:#ccc;opacity:.85;transition:opacity .2s,color .2s,background .15s;border-radius:3px 3px 0 0;user-select:none;-webkit-user-select:none;background:rgba(0,0,0,.25)}',
|
|
71
|
+
'.agy-rtl-status-icon:hover{opacity:1;color:#fff;background:rgba(255,255,255,.12)}',
|
|
72
|
+
'.agy-rtl-status-dot{position:absolute;top:2px;right:2px;width:6px;height:6px;border-radius:50%;background:#666;transition:background .3s,box-shadow .3s}',
|
|
73
|
+
'.agy-rtl-status-dot.active{background:#4caf50;box-shadow:0 0 4px rgba(76,175,80,.6)}',
|
|
74
|
+
// Panel
|
|
75
|
+
'.agy-rtl-panel{position:fixed;z-index:100000;min-width:260px;max-width:300px;background:#252526;border:1px solid #454545;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,.45),0 2px 8px rgba(0,0,0,.2);padding:16px;opacity:0;visibility:hidden;transform:translateY(8px) scale(.96);transition:opacity .2s,transform .2s,visibility .2s;font-family:-apple-system,"Segoe UI",system-ui,sans-serif;font-size:13px;color:#ccc}',
|
|
76
|
+
'.agy-rtl-panel.visible{opacity:1;visibility:visible;transform:translateY(0) scale(1)}',
|
|
77
|
+
'.agy-rtl-panel-header{margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid #3a3a3a}',
|
|
78
|
+
'.agy-rtl-panel-title{font-size:14px;font-weight:600;color:#e0e0e0;letter-spacing:-.01em}',
|
|
79
|
+
'.agy-rtl-panel-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0}',
|
|
80
|
+
'.agy-rtl-panel-label{font-size:13px;color:#ccc}',
|
|
81
|
+
'.agy-rtl-panel-sep{height:1px;background:#3a3a3a;margin:8px 0}',
|
|
82
|
+
// Toggle switch
|
|
83
|
+
'.agy-rtl-switch{position:relative;display:inline-block;width:40px;height:22px;cursor:pointer;flex-shrink:0}',
|
|
84
|
+
'.agy-rtl-switch input{opacity:0;width:0;height:0;position:absolute}',
|
|
85
|
+
'.agy-rtl-slider{position:absolute;inset:0;background:#555;border-radius:22px;transition:background .25s,box-shadow .25s}',
|
|
86
|
+
'.agy-rtl-slider::before{content:"";position:absolute;width:16px;height:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:transform .25s;box-shadow:0 1px 3px rgba(0,0,0,.3)}',
|
|
87
|
+
'.agy-rtl-switch input:checked+.agy-rtl-slider{background:#4b9cf5;box-shadow:0 0 8px rgba(75,156,245,.3)}',
|
|
88
|
+
'.agy-rtl-switch input:checked+.agy-rtl-slider::before{transform:translateX(18px)}',
|
|
89
|
+
// GitHub link
|
|
90
|
+
'.agy-rtl-panel-gh{justify-content:center;padding-top:4px}',
|
|
91
|
+
'.agy-rtl-gh-link{display:inline-flex;align-items:center;gap:6px;color:#9cdcfe;text-decoration:none;font-size:12px;opacity:.8;transition:opacity .2s}',
|
|
92
|
+
'.agy-rtl-gh-link:hover{opacity:1;text-decoration:underline}',
|
|
93
|
+
].join('\n');
|
|
94
|
+
|
|
95
|
+
// ─── Helpers ─────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function getStoredState() {
|
|
98
|
+
try {
|
|
99
|
+
var s = localStorage.getItem(STORAGE_KEY);
|
|
100
|
+
return s === null ? true : s === 'true';
|
|
101
|
+
} catch (e) { return true; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setStoredState(v) {
|
|
105
|
+
try { localStorage.setItem(STORAGE_KEY, v ? 'true' : 'false'); }
|
|
106
|
+
catch (e) { /* ignore */ }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isRTL(text) {
|
|
110
|
+
if (!text || text.trim().length === 0) return false;
|
|
111
|
+
if (!RTL_REGEX.test(text)) return false;
|
|
112
|
+
var m = text.match(RTL_CHAR_REGEX);
|
|
113
|
+
if (!m) return false;
|
|
114
|
+
var alpha = text.replace(/[\s\d\W]/g, '').length;
|
|
115
|
+
return alpha > 0 && (m.length / alpha) >= MIN_RTL_RATIO;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applyRTL(el) {
|
|
119
|
+
if (el.getAttribute(PROCESSED_ATTR)) return;
|
|
120
|
+
if (isRTL(el.textContent || '')) {
|
|
121
|
+
el.setAttribute(PROCESSED_ATTR, 'true');
|
|
122
|
+
el.style.direction = 'rtl';
|
|
123
|
+
el.style.textAlign = 'start';
|
|
124
|
+
el.style.unicodeBidi = 'plaintext';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function removeRTL(el) {
|
|
129
|
+
if (el.getAttribute(PROCESSED_ATTR)) {
|
|
130
|
+
el.removeAttribute(PROCESSED_ATTR);
|
|
131
|
+
el.style.direction = '';
|
|
132
|
+
el.style.textAlign = '';
|
|
133
|
+
el.style.unicodeBidi = '';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function scanElement(root) {
|
|
138
|
+
if (!root || root.nodeType !== 1) return;
|
|
139
|
+
try { if (root.matches && root.matches(TARGET_SELECTORS)) applyRTL(root); } catch (e) {}
|
|
140
|
+
try { root.querySelectorAll(TARGET_SELECTORS).forEach(applyRTL); } catch (e) {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function clearAllRTL() {
|
|
144
|
+
try { document.querySelectorAll('[' + PROCESSED_ATTR + ']').forEach(removeRTL); } catch (e) {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Observer ────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function startObserver() {
|
|
150
|
+
if (observer) return;
|
|
151
|
+
observer = new MutationObserver(function (muts) {
|
|
152
|
+
if (!isEnabled) return;
|
|
153
|
+
muts.forEach(function (m) {
|
|
154
|
+
m.addedNodes.forEach(function (n) { if (n.nodeType === 1) scanElement(n); });
|
|
155
|
+
if (m.type === 'characterData' && m.target.parentElement) applyRTL(m.target.parentElement);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
159
|
+
scanElement(document.body);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function stopObserver() {
|
|
163
|
+
if (observer) { observer.disconnect(); observer = null; }
|
|
164
|
+
clearAllRTL();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── UI ──────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
function injectCSS() {
|
|
170
|
+
if (document.getElementById('agy-rtl-styles')) return;
|
|
171
|
+
var style = document.createElement('style');
|
|
172
|
+
style.id = 'agy-rtl-styles';
|
|
173
|
+
style.textContent = CSS;
|
|
174
|
+
(document.head || document.documentElement).appendChild(style);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createStatusIcon() {
|
|
178
|
+
if (document.getElementById(STATUS_ICON_ID)) return;
|
|
179
|
+
|
|
180
|
+
var icon = document.createElement('div');
|
|
181
|
+
icon.id = STATUS_ICON_ID;
|
|
182
|
+
icon.className = 'agy-rtl-status-icon';
|
|
183
|
+
icon.setAttribute('title', 'Antigravity Smart RTL');
|
|
184
|
+
|
|
185
|
+
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
186
|
+
svg.setAttribute('width', '16');
|
|
187
|
+
svg.setAttribute('height', '16');
|
|
188
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
189
|
+
svg.setAttribute('fill', 'none');
|
|
190
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
191
|
+
svg.setAttribute('stroke-width', '2');
|
|
192
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
193
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
194
|
+
|
|
195
|
+
var p1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
196
|
+
p1.setAttribute('d', 'M16 4H9.5a3.5 3.5 0 1 0 0 7H12');
|
|
197
|
+
svg.appendChild(p1);
|
|
198
|
+
var l1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
199
|
+
l1.setAttribute('x1', '12'); l1.setAttribute('y1', '4');
|
|
200
|
+
l1.setAttribute('x2', '12'); l1.setAttribute('y2', '20');
|
|
201
|
+
svg.appendChild(l1);
|
|
202
|
+
var l2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
203
|
+
l2.setAttribute('x1', '16'); l2.setAttribute('y1', '4');
|
|
204
|
+
l2.setAttribute('x2', '16'); l2.setAttribute('y2', '20');
|
|
205
|
+
svg.appendChild(l2);
|
|
206
|
+
|
|
207
|
+
icon.appendChild(svg);
|
|
208
|
+
|
|
209
|
+
var dot = document.createElement('span');
|
|
210
|
+
dot.className = 'agy-rtl-status-dot' + (isEnabled ? ' active' : '');
|
|
211
|
+
icon.appendChild(dot);
|
|
212
|
+
|
|
213
|
+
icon.addEventListener('click', function (e) { e.stopPropagation(); togglePanel(); });
|
|
214
|
+
document.body.appendChild(icon);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function createPanel() {
|
|
218
|
+
if (document.getElementById(PANEL_ID)) return;
|
|
219
|
+
|
|
220
|
+
var panel = document.createElement('div');
|
|
221
|
+
panel.id = PANEL_ID;
|
|
222
|
+
panel.className = 'agy-rtl-panel';
|
|
223
|
+
|
|
224
|
+
// Header
|
|
225
|
+
var header = document.createElement('div');
|
|
226
|
+
header.className = 'agy-rtl-panel-header';
|
|
227
|
+
var title = document.createElement('span');
|
|
228
|
+
title.className = 'agy-rtl-panel-title';
|
|
229
|
+
title.textContent = 'Antigravity Smart RTL';
|
|
230
|
+
header.appendChild(title);
|
|
231
|
+
panel.appendChild(header);
|
|
232
|
+
|
|
233
|
+
// Toggle row
|
|
234
|
+
var row = document.createElement('div');
|
|
235
|
+
row.className = 'agy-rtl-panel-row';
|
|
236
|
+
var label = document.createElement('span');
|
|
237
|
+
label.className = 'agy-rtl-panel-label';
|
|
238
|
+
label.textContent = 'Enabled';
|
|
239
|
+
row.appendChild(label);
|
|
240
|
+
|
|
241
|
+
var sw = document.createElement('label');
|
|
242
|
+
sw.className = 'agy-rtl-switch';
|
|
243
|
+
var cb = document.createElement('input');
|
|
244
|
+
cb.type = 'checkbox';
|
|
245
|
+
cb.id = TOGGLE_BTN_ID;
|
|
246
|
+
cb.checked = isEnabled;
|
|
247
|
+
var slider = document.createElement('span');
|
|
248
|
+
slider.className = 'agy-rtl-slider';
|
|
249
|
+
sw.appendChild(cb);
|
|
250
|
+
sw.appendChild(slider);
|
|
251
|
+
row.appendChild(sw);
|
|
252
|
+
panel.appendChild(row);
|
|
253
|
+
|
|
254
|
+
// Separator
|
|
255
|
+
var sep = document.createElement('div');
|
|
256
|
+
sep.className = 'agy-rtl-panel-sep';
|
|
257
|
+
panel.appendChild(sep);
|
|
258
|
+
|
|
259
|
+
// GitHub link
|
|
260
|
+
var ghRow = document.createElement('div');
|
|
261
|
+
ghRow.className = 'agy-rtl-panel-row agy-rtl-panel-gh';
|
|
262
|
+
var ghLink = document.createElement('a');
|
|
263
|
+
ghLink.className = 'agy-rtl-gh-link';
|
|
264
|
+
ghLink.href = GITHUB_URL;
|
|
265
|
+
ghLink.target = '_blank';
|
|
266
|
+
ghLink.rel = 'noopener noreferrer';
|
|
267
|
+
|
|
268
|
+
var ghSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
269
|
+
ghSvg.setAttribute('width', '14');
|
|
270
|
+
ghSvg.setAttribute('height', '14');
|
|
271
|
+
ghSvg.setAttribute('viewBox', '0 0 16 16');
|
|
272
|
+
ghSvg.setAttribute('fill', 'currentColor');
|
|
273
|
+
var ghP = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
274
|
+
ghP.setAttribute('d', 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z');
|
|
275
|
+
ghSvg.appendChild(ghP);
|
|
276
|
+
ghLink.appendChild(ghSvg);
|
|
277
|
+
|
|
278
|
+
var ghText = document.createElement('span');
|
|
279
|
+
ghText.textContent = ' Star on GitHub';
|
|
280
|
+
ghLink.appendChild(ghText);
|
|
281
|
+
ghRow.appendChild(ghLink);
|
|
282
|
+
panel.appendChild(ghRow);
|
|
283
|
+
|
|
284
|
+
// Events
|
|
285
|
+
cb.addEventListener('change', function () {
|
|
286
|
+
isEnabled = cb.checked;
|
|
287
|
+
setStoredState(isEnabled);
|
|
288
|
+
updateState();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
document.addEventListener('click', function (e) {
|
|
292
|
+
var p = document.getElementById(PANEL_ID);
|
|
293
|
+
var ic = document.getElementById(STATUS_ICON_ID);
|
|
294
|
+
if (p && panelVisible && !p.contains(e.target) && !ic.contains(e.target)) hidePanel();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
document.body.appendChild(panel);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function togglePanel() {
|
|
301
|
+
panelVisible ? hidePanel() : showPanel();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function showPanel() {
|
|
305
|
+
var panel = document.getElementById(PANEL_ID);
|
|
306
|
+
if (!panel) { createPanel(); panel = document.getElementById(PANEL_ID); }
|
|
307
|
+
|
|
308
|
+
var icon = document.getElementById(STATUS_ICON_ID);
|
|
309
|
+
if (icon) {
|
|
310
|
+
var r = icon.getBoundingClientRect();
|
|
311
|
+
panel.style.bottom = (window.innerHeight - r.top + 8) + 'px';
|
|
312
|
+
panel.style.right = (window.innerWidth - r.right) + 'px';
|
|
313
|
+
}
|
|
314
|
+
panel.classList.add('visible');
|
|
315
|
+
panelVisible = true;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hidePanel() {
|
|
319
|
+
var panel = document.getElementById(PANEL_ID);
|
|
320
|
+
if (panel) panel.classList.remove('visible');
|
|
321
|
+
panelVisible = false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function updateState() {
|
|
325
|
+
var dot = document.querySelector('.agy-rtl-status-dot');
|
|
326
|
+
if (dot) {
|
|
327
|
+
if (isEnabled) dot.classList.add('active');
|
|
328
|
+
else dot.classList.remove('active');
|
|
329
|
+
}
|
|
330
|
+
var cb = document.getElementById(TOGGLE_BTN_ID);
|
|
331
|
+
if (cb) cb.checked = isEnabled;
|
|
332
|
+
|
|
333
|
+
if (isEnabled) startObserver();
|
|
334
|
+
else stopObserver();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ─── Init ────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
function init() {
|
|
340
|
+
injectCSS();
|
|
341
|
+
createStatusIcon();
|
|
342
|
+
createPanel();
|
|
343
|
+
updateState();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (document.readyState === 'loading') {
|
|
347
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
348
|
+
} else {
|
|
349
|
+
setTimeout(init, 800);
|
|
350
|
+
}
|
|
351
|
+
}
|