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
11 changes: 10 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 } from 'harper.js';
import type { UnpackedLintGroups } from 'lint-framework';
import { LRUCache } from 'lru-cache';
import type { ActivationKey } from './protocol';
import type { ActivationKey, SpellCheckingMode } from './protocol';

export default class ProtocolClient {
private static readonly lintCache = new LRUCache<string, Promise<UnpackedLintGroups>>({
Expand Down Expand Up @@ -80,10 +80,19 @@ 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 setActivationKey(key: ActivationKey): Promise<void> {
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
30 changes: 30 additions & 0 deletions packages/chrome-plugin/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import {
type GetEnabledDomainsResponse,
type GetLintDescriptionsRequest,
type GetLintDescriptionsResponse,
type GetSpellCheckingModeResponse,
type GetSpellCheckingModeRequest,
type GetUserDictionaryResponse,
type IgnoreLintRequest,
type LintRequest,
type LintResponse,
SpellCheckingMode,
type OpenReportErrorRequest,
type PostFormDataRequest,
type PostFormDataResponse,
Expand All @@ -30,6 +33,7 @@ import {
type SetDefaultStatusRequest,
type SetDialectRequest,
type SetDomainStatusRequest,
type SetSpellCheckingModeRequest,
type SetUserDictionaryRequest,
type UnitResponse,
} from '../protocol';
Expand Down Expand Up @@ -140,6 +144,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 @@ -278,6 +286,18 @@ 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);
}
async function handleOpenReportError(req: OpenReportErrorRequest): Promise<UnitResponse> {
const popupState: PopupState = {
page: 'report-error',
Expand Down Expand Up @@ -364,6 +384,16 @@ 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 });
}


function initializeLinter(dialect: Dialect) {
linter = new LocalLinter({
binary: new BinaryModule(chrome.runtime.getURL('./wasm/harper_wasm_bg.wasm')),
Expand Down
24 changes: 23 additions & 1 deletion packages/chrome-plugin/src/options/Options.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Button, Input, Select } from 'flowbite-svelte';
import { Dialect, type LintConfig } from 'harper.js';
import logo from '/logo.png';
import ProtocolClient from '../ProtocolClient';
import { ActivationKey } from '../protocol';
import { ActivationKey, SpellCheckingMode } from '../protocol';

let lintConfig: LintConfig = $state({});
let lintDescriptions: Record<string, string> = $state({});
Expand All @@ -13,6 +13,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);

$effect(() => {
ProtocolClient.setLintConfig($state.snapshot(lintConfig));
Expand All @@ -26,6 +27,10 @@ $effect(() => {
ProtocolClient.setDefaultEnabled(defaultEnabled);
});

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

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

ProtocolClient.getSpellCheckingMode().then((d) => {
spellCheckingMode = d;
});

ProtocolClient.getUserDictionary().then((d) => {
userDict = dictToString(d.toSorted());
});
Expand Down Expand Up @@ -164,6 +173,19 @@ async function exportEnabledDomainsCSV() {
</div>


<div class="space-y-5">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<span class="font-medium">Spell Checking Mode</span>
<span class="font-light">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">
Expand Down
29 changes: 27 additions & 2 deletions packages/chrome-plugin/src/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Dialect, LintConfig, Summary } from 'harper.js';
import type { Dialect, LintConfig } from 'harper.js';
import type { UnpackedLintGroups } from 'lint-framework';

export type Request =
Expand All @@ -19,6 +19,8 @@ export type Request =
| GetUserDictionaryRequest
| GetActivationKeyRequest
| SetActivationKeyRequest
| GetSpellCheckingModeRequest
| SetSpellCheckingModeRequest
| OpenOptionsRequest
| OpenReportErrorRequest
| PostFormDataRequest;
Expand All @@ -34,7 +36,9 @@ export type Response =
| GetEnabledDomainsResponse
| GetUserDictionaryResponse
| GetActivationKeyResponse
| PostFormDataResponse;
| PostFormDataResponse
| GetSpellCheckingModeResponse;


export type LintRequest = {
kind: 'lint';
Expand Down Expand Up @@ -184,6 +188,27 @@ 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
3 changes: 3 additions & 0 deletions packages/chrome-plugin/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"paths": {
"lint-framework": ["../lint-framework/src/index.ts"]
},
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
Expand Down
1 change: 1 addition & 0 deletions packages/lint-framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"harper.js": "workspace:*"
},
"devDependencies": {
"@types/chrome": "0.1.24",
"@types/virtual-dom": "^2.1.4",
"type-fest": "^4.37.0",
"typescript": "catalog:",
Expand Down
37 changes: 35 additions & 2 deletions packages/lint-framework/src/lint/LintFramework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import { isVisible } from './domUtils';
import Highlights from './Highlights';
import PopupHandler from './PopupHandler';
import type { UnpackedLint, UnpackedLintGroups } from './unpackLint';
import ProtocolClient from '../../../chrome-plugin/src/ProtocolClient';

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

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

let renderTimer: ReturnType<typeof setTimeout> | null = null;

/** Events on an input (any kind) that can trigger a re-render. */
const INPUT_EVENTS = ['focus', 'keyup', 'paste', 'change', 'scroll'];
/** Events on the window that can trigger a re-render. */
Expand All @@ -30,6 +35,7 @@ export default class LintFramework {
private actions: {
ignoreLint?: (hash: string) => Promise<void>;
getActivationKey?: () => Promise<ActivationKey>;
getSpellCheckingMode?: () => Promise<SpellCheckingMode>;
openOptions?: () => Promise<void>;
addToUserDictionary?: (words: string[]) => Promise<void>;
reportError?: (lint: UnpackedLint) => Promise<void>;
Expand All @@ -40,6 +46,7 @@ export default class LintFramework {
actions: {
ignoreLint?: (hash: string) => Promise<void>;
getActivationKey?: () => Promise<ActivationKey>;
getSpellCheckingMode?: () => Promise<SpellCheckingMode>;
openOptions?: () => Promise<void>;
addToUserDictionary?: (words: string[]) => Promise<void>;
reportError?: () => Promise<void>;
Expand Down Expand Up @@ -87,8 +94,8 @@ export default class LintFramework {
}

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

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

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

public async addTarget(target: Node) {
Expand Down Expand Up @@ -182,6 +188,33 @@ export default class LintFramework {
}
}

private async determineSpellCheckingMode() {
let spellCheckingMode: SpellCheckingMode = await ProtocolClient.getSpellCheckingMode();
switch(spellCheckingMode) {
case 'space':
window.addEventListener('keyup', (event: KeyboardEvent) => {
let key = event.code;
let 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() {
if (this.renderRequested) {
return;
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading