Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/chrome-plugin/src/ProtocolClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Dialect, LintConfig, LintOptions } from 'harper.js';
import type { UnpackedLintGroups } from 'lint-framework';
import { LRUCache } from 'lru-cache';
import type { ActivationKey, Hotkey } from './protocol';
import type { ActivationKey, Hotkey, SpellCheckingMode } from './protocol';

export default class ProtocolClient {
private static readonly lintCache = new LRUCache<string, Promise<UnpackedLintGroups>>({
Expand Down Expand Up @@ -90,6 +90,10 @@ export default class ProtocolClient {
return (await chrome.runtime.sendMessage({ kind: 'getActivationKey' })).key;
}

public static async getSpellCheckingMode(): Promise<SpellCheckingMode> {
return (await chrome.runtime.sendMessage({ kind: 'getSpellCheckingMode' })).spellCheckingMode;
}

public static async getHotkey(): Promise<Hotkey> {
return (await chrome.runtime.sendMessage({ kind: 'getHotkey' })).hotkey;
}
Expand All @@ -107,6 +111,10 @@ export default class ProtocolClient {
await chrome.runtime.sendMessage({ kind: 'setActivationKey', key });
}

public static async setSpellCheckingMode(spellCheckingMode: SpellCheckingMode): Promise<void> {
await chrome.runtime.sendMessage({ kind: 'setSpellCheckingMode', spellCheckingMode });
}

public static async addToUserDictionary(words: string[]): Promise<void> {
this.lintCache.clear();
await chrome.runtime.sendMessage({ kind: 'addToUserDictionary', words });
Expand Down
31 changes: 31 additions & 0 deletions packages/chrome-plugin/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
type GetLintDescriptionsResponse,
type GetReviewedRequest,
type GetReviewedResponse,
type GetSpellCheckingModeRequest,
type GetSpellCheckingModeResponse,
type GetUserDictionaryResponse,
type Hotkey,
type IgnoreLintRequest,
Expand All @@ -38,7 +40,9 @@ import {
type SetDomainStatusRequest,
type SetHotkeyRequest,
type SetReviewedRequest,
type SetSpellCheckingModeRequest,
type SetUserDictionaryRequest,
SpellCheckingMode,
type UnitResponse,
} from '../protocol';
import { detectBrowserDialect } from './detectDialect';
Expand Down Expand Up @@ -158,6 +162,10 @@ function handleRequest(message: Request): Promise<Response> {
return handleGetUserDictionary();
case 'setUserDictionary':
return handleSetUserDictionary(message);
case 'getSpellCheckingMode':
return handleGetSpellCheckingMode();
case 'setSpellCheckingMode':
return handleSetSpellCheckingMode(message);
case 'getActivationKey':
return handleGetActivationKey();
case 'setActivationKey':
Expand Down Expand Up @@ -306,6 +314,20 @@ async function handleSetActivationKey(req: SetActivationKeyRequest): Promise<Uni
return createUnitResponse();
}

async function handleGetSpellCheckingMode(): Promise<GetSpellCheckingModeResponse> {
const spellCheckingMode = await getSpellCheckingMode();

return { kind: 'getSpellCheckingMode', spellCheckingMode };
}

async function handleSetSpellCheckingMode(req: SetSpellCheckingModeRequest): Promise<UnitResponse> {
if (!Object.values(SpellCheckingMode).includes(req.spellCheckingMode)) {
throw new Error(`Invalid spell checking mode: ${req.spellCheckingMode}`);
}
await setSpellCheckingMode(req.spellCheckingMode);

return createUnitResponse();
}
async function handleGetHotkey(): Promise<GetHotkeyResponse> {
const hotkey = await getHotkey();

Expand Down Expand Up @@ -431,6 +453,15 @@ async function setActivationKey(key: ActivationKey) {
await chrome.storage.local.set({ activationKey: key });
}

async function getSpellCheckingMode(): Promise<SpellCheckingMode> {
const resp = await chrome.storage.local.get({ spellCheckingMode: SpellCheckingMode.Default });
return resp.spellCheckingMode;
}

async function setSpellCheckingMode(spellCheckingMode: SpellCheckingMode) {
await chrome.storage.local.set({ spellCheckingMode: spellCheckingMode });
}

async function setHotkey(hotkey: Hotkey) {
await chrome.storage.local.set({ hotkey: hotkey });
}
Expand Down
25 changes: 24 additions & 1 deletion packages/chrome-plugin/src/options/Options.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Dialect, type LintConfig } from 'harper.js';
import logo from '/logo.png';
import ProtocolClient from '../ProtocolClient';
import type { Hotkey, Modifier } from '../protocol';
import { ActivationKey } from '../protocol';
import { ActivationKey, SpellCheckingMode } from '../protocol';

let lintConfig: LintConfig = $state({});
let lintDescriptions: Record<string, string> = $state({});
Expand All @@ -14,6 +14,7 @@ let dialect = $state(Dialect.American);
let defaultEnabled = $state(false);
let activationKey: ActivationKey = $state(ActivationKey.Off);
let userDict = $state('');
let spellCheckingMode: SpellCheckingMode = $state(SpellCheckingMode.Default);
let modifyHotkeyButton: Button;
let hotkey: Hotkey = $state({ modifiers: ['Ctrl'], key: 'e' });
let anyRulesEnabled = $derived(Object.values(lintConfig ?? {}).some((value) => value !== false));
Expand All @@ -30,6 +31,10 @@ $effect(() => {
ProtocolClient.setDefaultEnabled(defaultEnabled);
});

$effect(() => {
ProtocolClient.setSpellCheckingMode(spellCheckingMode);
});

$effect(() => {
ProtocolClient.setActivationKey(activationKey);
});
Expand Down Expand Up @@ -58,6 +63,9 @@ ProtocolClient.getActivationKey().then((d) => {
activationKey = d;
});

ProtocolClient.getSpellCheckingMode().then((d) => {
spellCheckingMode = d;
});
ProtocolClient.getHotkey().then((d) => {
// Ensure we have a plain object, not a Proxy
hotkey = {
Expand Down Expand Up @@ -256,6 +264,21 @@ function startHotkeyCapture(_modifyHotkeyButton: Button) {
</div>
</div>


<div class="space-y-5">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<span class="text-sm">Spell Checking Mode</span>
<span class="text-xs text-gray-600 dark:text-gray-400">Controls the timing when spell checking is applied.</span>
</div>
<Select size="sm" color="primary" class="w-44" bind:value={spellCheckingMode}>
<option value={SpellCheckingMode.Default}>Instant (Default)</option>
<option value={SpellCheckingMode.Space}>After Spacebar Press</option>
<option value={SpellCheckingMode.Stop}>After Typing Stops</option>
</Select>
</div>
</div>

<div class="space-y-5">
<div class="flex items-center justify-between">
<div class="flex flex-col">
Expand Down
23 changes: 23 additions & 0 deletions packages/chrome-plugin/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type Request =
| GetUserDictionaryRequest
| GetActivationKeyRequest
| SetActivationKeyRequest
| GetSpellCheckingModeRequest
| SetSpellCheckingModeRequest
| GetHotkeyRequest
| SetHotkeyRequest
| OpenOptionsRequest
Expand All @@ -40,6 +42,7 @@ export type Response =
| GetUserDictionaryResponse
| GetHotkeyResponse
| GetActivationKeyResponse
| GetSpellCheckingModeResponse
| GetInstalledOnResponse
| GetReviewedResponse
| PostFormDataResponse;
Expand Down Expand Up @@ -220,6 +223,26 @@ export type SetActivationKeyRequest = {
key: ActivationKey;
};

export enum SpellCheckingMode {
Default = 'default',
Space = 'space',
Stop = 'stop',
}

export type GetSpellCheckingModeRequest = {
kind: 'getSpellCheckingMode';
};

export type GetSpellCheckingModeResponse = {
kind: 'getSpellCheckingMode';
spellCheckingMode: SpellCheckingMode;
};

export type SetSpellCheckingModeRequest = {
kind: 'setSpellCheckingMode';
spellCheckingMode: SpellCheckingMode;
};

export type OpenOptionsRequest = {
kind: 'openOptions';
};
Expand Down
43 changes: 41 additions & 2 deletions packages/lint-framework/src/lint/LintFramework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import type { UnpackedLint, UnpackedLintGroups } from './unpackLint';

type ActivationKey = 'off' | 'shift' | 'control';

type SpellCheckingMode = 'default' | 'space' | 'stop';

let renderTimer: ReturnType<typeof setTimeout> | null = null;
type Modifier = 'Ctrl' | 'Shift' | 'Alt';

type Hotkey = {
Expand Down Expand Up @@ -46,6 +49,7 @@ export default class LintFramework {
private actions: {
ignoreLint?: (hash: string) => Promise<void>;
getActivationKey?: () => Promise<ActivationKey>;
getSpellCheckingMode?: () => Promise<SpellCheckingMode>;
getHotkey?: () => Promise<Hotkey>;
openOptions?: () => Promise<void>;
addToUserDictionary?: (words: string[]) => Promise<void>;
Expand All @@ -62,6 +66,7 @@ export default class LintFramework {
actions: {
ignoreLint?: (hash: string) => Promise<void>;
getActivationKey?: () => Promise<ActivationKey>;
getSpellCheckingMode?: () => Promise<SpellCheckingMode>;
getHotkey?: () => Promise<Hotkey>;
openOptions?: () => Promise<void>;
addToUserDictionary?: (words: string[]) => Promise<void>;
Expand Down Expand Up @@ -112,8 +117,8 @@ export default class LintFramework {
}

async update() {
this.determineSpellCheckingMode();
this.requestRender();
this.requestLintUpdate();
}

async requestLintUpdate() {
Expand Down Expand Up @@ -149,7 +154,6 @@ export default class LintFramework {

this.lastLints = lintResults.filter((r) => r.target != null) as any;
this.lintRequested = false;
this.requestRender();
}

/**
Expand Down Expand Up @@ -185,6 +189,8 @@ export default class LintFramework {
const suggestions = previousBox.lint.suggestions;
if (suggestions.length > 0) {
previousBox.applySuggestion(suggestions[0]);
this.requestLintUpdate();
this.requestRender();
} else {
previousBox.ignoreLint?.();
}
Expand Down Expand Up @@ -256,6 +262,39 @@ export default class LintFramework {
for (const event of PAGE_EVENTS) {
window.addEventListener(event, this.updateEventCallback);
}
window.addEventListener('suggestionApplied', () => {
this.requestLintUpdate();
this.requestRender();
});
}

private async determineSpellCheckingMode() {
const spellCheckingMode = await this.actions.getSpellCheckingMode?.();
switch (spellCheckingMode) {
case 'space':
window.addEventListener('keyup', (event: KeyboardEvent) => {
const key = event.code;
const expectedKey = 'Space';
if (key === expectedKey) {
this.requestLintUpdate();
}
});
break;
case 'stop':
window.addEventListener(
'input',
() => {
if (renderTimer) clearTimeout(renderTimer);
renderTimer = setTimeout(() => {
this.requestLintUpdate();
}, 2000);
},
{ once: true },
);
break;
case 'default':
this.requestLintUpdate();
}
}

private requestRender() {
Expand Down
2 changes: 2 additions & 0 deletions packages/lint-framework/src/lint/computeLintBoxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ export default function computeLintBoxes(
const current = isFormEl(el)
? (el as HTMLInputElement | HTMLTextAreaElement).value
: (el.textContent ?? '');
const suggestionAppliedEvent = new CustomEvent('suggestionApplied');
const newValue = applySuggestion(current, lint.span, sug);
replaceValue(el, newValue, lint.span, sug.replacement_text);
window.dispatchEvent(suggestionAppliedEvent);
},
ignoreLint: opts.ignoreLint ? () => opts.ignoreLint!(lint.context_hash) : undefined,
});
Expand Down
Loading