Skip to content

Commit 3c30708

Browse files
committed
Add language tests, fix old editor UI, make pop
up respond to websites dynamiclly
1 parent de3336b commit 3c30708

File tree

12 files changed

+230
-85
lines changed

12 files changed

+230
-85
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@
3333
"test:changed": "vitest related --run --passWithNoTests",
3434
"test:gm": "vitest run tests/unit/gm_*.test.js tests/unit/gm*.test.js",
3535
"test:runtime": "vitest run tests/unit/background.test.js tests/unit/content_bridge.test.js tests/unit/offscreen.test.js tests/unit/userscript_adapter.test.js tests/unit/script_registry.test.js tests/unit/browser_adapter.test.js",
36+
"test:i18n": "node scripts/check-i18n.js",
3637
"prepare": "husky",
37-
"precommit:run": "lint-staged && npm run test:changed"
38+
"precommit:run": "npm run lint && npm run format:check && npm run test:i18n && npm run test"
3839
},
3940
"dependencies": {
4041
"@babel/runtime": "7.28.4",

scripts/check-i18n.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { fileURLToPath } from 'url';
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = path.dirname(__filename);
7+
const rootDir = path.resolve(__dirname, '..');
8+
9+
const locales = ['en', 'es', 'fr'];
10+
const localeFiles = locales.reduce((acc, lang) => {
11+
acc[lang] = JSON.parse(
12+
fs.readFileSync(path.join(rootDir, 'src', '_locales', lang, 'messages.json'), 'utf8')
13+
);
14+
return acc;
15+
}, {});
16+
17+
const seenKeys = new Set();
18+
const missingKeys = {}; // { key: [languages_missing_it] }
19+
20+
function scanFiles(dir) {
21+
const files = fs.readdirSync(dir);
22+
23+
for (const file of files) {
24+
const fullPath = path.join(dir, file);
25+
const stat = fs.statSync(fullPath);
26+
27+
if (stat.isDirectory()) {
28+
if (file !== 'node_modules' && file !== '.git' && file !== 'build' && file !== 'docs-src') {
29+
scanFiles(fullPath);
30+
}
31+
} else if (file.endsWith('.html') || file.endsWith('.js')) {
32+
const content = fs.readFileSync(fullPath, 'utf8');
33+
const htmlRegex = /data-i18n(?:-attr|-title|-placeholder)?="([^"]+)"/g;
34+
let match;
35+
while ((match = htmlRegex.exec(content)) !== null) {
36+
const key = match[1];
37+
if (key !== 'title' && key !== 'placeholder') {
38+
seenKeys.add(key);
39+
}
40+
}
41+
42+
const jsRegex = /getMessage(?:Sync)?\(['"]([^'"]+)['"]\)/g;
43+
while ((match = jsRegex.exec(content)) !== null) {
44+
const key = match[1];
45+
if (key !== 'key' && key !== 'title' && key !== 'placeholder') {
46+
seenKeys.add(key);
47+
}
48+
}
49+
}
50+
}
51+
}
52+
53+
console.log('Scanning codebase for i18n keys...');
54+
scanFiles(path.join(rootDir, 'src'));
55+
56+
let hasError = false;
57+
58+
for (const key of seenKeys) {
59+
for (const lang of locales) {
60+
if (!localeFiles[lang][key]) {
61+
if (!missingKeys[key]) missingKeys[key] = [];
62+
missingKeys[key].push(lang);
63+
hasError = true;
64+
}
65+
}
66+
}
67+
68+
const enKeys = Object.keys(localeFiles['en']);
69+
for (const key of enKeys) {
70+
for (const lang of locales) {
71+
if (lang === 'en') continue;
72+
if (!localeFiles[lang][key]) {
73+
if (!missingKeys[key]) missingKeys[key] = [];
74+
if (!missingKeys[key].includes(lang)) {
75+
missingKeys[key].push(lang);
76+
}
77+
hasError = true;
78+
}
79+
}
80+
}
81+
82+
if (hasError) {
83+
console.error('\x1b[31m%s\x1b[0m', 'i18n Coverage Check Failed!');
84+
console.table(
85+
Object.entries(missingKeys).map(([key, langs]) => ({
86+
Key: key,
87+
'Missing In': langs.join(', '),
88+
}))
89+
);
90+
process.exit(1);
91+
} else {
92+
console.log(
93+
'\x1b[32m%s\x1b[0m',
94+
'i18n Coverage Check Passed! All keys are present in all languages.'
95+
);
96+
process.exit(0);
97+
}

