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

feat(spellcheckerprovider): async spellchecker support #389

Merged
merged 28 commits into from
May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3041e2b
refactor(loaddictionary): only accept arraybuffer
kwonoj May 16, 2019
eda21cf
build(package): bump up version
kwonoj May 16, 2019
12bf1ec
Merge pull request #390 from kwonoj/feat-async-spellchecker
kwonoj May 16, 2019
5e6dcbf
refactor(unloaddictoinary): do not reset webframe provider
kwonoj May 18, 2019
27d7f49
build(package): bump up electon to latest
kwonoj May 18, 2019
b926902
feat(spellcheckerprovider): switchdictionary do not autoattach
kwonoj May 18, 2019
eeea886
feat(attachspellcheckerprovider): implement attachprovider
kwonoj May 18, 2019
88b3ed6
feat(providerproxy): expose interfaces for attach
kwonoj May 19, 2019
6c3bf89
feat(spellcheckerprovider): async interfaces
kwonoj May 19, 2019
0b72398
feat(spellcheckerprovider): implement spell
kwonoj May 19, 2019
198f7c6
feat(providerproxy): no longer explicitly requires languagekey
kwonoj May 19, 2019
aa50c43
docs(provider): update comments
kwonoj May 19, 2019
816023c
Merge pull request #391 from kwonoj/feat-loaddict
kwonoj May 19, 2019
a64b7de
docs(example): update example code, remove separate pkg
kwonoj May 19, 2019
3439110
refactor(spellcheckcallback): use async
kwonoj May 19, 2019
cf4af3b
Merge pull request #392 from kwonoj/refactor-docs
kwonoj May 19, 2019
cd33569
docs(example): add example for worker
kwonoj May 19, 2019
a4fe989
Merge pull request #393 from kwonoj/example-worker
kwonoj May 19, 2019
a567ae9
fix(provider): allow tree shake
kwonoj May 19, 2019
0dd2bab
Merge pull request #394 from kwonoj/tree-shake
kwonoj May 19, 2019
fb6e89d
test(provider): no longer uses chai for assertion
kwonoj May 20, 2019
95d0c15
test(attachspellcheckprovider): add test cases
kwonoj May 20, 2019
a1dde94
Merge pull request #395 from kwonoj/update-test
kwonoj May 20, 2019
c0108a3
docs(readme): update documentation
kwonoj May 20, 2019
3b168bc
Merge pull request #396 from kwonoj/docs-readme
kwonoj May 20, 2019
03a77a5
build(package): bump up version
kwonoj May 20, 2019
0057c86
build(release): release 1.0.0-beta.7
kwonoj May 20, 2019
631f539
Merge pull request #397 from kwonoj/bump-version
kwonoj May 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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