Swadhin Biswas

[email protected]
2026-03-28 09:21:57 +0600 committed NaN years ago
91cb77b
commit 91cb77b
11 files changed
+400 -18
β–Όopengrammar/CONTRIBUTING.md
+124-0
1
+# Contributing to OpenGrammar
2
+
3
+Thank you for your interest in contributing! Whether you're adding a new grammar rule, refining the AI context, or building out a new front-end UI feature, we are incredibly excited to welcome you.
4
+
5
+This document outlines everything you need to know about the engine structure.
6
+
7
+---
8
+
9
+## πŸ› οΈ Local Development
10
+
11
+### Prerequisites
12
+
13
+- [Bun](https://bun.sh/) (>= 1.3)
14
+- Node.js (>= 20) for the extension front-end
15
+
16
+### Starting the Environment
17
+
18
+The project is split into the `/backend` (Hono API) and `/extension` (Google Chrome frontend).
19
+
20
+1. **Spin up the Backend:**
21
+ ```bash
22
+ cd backend
23
+ bun install
24
+ bun dev
25
+ ```
26
+
27
+2. **Spin up the Extension:**
28
+ ```bash
29
+ cd extension
30
+ npm install
31
+ npm run build # (Vite output goes to /extension/dist)
32
+ npm run dev # (To auto-recompile UI edits)
33
+ ```
34
+
35
+### Running Tests
36
+
37
+We have strict performance and validation checks. Before pushing anywhere, run:
38
+
39
+```bash
40
+cd backend
41
+bun test
42
+bun run bench.ts
43
+```
44
+
45
+The 814+ rule engine should comfortably process 10,000 words in under a second (`1000ms`).
46
+
47
+---
48
+
49
+## πŸ“– The Rule Engine Architecture
50
+
51
+OpenGrammar is fundamentally an offline-first system powered by the huge `CORE_RULES` library in `backend/src/rules/index.ts`.
52
+
53
+It runs `RegexRules` and `NLPRules` to check every single chunk of a sentence. Before adding a new rule, search through the modules (`adjectives-adverbs.ts`, `style-tone.ts`, etc.) to see if your logical check fits an existing category.
54
+
55
+### How to Add a Regex Rule
56
+Regex rules are incredibly fast but lack contextual awareness (they don't know the difference between "a lead pipe" and "to lead a horse"). Write these when the error is deterministic.
57
+
58
+**Example `backend/src/rules/categories/punctuation.ts`:**
59
+```typescript
60
+{
61
+ id: 'PU_double_spaces',
62
+ type: 'regex',
63
+ pattern: '\\b[a-zA-Z]+\\s{2,}[a-zA-Z]+\\b',
64
+ check: (text: string) => {
65
+ return createRegexRule(
66
+ 'PU_double_spaces',
67
+ /(\S)(\s{2,})(\S)/g,
68
+ 'grammar',
69
+ '', // replacement dynamically handled below:
70
+ 'Avoid double spaces between words.',
71
+ text
72
+ ).map(issue => ({
73
+ ...issue,
74
+ suggestion: issue.original.replace(/\s{2,}/g, ' '),
75
+ priority: 85
76
+ }));
77
+ }
78
+}
79
+```
80
+
81
+### How to Add an NLP Rule
82
+Using the `compromise` NLP library, we tag parts-of-speech (Verbs, Nouns, Adjectives). Use NLP rules when the phrasing is ambiguous and relies on grammar syntax.
83
+
84
+**Example `backend/src/rules/categories/verb-tense.ts`:**
85
+```typescript
86
+{
87
+ id: 'VT_stative_progressives',
88
+ type: 'nlp',
89
+ check: (text: string, doc: any) => {
90
+ const issues: Issue[] = [];
91
+ // Only target Present Participle Gerunds of stative verbs
92
+ const matches = doc.match('(is|am|are|was|were) (knowing|belonging|desiring|preferring|liking|loving|hating)');
93
+
94
+ matches.forEach((m: any) => {
95
+ const original = m.text();
96
+ // Heuristically map "is knowing" -> "knows"
97
+ issues.push({
98
+ id: 'VT_stative_progressives',
99
+ type: 'grammar',
100
+ original,
101
+ suggestion: fixStativeVerb(original),
102
+ reason: 'Stative verbs like this generally do not take the progressive (-ing) tense.',
103
+ offset: m.out('offset').start,
104
+ length: original.length,
105
+ priority: 90
106
+ });
107
+ });
108
+ return issues;
109
+ }
110
+}
111
+```
112
+
113
+---
114
+
115
+## πŸ“ Pull Request Guidelines
116
+
117
+When submitting a PR:
118
+1. Include passing tests in `engine.test.ts`. Every single rule MUST have at least one valid and invalid test assertion.
119
+2. If introducing a `style` rule or `business-writing` check, make sure its `id` follows the appropriate `MODULE_PREFIX` mapping inside `context-filter.ts`. Otherwise, the context engine won't know when to disable it in casual chat apps!
120
+3. Prefix your commits with `feat:`, `fix:`, or `chore:` following conventional commits.
121
+
122
+We use **Biome** on GitHub Actions to auto-lint against the core codebase formatting (2 spaces, single quotes). You don't need to format it perfectly yourself, just ensure the build isn't spectacularly broken.
123
+
124
+Welcome to OpenGrammar! πŸŽ‰
0
125
β–Όopengrammar/README.md
+92-0
1
+<div align="center">
2
+ <img src="https://raw.githubusercontent.com/lucide-icons/lucide/main/icons/feather.svg" width="80" height="80" alt="OpenGrammar Logo" />
3
+
4
+ <h1>OpenGrammar</h1>
5
+
6
+ <p><strong>A blazingly fast, privacy-first, open-source grammar and writing engine.</strong></p>
7
+
8
+ <p>
9
+ <a href="https://github.com/swadhinbiswas/opengrammar/actions"><img src="https://github.com/swadhinbiswas/opengrammar/actions/workflows/ci.yml/badge.svg" alt="CI Status"></a>
10
+ <a href="https://codefactor.io/repository/github/swadhinbiswas/opengrammar"><img src="https://www.codefactor.io/repository/github/swadhinbiswas/opengrammar/badge" alt="CodeFactor" /></a>
11
+ <a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License"></a>
12
+ <img src="https://img.shields.io/badge/Rules-814+-success" alt="Rules Active">
13
+ </p>
14
+</div>
15
+
16
+---
17
+
18
+**OpenGrammar** is an advanced grammar, style, and spell-checking engine built to be completely self-hosted. Unlike commercial exact-match grammar tools that vacuum up your keystrokes, OpenGrammar processes text either locally using deterministic Regex/NLP rules, or through an LLM provider of your choice (OpenAI, Local Ollama, Groq, OpenRouter).
19
+
20
+## ✨ Features
21
+
22
+- ⚑ **Blazingly Fast Engine**: Core engine analyzes 10,000 words in **< 650ms**.
23
+- 🧠 **Hybrid Architecture**: Combines ultra-fast Regex pattern matching with `compromise` NLP for intelligent syntax analysis.
24
+- πŸ“š **Massive Rule Library**: Ships with **814+ built-in rules** spanning:
25
+ - Subject-Verb Agreement, Irregular Verbs, Subjunctives
26
+ - Business Writing, Academic Tone, Inclusive Language
27
+ - Clarity, Redundancy, Readability, and ClichΓ©s
28
+- 🎯 **Context-Aware Filtering**: Automatically detects where you are writing (Slack = Chat, GitHub = Technical, Gmail = Email) and dynamically turns off nagging style rules when you're just typing casually.
29
+- πŸŽ›οΈ **Granular Toggles**: Fully configurable rules via the Chrome Extension UI.
30
+- ☁️ **Bring Your Own LLM**: Optionally hook up OpenAI, Groq, or a completely offline local **Ollama** model for deep contextual analysis and AI sentence rewriting.
31
+
32
+## πŸš€ Getting Started
33
+
34
+OpenGrammar is split into two parts: an **API Backend** (built with Hono + Bun) and a **Chrome Extension** (built with React + Vite).
35
+
36
+### 1. Start the Backend
37
+
38
+The engine runs on a lightweight [Hono](https://hono.dev/) server using [Bun](https://bun.sh/).
39
+
40
+```bash
41
+cd backend
42
+bun install
43
+bun run dev
44
+```
45
+*Server starts on `http://localhost:8787`*
46
+
47
+### 2. Load the Chrome Extension
48
+
49
+```bash
50
+cd extension
51
+npm install
52
+npm run build
53
+```
54
+
55
+Then load it into your browser:
56
+1. Open Chrome and navigate to `chrome://extensions/`
57
+2. Enable **Developer mode** (top right toggle).
58
+3. Click **Load unpacked** and select the `/extension/dist` folder.
59
+
60
+---
61
+
62
+## ☁️ Deployment
63
+
64
+The backend is built on standard Web APIs and runs perfectly on edge networks.
65
+
66
+### Deploy to Cloudflare Workers (Recommended)
67
+```bash
68
+cd backend
69
+npm run deploy
70
+```
71
+*This uses `wrangler` to instantly deploy your engine globally via Cloudflare.*
72
+
73
+---
74
+
75
+## πŸ› οΈ Performance Architecture
76
+
77
+OpenGrammar prioritizes offline, instant feedback without the latency of an AI roundtrip:
78
+1. **Spellchecker Segment**: Loads a custom dictionary tree (120k+ words) for instant typo detection.
79
+2. **NLP Tagger Segment**: `compromise` tags parts of speech (Nouns, Verbs, Quotations) to prevent false positives.
80
+3. **Regex Engine**: Matches 800+ deterministic error patterns in a single pass.
81
+4. **Deduplicator**: Ranks overlapping issues (Grammar > Spelling > Clarity > Style) and drops lower-priority items.
82
+
83
+## 🀝 Contributing
84
+
85
+We welcome contributions of all kinds! The heartbeat of this project is its `CORE_RULES` rule registry.
86
+Adding a new grammar or style rule is as easy as writing a regex expression in TypeScript.
87
+
88
+Please read our [Contributing Guide](CONTRIBUTING.md) to learn how the engine handles rules, how to write tests, and how to submit a PR!
89
+
90
+## πŸ“œ License
91
+
92
+Apache 2.0 License - see the [LICENSE](LICENSE) file for details.
0
93
β–Όopengrammar/backend/src/analyzer.ts
+9-6
35
35
private static dictionary: Set<string> = new Set();
36
36
private static customRules: CustomRule[] = [];
37
37
38
- static analyze(text: string, options?: { dictionary?: string[]; customRules?: CustomRule[]; writingContext?: WritingContext }): Issue[] {
38
+ static analyze(text: string, options?: { dictionary?: string[]; customRules?: CustomRule[]; writingContext?: WritingContext; disabledModules?: string[] }): Issue[] {
39
39
const issues: Issue[] = [];
40
40
41
41
if (options?.dictionary) {
46
46
this.customRules = options.customRules;
47
47
}
48
48
49
- // Dictionary-based spell checking (real spell checker)
50
- issues.push(...checkSpelling(text, this.dictionary));
49
+ // Dictionary-based spell checking (unless spelling module is disabled)
50
+ const isSpellingDisabled = options?.disabledModules?.some(m => m.toLowerCase() === 'spelling');
51
+ if (!isSpellingDisabled) {
52
+ issues.push(...checkSpelling(text, this.dictionary));
53
+ }
51
54
52
55
// Initialize NLP Engine for Syntax Checks
53
56
let doc: any = null;
57
60
console.warn('NLP Engine parsing disabled or failed:', e);
58
61
}
59
62
60
- // Run Modular CORE RULES (filtered by writing context)
61
- const activeRules = options?.writingContext
62
- ? filterRulesByContext(CORE_RULES, options.writingContext)
63
+ // Run Modular CORE RULES (filtered by writing context and manual overrides)
64
+ const activeRules = options?.writingContext || options?.disabledModules
65
+ ? filterRulesByContext(CORE_RULES, options.writingContext || 'general', options.disabledModules)
63
66
: CORE_RULES;
64
67
65
68
for (const rule of activeRules) {
66
69
β–Όopengrammar/backend/src/index.ts
+2-2
88
88
89
89
try {
90
90
const body: AnalyzeRequest = await c.req.json();
91
- const { text, apiKey, model, provider, baseUrl, context } = body;
91
+ const { text, apiKey, model, provider, baseUrl, context, disabledModules } = body;
92
92
93
93
// Validate input
94
94
if (!text || typeof text !== 'string') {
103
103
const writingContext = detectWritingContext(context?.domain);
104
104
105
105
// Run rule-based analysis with context-aware filtering
106
- const ruleIssues = RuleBasedAnalyzer.analyze(text, { writingContext });
106
+ const ruleIssues = RuleBasedAnalyzer.analyze(text, { writingContext, disabledModules });
107
107
let issues = enrichIssues(ruleIssues, 'rule', text, context);
108
108
109
109
// Run LLM analysis if API key provided or using local Ollama
110
110
β–Όopengrammar/backend/src/rules/context-filter.ts
+39-8
132
132
}
133
133
134
134
/**
135
- * Filter rules based on writing context.
136
- * Returns only the rules that are relevant for the given context.
135
+ * Filter rules based on writing context and user-defined disabled modules.
136
+ * Returns only the rules that are relevant for the given context and not manually disabled.
137
137
*/
138
-export function filterRulesByContext(rules: Rule[], context: WritingContext): Rule[] {
139
- if (context === 'general' || context === 'document') {
140
- return rules; // No filtering β€” all rules active
138
+export function filterRulesByContext(rules: Rule[], context: WritingContext, disabledModules?: string[]): Rule[] {
139
+ // 1. First, apply manual user overrides (stripping out entire categories if disabled)
140
+ // We map the UI categories (Grammar, Spelling, Punctuation, Style, Clarity)
141
+ // to the internal MODULE_PREFIXES groups.
142
+ const disabledPrefixGroups = new Set<string>();
143
+
144
+ if (disabledModules && disabledModules.length > 0) {
145
+ const dLower = disabledModules.map(d => d.toLowerCase());
146
+ if (dLower.includes('grammar')) {
147
+ ['grammar', 'grammar-advanced', 'sentence-structure', 'verb-tense', 'nouns-pronouns', 'adjectives-adverbs', 'prepositions', 'conjunctions'].forEach(m => disabledPrefixGroups.add(m));
148
+ }
149
+ if (dLower.includes('spelling')) {
150
+ ['spelling', 'spelling-advanced', 'confused-words'].forEach(m => disabledPrefixGroups.add(m));
151
+ }
152
+ if (dLower.includes('punctuation')) {
153
+ ['punctuation', 'capitalization'].forEach(m => disabledPrefixGroups.add(m));
154
+ }
155
+ if (dLower.includes('style')) {
156
+ ['style', 'style-tone', 'formality', 'academic-writing', 'business-writing', 'inclusive-language', 'formatting-idioms'].forEach(m => disabledPrefixGroups.add(m));
157
+ }
158
+ if (dLower.includes('clarity')) {
159
+ ['clarity', 'readability'].forEach(m => disabledPrefixGroups.add(m));
160
+ }
141
161
}
142
162
143
- const enabledModules = CONTEXT_MODULES[context];
144
- if (!enabledModules) return rules;
163
+ // 2. Map standard context
164
+ const enabledContextModules = CONTEXT_MODULES[context];
145
165
146
166
return rules.filter(rule => {
147
167
const module = getRuleModule(rule.id);
148
- return module === 'general' || enabledModules.has(module);
168
+
169
+ // Manual override check: if the user disabled this entire module group, strip it out.
170
+ if (disabledPrefixGroups.has(module)) {
171
+ return false;
172
+ }
173
+
174
+ // Context filter check
175
+ if (context === 'general' || context === 'document') {
176
+ return true; // No context filtering
177
+ }
178
+
179
+ return module === 'general' || (enabledContextModules && enabledContextModules.has(module));
149
180
});
150
181
}
151
182
152
183
β–Όopengrammar/backend/src/shared-types.ts
+1-0
31
31
customRules?: CustomRule[];
32
32
dictionary?: string[];
33
33
context?: AnalysisContext;
34
+ disabledModules?: string[];
34
35
}
35
36
36
37
export interface CustomRule {
37
38
β–Όopengrammar/backend/tests/engine.test.ts
+32-0
627
627
expect(detectWritingContext(undefined)).toBe('general');
628
628
});
629
629
});
630
+
631
+// ══════════════════════════════════════════════════
632
+// Manual Rule Overrides (disabledModules)
633
+// ══════════════════════════════════════════════════
634
+describe('Manual Rule Filtering (disabledModules)', () => {
635
+ const testText = 'Gonna go btw. He buyed a car. The chairman spoke. I think that this is true.';
636
+
637
+ test('disabling style rules ignores "gonna" and "btw"', () => {
638
+ // Usually 'general' flags everything. If we disable 'style', only grammar and spelling run.
639
+ const issues = RuleBasedAnalyzer.analyze(testText, {
640
+ writingContext: 'general',
641
+ disabledModules: ['style'], // 'style' covers style, formality, etc.
642
+ });
643
+
644
+ // Should still catch 'buyed' (grammar)
645
+ expect(issues.some(i => i.original.toLowerCase() === 'buyed')).toBe(true);
646
+ // Should NOT catch 'gonna' (style/formality)
647
+ expect(issues.some(i => i.original.toLowerCase() === 'gonna')).toBe(false);
648
+ });
649
+
650
+ test('disabling grammar ignores grammar-specific "buyed"', () => {
651
+ const issues = RuleBasedAnalyzer.analyze(testText, {
652
+ writingContext: 'general',
653
+ disabledModules: ['grammar'],
654
+ });
655
+
656
+ // Should NOT catch 'buyed' as a grammar issue (though spellchecker still flags it)
657
+ expect(issues.some(i => i.original.toLowerCase() === 'buyed' && i.type === 'grammar')).toBe(false);
658
+ // Might still catch 'gonna' (style)
659
+ expect(issues.some(i => i.original.toLowerCase() === 'gonna')).toBe(true);
660
+ });
661
+});
630
662
β–Όopengrammar/extension/src/background/index.ts
+5-2
220
220
dictionary,
221
221
backendUrl,
222
222
provider,
223
- customBaseUrl
223
+ customBaseUrl,
224
+ disabledModules
224
225
} = await chrome.storage.sync.get([
225
226
'apiKey',
226
227
'model',
229
230
'dictionary',
230
231
'backendUrl',
231
232
'provider',
232
- 'customBaseUrl'
233
+ 'customBaseUrl',
234
+ 'disabledModules'
233
235
]);
234
236
235
237
if (enabled === false) {
251
253
ignoredIssues: ignoredIssues || [],
252
254
dictionary: dictionary || [],
253
255
context,
256
+ disabledModules: disabledModules || [],
254
257
};
255
258
256
259
const response = await fetch(`${baseUrl}/analyze`, {
257
260
β–Όopengrammar/extension/src/options/index.html
+63-0
71
71
</div>
72
72
</section>
73
73
74
+ <!-- Rule Configurations -->
75
+ <section class="settings-section">
76
+ <h2>Writing Preferences</h2>
77
+ <p class="section-description">
78
+ Customize which categories of errors OpenGrammar looks for. By default, everything is enabled.
79
+ </p>
80
+
81
+ <div class="setting-group">
82
+ <label class="toggle-setting">
83
+ <div class="toggle-info">
84
+ <span class="setting-label">Grammar</span>
85
+ <span class="setting-description">Subject-verb agreement, irregular verbs, pronouns</span>
86
+ </div>
87
+ <input type="checkbox" id="checkGrammar" class="toggle-input" checked>
88
+ <span class="toggle-slider"></span>
89
+ </label>
90
+ </div>
91
+
92
+ <div class="setting-group">
93
+ <label class="toggle-setting">
94
+ <div class="toggle-info">
95
+ <span class="setting-label">Spelling</span>
96
+ <span class="setting-description">Typos, misspelled words, confused words</span>
97
+ </div>
98
+ <input type="checkbox" id="checkSpelling" class="toggle-input" checked>
99
+ <span class="toggle-slider"></span>
100
+ </label>
101
+ </div>
102
+
103
+ <div class="setting-group">
104
+ <label class="toggle-setting">
105
+ <div class="toggle-info">
106
+ <span class="setting-label">Punctuation</span>
107
+ <span class="setting-description">Missing commas, incorrect hyphens</span>
108
+ </div>
109
+ <input type="checkbox" id="checkPunctuation" class="toggle-input" checked>
110
+ <span class="toggle-slider"></span>
111
+ </label>
112
+ </div>
113
+
114
+ <div class="setting-group">
115
+ <label class="toggle-setting">
116
+ <div class="toggle-info">
117
+ <span class="setting-label">Style & Tone</span>
118
+ <span class="setting-description">Formality, academic writing, business etiquette</span>
119
+ </div>
120
+ <input type="checkbox" id="checkStyle" class="toggle-input" checked>
121
+ <span class="toggle-slider"></span>
122
+ </label>
123
+ </div>
124
+
125
+ <div class="setting-group">
126
+ <label class="toggle-setting">
127
+ <div class="toggle-info">
128
+ <span class="setting-label">Clarity</span>
129
+ <span class="setting-description">Wordiness, redundant phrasing, readability</span>
130
+ </div>
131
+ <input type="checkbox" id="checkClarity" class="toggle-input" checked>
132
+ <span class="toggle-slider"></span>
133
+ </label>
134
+ </div>
135
+ </section>
136
+
74
137
<!-- API Settings -->
75
138
<section class="settings-section">
76
139
<h2>AI Settings</h2>
77
140
β–Όopengrammar/extension/src/options/options.tsx
+32-0
11
11
disabledDomains: string[];
12
12
dictionary: string[];
13
13
ignoredIssues: IgnoredIssue[];
14
+ disabledModules: string[];
14
15
}
15
16
16
17
// DOM Elements
19
20
checkAsYouType: document.getElementById('checkAsYouType') as HTMLInputElement,
20
21
showNotifications: document.getElementById('showNotifications') as HTMLInputElement,
21
22
autocompleteEnabled: document.getElementById('autocompleteEnabled') as HTMLInputElement,
23
+ checkGrammar: document.getElementById('checkGrammar') as HTMLInputElement,
24
+ checkSpelling: document.getElementById('checkSpelling') as HTMLInputElement,
25
+ checkPunctuation: document.getElementById('checkPunctuation') as HTMLInputElement,
26
+ checkStyle: document.getElementById('checkStyle') as HTMLInputElement,
27
+ checkClarity: document.getElementById('checkClarity') as HTMLInputElement,
22
28
apiKey: document.getElementById('apiKey') as HTMLInputElement,
23
29
toggleApiKey: document.getElementById('toggleApiKey') as HTMLButtonElement,
24
30
model: document.getElementById('model') as HTMLSelectElement,
58
64
disabledDomains: [],
59
65
dictionary: [],
60
66
ignoredIssues: [],
67
+ disabledModules: [],
61
68
};
62
69
63
70
let analyticsSummary: AnalyticsSummary | null = null;
96
103
disabledDomains: result.disabledDomains || [],
97
104
dictionary: result.dictionary || [],
98
105
ignoredIssues: normalizeIgnoredIssues(result.ignoredIssues),
106
+ disabledModules: result.disabledModules || [],
99
107
};
100
108
101
109
// Update UI
103
111
elements.checkAsYouType.checked = settings.checkAsYouType;
104
112
elements.showNotifications.checked = settings.showNotifications;
105
113
elements.autocompleteEnabled.checked = settings.autocompleteEnabled;
114
+ elements.checkGrammar.checked = !settings.disabledModules.includes('grammar');
115
+ elements.checkSpelling.checked = !settings.disabledModules.includes('spelling');
116
+ elements.checkPunctuation.checked = !settings.disabledModules.includes('punctuation');
117
+ elements.checkStyle.checked = !settings.disabledModules.includes('style');
118
+ elements.checkClarity.checked = !settings.disabledModules.includes('clarity');
106
119
elements.apiKey.value = settings.apiKey;
107
120
elements.model.value = settings.model;
108
121
elements.backendUrl.value = settings.backendUrl;
164
177
disabledDomains: settings.disabledDomains,
165
178
dictionary: settings.dictionary,
166
179
ignoredIssues: settings.ignoredIssues,
180
+ disabledModules: settings.disabledModules,
167
181
},
168
182
() => {
169
183
console.log('Settings saved');
198
212
saveSettings();
199
213
});
200
214
215
+ // Rule Category Toggles
216
+ const updateDisabledModules = () => {
217
+ const disabled: string[] = [];
218
+ if (!elements.checkGrammar.checked) disabled.push('grammar');
219
+ if (!elements.checkSpelling.checked) disabled.push('spelling');
220
+ if (!elements.checkPunctuation.checked) disabled.push('punctuation');
221
+ if (!elements.checkStyle.checked) disabled.push('style');
222
+ if (!elements.checkClarity.checked) disabled.push('clarity');
223
+ settings.disabledModules = disabled;
224
+ saveSettings();
225
+ };
226
+
227
+ elements.checkGrammar.addEventListener('change', updateDisabledModules);
228
+ elements.checkSpelling.addEventListener('change', updateDisabledModules);
229
+ elements.checkPunctuation.addEventListener('change', updateDisabledModules);
230
+ elements.checkStyle.addEventListener('change', updateDisabledModules);
231
+ elements.checkClarity.addEventListener('change', updateDisabledModules);
232
+
201
233
// API Key
202
234
elements.apiKey.addEventListener('input', () => {
203
235
settings.apiKey = elements.apiKey.value;
204
236
β–Όopengrammar/extension/src/types.ts
+1-0
46
46
ignoredIssues?: string[];
47
47
dictionary?: string[];
48
48
context?: AnalysisContext;
49
+ disabledModules?: string[];
49
50
}
50
51
51
52
export interface AnalysisContext {
52
53