src/_locales/en/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@
110110
"settingsLanguageDesc": {
111111
"message": "Choose your preferred interface language"
112112
},
113+
"settingsCheckUpdates": {
114+
"message": "Check for updates"
115+
},
116+
"settingsCheckUpdatesDesc": {
117+
"message": "Manually check for userscript updates"
118+
},
119+
"settingsCheckUpdatesButton": {
120+
"message": "Check for updates"
121+
},
113122
"languageBrowserDefault": {
114123
"message": "Browser Default (Auto)"
115124
},

src/_locales/es/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@
110110
"settingsLanguageDesc": {
111111
"message": "Elige tu idioma de interfaz preferido"
112112
},
113+
"settingsCheckUpdates": {
114+
"message": "Buscar actualizaciones"
115+
},
116+
"settingsCheckUpdatesDesc": {
117+
"message": "Buscar actualizaciones de userscripts manualmente"
118+
},
119+
"settingsCheckUpdatesButton": {
120+
"message": "Buscar actualizaciones"
121+
},
113122
"languageBrowserDefault": {
114123
"message": "Predeterminado del Navegador (Auto)"
115124
},

src/_locales/fr/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@
110110
"settingsLanguageDesc": {
111111
"message": "Choisissez votre langue d'interface préférée"
112112
},
113+
"settingsCheckUpdates": {
114+
"message": "Vérifier les mises à jour"
115+
},
116+
"settingsCheckUpdatesDesc": {
117+
"message": "Vérifier manuellement les mises à jour des userscripts"
118+
},
119+
"settingsCheckUpdatesButton": {
120+
"message": "Vérifier les mises à jour"
121+
},
113122
"languageBrowserDefault": {
114123
"message": "Défaut du Navigateur (Auto)"
115124
},

src/background/background.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,15 @@ const messageHandlers = {
661661

662662
return { success: true };
663663
},
664+
665+
checkUpdates: async () => {
666+
try {
667+
await scriptUpdater.checkAndPerformUpdates();
668+
return { success: true };
669+
} catch (error) {
670+
return { error: error.message };
671+
}
672+
},
664673
};
665674

