Skip to content
This repository has been archived by the owner on Feb 27, 2020. It is now read-only.

Commit

Permalink
Merge pull request #389 from kwonoj/feat-async
Browse files Browse the repository at this point in the history
feat(spellcheckerprovider): async spellchecker support
  • Loading branch information
kwonoj committed May 20, 2019
2 parents 863b619 + 631f539 commit c9e0658
Show file tree
Hide file tree
Showing 23 changed files with 2,446 additions and 2,357 deletions.
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
<a name="1.0.0-beta.7"></a>
# [1.0.0-beta.7](https://github.com/kwonoj/electron-hunspell/compare/v1.0.0-beta.5...v1.0.0-beta.7) (2019-05-20)


### Bug Fixes

* **provider:** allow tree shake ([a567ae9](https://github.com/kwonoj/electron-hunspell/commit/a567ae9))


### Code Refactoring

* **loaddictionary:** only accept arraybuffer ([3041e2b](https://github.com/kwonoj/electron-hunspell/commit/3041e2b))
* **unloaddictoinary:** do not reset webframe provider ([5e6dcbf](https://github.com/kwonoj/electron-hunspell/commit/5e6dcbf))


### Features

* **attachspellcheckerprovider:** implement attachprovider ([eeea886](https://github.com/kwonoj/electron-hunspell/commit/eeea886))
* **providerproxy:** expose interfaces for attach ([88b3ed6](https://github.com/kwonoj/electron-hunspell/commit/88b3ed6))
* **providerproxy:** no longer explicitly requires languagekey ([198f7c6](https://github.com/kwonoj/electron-hunspell/commit/198f7c6))
* **spellcheckerprovider:** async interfaces ([6c3bf89](https://github.com/kwonoj/electron-hunspell/commit/6c3bf89))
* **spellcheckerprovider:** implement spell ([0b72398](https://github.com/kwonoj/electron-hunspell/commit/0b72398))
* **spellcheckerprovider:** switchdictionary do not autoattach ([b926902](https://github.com/kwonoj/electron-hunspell/commit/b926902))


### BREAKING CHANGES

* **spellcheckerprovider:** provider interface is now async
* **providerproxy:** switchDictionary deprecated
* **spellcheckerprovider:** spellchecker provider do not attach to
webframe.setSpellcheckProvider
* **unloaddictoinary:** unloadDictionary does not reset webframe spellchecker
* **loaddictionary:** loadDictionary does not read physical file from path
anymore



<a name="1.0.0-beta.5"></a>
# [1.0.0-beta.5](https://github.com/kwonoj/electron-hunspell/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2019-01-31)

Expand Down
90 changes: 63 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
[![codecov](https://codecov.io/gh/kwonoj/electron-hunspell/branch/master/graph/badge.svg)](https://codecov.io/gh/kwonoj/electron-hunspell)
[![npm](https://img.shields.io/npm/v/electron-hunspell.svg)](https://www.npmjs.com/package/electron-hunspell)
[![node](https://img.shields.io/badge/node-=>4.0-blue.svg?style=flat)](https://www.npmjs.com/package/electron-hunspell)
[![Greenkeeper badge](https://badges.greenkeeper.io/kwonoj/electron-hunspell.svg)](https://greenkeeper.io/)

# Electron-hunspell

`electron-hunspell` provides [`hunspell`](https://github.com/hunspell/hunspell) based spell checker to [`Electron`](https://electron.atom.io/) based applications with minimal, simple api. This module aims specific design goals compare to other spellchecker implementations

- No native module dependencies
- No platform specific, consistent behavior via hunspell
- No platform specific code, consistent behavior via hunspell
- Low level explicit api surface

There are couple of modules to improve spell checking experiences via `electron-hunspell` to check out if you're interested

- [`cld3-asm`](https://github.com/kwonoj/cld3-asm): Javascript bindings for google compact language detector v3
- [`hunspell-dict-downloader`](https://github.com/kwonoj/hunspell-dict-downloader): Downloader for hunspell dict around several available locales

From 1.0, `electron-hunspell` only supports electron@5 and above supports async spellchecker interface. For previous version of electron, use 0.x version.

# Install

```sh
Expand All @@ -41,47 +42,36 @@ await provider.initialize();

```typescript
initialize(initOptions?: Partial<{
timeout: number;
locateBinary: (filePath: string) => string | object;
environment?: ENVIRONMENT;
}>): Promise<void>;
timeout: number;
environment?: ENVIRONMENT;
}>): Promise<void>;
```

Once you have provider instance, you can manage each dictionary based on locale key.

```typescript
await provider.loadDictionary('en', './en-US.dic', './en-US.aff');
const aff = await (await fetch('https://unpkg.com/hunspell-dict-en-us@0.1.0/en-us.aff')).arrayBuffer();
const dic = await (await fetch('https://unpkg.com/hunspell-dict-en-us@0.1.0/en-us.dic')).arrayBuffer();

await provider.loadDictionary('en', new Uint8Array(dic), new Uint8Array(aff));
```

`loadDictionary` creates spellchecker instance to corresponding locale key.

```typescript
public loadDictionary(key: string, dicPath: string, affPath: string): Promise<void>;
public loadDictionary(key: string, dicBuffer: ArrayBufferView, affBuffer: ArrayBufferView): Promise<void>;
```

It also accepts overload of supplying `ArrayBufferView` for cases you're under environment download dictionary via `fetch` or similar manner and have object in memory.

Once dictionary is loaded in provider instance, you can specify which dictionary to check spells.

```typescript
public switchDictionary(key: string): void
```

Note switching dictionary doesn't occur automatically, it should be called explicitly as needed.

When dictionary is no longer needed it should be manually disposed via `unloadDictionary` interface.

```typescript
public unloadDictionary(key: string): void
public unloadDictionary(key: string): Promise<void>
```

If given key is currently selected spellchecker instance, unload will dispose dictionary as well as clear currently selected spellchecker instance. Otherwise it'll simply dispose dictionary from provider.

To get suggested text for misspelled text use `getSuggestion`

```typescript
public getSuggestion(text: string): Readonly<Array<string>>
public getSuggestion(text: string): Promise<Readonly<Array<string>>>
```

It'll ask currently selected spellchecker to get suggestion for misspelling.
Expand All @@ -91,16 +81,62 @@ Few other convenient interfaces are available as well.
```typescript
//Returns array of key for currently loaded dictionaries
//in desecending order of how long it has been used.
public availableDictionaries: Readonly<Array<string>>;
public getAvailableDictionaries: Promise<Readonly<Array<string>>>;

//Returns key of currently selected dictionary.
public selectedDictionary: string | null;
public getSelectedDictionary: Promise<string | null>;
```

## Attach provider to webFrame

Once provider instance is ready, `attachSpellCheckProvider` can actually attach those into current webFrame.

//Writes bit more verbosed log. Setter only.
public verboseLog: boolean;
```typescript
const attached = attachSpellCheckProvider(provider);

//Change language for spellchecker attached to webFrame.
await attached.switchLanguage('en');
//Teardown webFrame's spellchecker based on current language.
await attached.unsubscribe();
```

[`Example`](https://github.com/kwonoj/electron-hunspell/tree/master/example) provides simple code how to use SpellCheckerProvider in general. `npm run browserwindow` will executes example for general browserWindow, `npm run browserview` provides example for loading external page via `browserView` with preload scripts.
`attachSpellCheckProvider` relies on `provider` to get current language's dictionary. If dictionary is not loaded via `loadDictionary`, spellcheck won't work.

## Put provider to another thread

`attachSpellCheckProvider` not only accepts instance of `SpellCheckerProvider` but also accepts any proxy object can commnuicate to actual provider instance. Since Electron's spellchecker is now async, it is possible to place provider instnace to other than main thread like web worker.

```typescript
//pseudo code

//provider.js
const provider = new SpellCheckerProvider();
self.onmessage = (event) => {
switch (type) {
...
case 'spell':
postMessage('message', provider.spell(...));
}
}

//renderer.js
const worker = new Worker('provider.js');
//proxy object implements necessary interfaces to attach spellchecker
const providerProxy = {
spell: (text) => {
worker.addEventListener('message', onSpell);
worker.postMessage(...);
},
...
};

// use proxy object to attach spellchecker to webframe
await attachSpellCheckProvider(providerProxy);
```

`attachSpellCheckProvider` does not aware where does provider placed - it can be other process communicates via IPC, or webworker, or something else and does not provide any proxy implementation by default. Also note using IPC for proxy need caution, as spellcheck can cause amount of IPC request based on user typings.

[`Example`](https://github.com/kwonoj/electron-hunspell/tree/master/example) provides simple code how to use SpellCheckerProvider in general. `npm run example:browserwindow` will executes example for general browserWindow, `npm run example:browserview` provides example for loading external page via `browserView` with preload scripts. `npm run example:worker` will load example running provider under web worker.

# Building / Testing

Expand Down
12 changes: 8 additions & 4 deletions example/browserView-preload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as fs from 'fs';
import { ENVIRONMENT } from 'hunspell-asm';
import * as path from 'path';
import { enableLogger, SpellCheckerProvider } from '../src/index';
import { attachSpellCheckProvider, enableLogger, SpellCheckerProvider } from '../src/index';

enableLogger(console);

Expand All @@ -12,10 +13,13 @@ const init = async () => {

await browserViewProvider.loadDictionary(
'en',
path.join(path.resolve('./'), 'en-US.dic'),
path.join(path.resolve('./'), 'en-US.aff')
fs.readFileSync(path.join(path.resolve('./example'), 'en-US.dic')),
fs.readFileSync(path.join(path.resolve('./example'), 'en-US.aff'))
);
setTimeout(async () => browserViewProvider.switchDictionary('en'), 3000);

const attached = await attachSpellCheckProvider(browserViewProvider);

setTimeout(async () => attached.switchLanguage('en'), 3000);
};

init();
19 changes: 8 additions & 11 deletions example/browserWindow.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import * as fs from 'fs';
import { ENVIRONMENT } from 'hunspell-asm';
import * as path from 'path';
import { enableLogger, SpellCheckerProvider } from '../src/index';
import { attachSpellCheckProvider, enableLogger, SpellCheckerProvider } from '../src/index';

enableLogger(console);

const init = async () => {
const browserWindowProvider = new SpellCheckerProvider();
(window as any).browserWindowProvider = browserWindowProvider;

await browserWindowProvider.initialize({
environment: ENVIRONMENT.NODE,
locateBinary: file => {
if (file.endsWith('.wasm')) {
return path.resolve('../node_modules/hunspell-asm/dist/cjs/lib/hunspell.wasm');
}
return file;
}
environment: ENVIRONMENT.NODE
});

await browserWindowProvider.loadDictionary(
'en',
path.join(path.resolve('./'), 'en-US.dic'),
path.join(path.resolve('./'), 'en-US.aff')
fs.readFileSync(path.join(path.resolve('./example'), 'en-US.dic')),
fs.readFileSync(path.join(path.resolve('./example'), 'en-US.aff'))
);

browserWindowProvider.switchDictionary('en');
const attached = await attachSpellCheckProvider(browserWindowProvider);
attached.switchLanguage('en');
};

init();
9 changes: 8 additions & 1 deletion example/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ app.on('window-all-closed', () => {
app.on('ready', () => {
mainWindow = new BrowserWindow({
width: 1024,
height: 768
height: 768,
//nodeIntegration is enabled only for simple example. do not follow this in production.
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});

mainWindow.loadURL(`file://${__dirname}/${process.env.ENTRY}.html`);
Expand All @@ -50,8 +55,10 @@ app.on('ready', () => {
//Example logic for browser view
if (process.env.ENTRY === 'browserView') {
const view = new BrowserView({
//nodeIntegration is enabled only for simple example. do not follow this in production.
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: require.resolve('./browserView-preload')
}
});
Expand Down

0 comments on commit c9e0658

Please sign in to comment.