666675
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {

src/dashboard/dashboard.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ <h3 data-i18n="settingsGeneral">General</h3>
152152
<option value="fr">Français (French)</option>
153153
</select>
154154
</div>
155+
<div class="setting-item">
156+
<span class="setting-label">
157+
<strong data-i18n="settingsCheckUpdates">Check for updates</strong>
158+
<small data-i18n="settingsCheckUpdatesDesc"
159+
>Manually check for userscript updates</small
160+
>
161+
</span>
162+
<button type="button" id="checkUpdatesBtn" class="btn-secondary">
163+
<i data-feather="refresh-cw"></i>
164+
<span data-i18n="settingsCheckUpdatesButton">Check for updates</span>
165+
</button>
166+
</div>
155167
</div>
156168
<div class="settings-group">
157169
<h3 data-i18n="settingsSecurity">Security</h3>

src/dashboard/dashboard.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function initDashboard() {
3131
confirmFirstRun: document.getElementById('confirmFirstRun'),
3232
accentColor: document.getElementById('accentColor'),
3333
languageSelector: document.getElementById('languageSelector'),
34+
checkUpdatesBtn: document.getElementById('checkUpdatesBtn'),
3435
},
3536
greasyfork: {
3637
button: document.getElementById('greasyforkBtn'),
@@ -83,6 +84,30 @@ function setupEventListeners(elements, state) {
8384
saveSettings(elements.settings);
8485
});
8586

87+
elements.settings.checkUpdatesBtn?.addEventListener('click', async () => {
88+
const btn = elements.settings.checkUpdatesBtn;
89+
const originalContent = btn.innerHTML;
90+
btn.disabled = true;
91+
btn.innerHTML = '<i data-feather="loader"></i><span>Checking...</span>';
92+
feather.replace();
93+
94+
try {
95+
const response = await chrome.runtime.sendMessage({ action: 'checkUpdates' });
96+
if (response && response.success) {
97+
showNotification('Update check completed', 'success');
98+
} else if (response && response.error) {
99+
showNotification(`Update check failed: ${response.error}`, 'error');
100+
}
101+
} catch (error) {
102+
showNotification('Error checking for updates', 'error');
103+
logger.error('Update check error:', error);
104+
} finally {
105+
btn.disabled = false;
106+
btn.innerHTML = originalContent;
107+
feather.replace();
108+
}
109+
});
110+
86111
// bulk export
87112
elements.exportAllBtn?.addEventListener('click', exportAllScripts);
88113

src/editor/editor.html

Lines changed: 42 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -634,11 +634,6 @@ <h3>Keyboard Shortcuts</h3>
634634
<td><kbd>Ctrl</kbd> + <kbd>S</kbd></td>
635635
<td><kbd>Cmd</kbd> + <kbd>S</kbd></td>
636636
</tr>
637-
<tr>
638-
<td>Format Code</td>
639-
<td><kbd>Alt</kbd> + <kbd>F</kbd></td>
640-
<td><kbd>Alt</kbd> + <kbd>F</kbd></td>
641-
</tr>
642637
<tr>
643638
<td>Autocomplete</td>
644639
<td><kbd>Ctrl</kbd> + <kbd>Space</kbd></td>
@@ -670,16 +665,15 @@ <h3>Keyboard Shortcuts</h3>
670665
</tr>
671666
</tbody>
672667
</table>
673-
<p class="help-note" style="margin-top: 8px; color: #9ca3af">
674-
Notes: Shortcuts reflect the current editor configuration. Formatting uses the
675-
built-in beautifier. The minimap can also be toggled from Settings.
676-
</p>
677668
</div>
678669
</div>
679670
<div class="help-tab-content" id="help-tab-wildcards">
680671
<div class="help-section">
681672
<h3>URL Matching with Wildcards</h3>
682-
<p>The URL matching system supports the following patterns:</p>
673+
<p>
674+
CodeTweak uses a match-pattern style URL matcher. Patterns are split into
675+
<code>scheme://host/path</code>.
676+
</p>
683677
<table class="wildcards-table">
684678
<thead>
685679
<tr>
@@ -690,19 +684,43 @@ <h3>URL Matching with Wildcards</h3>
690684
</thead>
691685
<tbody>
692686
<tr>
693-
<td><code>*</code></td>
694-
<td>Matches any characters except <code>/</code></td>
695-
<td><code>https://example.com/*</code> matches any page on example.com</td>
687+
<td><code>*://</code></td>
688+
<td>Any scheme (<code>http</code> or <code>https</code>)</td>
689+
<td><code>*://example.com/*</code></td>
690+
</tr>
691+
<tr>
692+
<td><code>*</code> (host)</td>
693+
<td>Any host</td>
694+
<td><code>*://*/*</code> matches all URLs</td>
695+
</tr>
696+
<tr>
697+
<td><code>*.example.com</code></td>
698+
<td>Any subdomain of a domain (also matches the bare domain)</td>
699+
<td><code>https://*.example.com/*</code></td>
700+
</tr>
701+
<tr>
702+
<td><code>*</code> (path)</td>
703+
<td>Matches one or more path segments</td>
704+
<td>
705+
<code>https://example.com/*</code> matches <code>/a</code>, <code>/a/b</code>,
706+
etc.
707+
</td>
696708
</tr>
697709
<tr>
698-
<td><code>**</code></td>
699-
<td>Matches any characters including <code>/</code></td>
700-
<td><code>https://**/api/**</code> matches any API endpoint on any domain</td>
710+
<td><code>**</code> (path segment)</td>
711+
<td>Matches across path boundaries (including <code>/</code>)</td>
712+
<td>
713+
<code>https://example.com/api/**</code> matches <code>/api</code>,
714+
<code>/api/v1/users</code>, etc.
715+
</td>
701716
</tr>
702717
<tr>
703-
<td><code>?</code></td>
704-
<td>Matches any single character</td>
705-
<td><code>file?.js</code> matches file1.js, file2.js, etc.</td>
718+
<td><code>foo*</code> (path segment)</td>
719+
<td>Wildcard within a single path segment</td>
720+
<td>
721+
<code>https://example.com/foo*</code> matches <code>/foo</code>,
722+
<code>/foobar</code>, etc.
723+
</td>
706724
</tr>
707725
</tbody>
708726
</table>
@@ -713,74 +731,18 @@ <h3>URL Matching with Wildcards</h3>
713731
<h3>GM API Documentation</h3>
714732
<p>
715733
The GM API provides special functions for userscripts to interact with the browser
716-
and page.<br />
734+
and page.
735+
</p>
736+
<p>
717737
<a
718-
href="https://wiki.greasespot.net/Greasemonkey_Manual:API"
738+
href="https://mrblankcoding.github.io/CodeTweak/reference/gm-apis"
719739
target="_blank"
720740
rel="noopener"
721741
style="color: #4ea1ff; text-decoration: underline"
722742
>
723-
View the full official GM API documentation
743+
Open the official GM API documentation
724744
</a>
725745
</p>
726-
<table class="gmapi-table">
727-
<thead>
728-
<tr>
729-
<th>API</th>
730-
<th>Description</th>
731-
</tr>
732-
</thead>
733-
<tbody>
734-
<tr>
735-
<td><code>GM_setValue</code></td>
736-
<td>Stores a value that persists across page loads.</td>
737-
</tr>
738-
<tr>
739-
<td><code>GM_getValue</code></td>
740-
<td>Retrieves a value stored with <code>GM_setValue</code>.</td>
741-
</tr>
742-
<tr>
743-
<td><code>GM_deleteValue</code></td>
744-
<td>Deletes a value stored with <code>GM_setValue</code>.</td>
745-
</tr>
746-
<tr>
747-
<td><code>GM_listValues</code></td>
748-
<td>Lists all keys stored with <code>GM_setValue</code>.</td>
749-
</tr>
750-
<tr>
751-
<td><code>GM_openInTab</code></td>
752-
<td>Opens a URL in a new browser tab.</td>
753-
</tr>
754-
<tr>
755-
<td><code>GM_notification</code></td>
756-
<td>Shows a desktop notification.</td>
757-
</tr>
758-
<tr>
759-
<td><code>GM_getResourceText</code></td>
760-
<td>Gets the content of a resource as text.</td>
761-
</tr>
762-
<tr>
763-
<td><code>GM_getResourceURL</code></td>
764-
<td>Gets the URL of a resource.</td>
765-
</tr>
766-
<tr>
767-
<td><code>GM_addStyle</code></td>
768-
<td>Injects CSS styles into the page.</td>
769-
</tr>
770-
<tr>
771-
<td><code>GM_registerMenuCommand</code></td>
772-
<td>Adds a custom command to the userscript menu.</td>
773-
</tr>
774-
<tr>
775-
<td><code>GM_setClipboard</code></td>
776-
<td>Copies data to the clipboard.</td>
777-
</tr>
778-
<tr>
779-
<td><code>GM_xmlhttpRequest</code></td>
780-
<td>Makes cross-origin HTTP requests.</td>
781-
</tr>
782-
</tbody>
783-
</table>
784746
</div>
785747
</div>
786748
</div>

0 commit comments

Comments
 (0)