Multi-error, multi-language, and cross-platform spell-check
FREEBIE
This commit is contained in:
parent
122719688a
commit
e8c7e31363
3 changed files with 138 additions and 61 deletions
134
js/spell_check.js
Normal file
134
js/spell_check.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
(function () {
|
||||
var electron = require('electron');
|
||||
var remote = electron.remote;
|
||||
var app = remote.app;
|
||||
var webFrame = electron.webFrame;
|
||||
var path = require('path');
|
||||
|
||||
var osLocale = require('os-locale');
|
||||
var os = require('os');
|
||||
var semver = require('semver');
|
||||
var spellchecker = require('spellchecker');
|
||||
|
||||
// `remote.require` since `Menu` is a main-process module.
|
||||
var buildEditorContextMenu = remote.require('electron-editor-context-menu');
|
||||
|
||||
var EN_VARIANT = /^en/;
|
||||
|
||||
// Prevent the spellchecker from showing contractions as errors.
|
||||
var ENGLISH_SKIP_WORDS = [
|
||||
'ain',
|
||||
'couldn',
|
||||
'didn',
|
||||
'doesn',
|
||||
'hadn',
|
||||
'hasn',
|
||||
'mightn',
|
||||
'mustn',
|
||||
'needn',
|
||||
'oughtn',
|
||||
'shan',
|
||||
'shouldn',
|
||||
'wasn',
|
||||
'weren',
|
||||
'wouldn'
|
||||
];
|
||||
|
||||
function setupLinux(locale) {
|
||||
if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
|
||||
// apt-get install hunspell-<locale> can be run for easy access to other dictionaries
|
||||
var location = process.env.HUNSPELL_DICTIONARIES || '/usr/share/hunspell';
|
||||
|
||||
console.log('Detected Linux. Setting up spell check with locale', locale, 'and dictionary location', location);
|
||||
spellchecker.setDictionary(locale, location);
|
||||
} else {
|
||||
console.log('Detected Linux. Using default en_US spell check dictionary');
|
||||
}
|
||||
}
|
||||
|
||||
function setupWin7AndEarlier(locale) {
|
||||
if (process.env.HUNSPELL_DICTIONARIES || locale !== 'en_US') {
|
||||
var location = process.env.HUNSPELL_DICTIONARIES;
|
||||
|
||||
console.log('Detected Windows 7 or below. Setting up spell-check with locale', locale, 'and dictionary location', location);
|
||||
spellchecker.setDictionary(locale, location);
|
||||
} else {
|
||||
console.log('Detected Windows 7 or below. Using default en_US spell check dictionary');
|
||||
}
|
||||
}
|
||||
|
||||
var locale = osLocale.sync().replace('-', '_');
|
||||
|
||||
// The LANG environment variable is how node spellchecker finds its default language:
|
||||
// https://github.com/atom/node-spellchecker/blob/59d2d5eee5785c4b34e9669cd5d987181d17c098/lib/spellchecker.js#L29
|
||||
if (!process.env.LANG) {
|
||||
process.env.LANG = locale;
|
||||
}
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
setupLinux(locale);
|
||||
} else if (process.platform === 'windows' && semver.lt(os.release(), '8.0.0')) {
|
||||
setupWin7AndEarlier(locale);
|
||||
} else {
|
||||
// OSX and Windows 8+ have OS-level spellcheck APIs
|
||||
console.log('Using OS-level spell check API with locale', process.env.LANG);
|
||||
}
|
||||
|
||||
var simpleChecker = window.spellChecker = {
|
||||
spellCheck: function(text) {
|
||||
return !this.isMisspelled(text);
|
||||
},
|
||||
isMisspelled: function(text) {
|
||||
var misspelled = spellchecker.isMisspelled(text);
|
||||
|
||||
// The idea is to make this as fast as possible. For the many, many calls which
|
||||
// don't result in the red squiggly, we minimize the number of checks.
|
||||
if (!misspelled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only if we think we've found an error do we check the locale and skip list.
|
||||
if (locale.match(EN_VARIANT) && _.contains(ENGLISH_SKIP_WORDS, text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
getSuggestions: function(text) {
|
||||
return spellchecker.getCorrectionsForMisspelling(text);
|
||||
},
|
||||
add: function(text) {
|
||||
spellchecker.add(text);
|
||||
}
|
||||
};
|
||||
|
||||
webFrame.setSpellCheckProvider(
|
||||
'en-US',
|
||||
// Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371
|
||||
// The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too.
|
||||
true,
|
||||
simpleChecker
|
||||
);
|
||||
|
||||
window.addEventListener('contextmenu', function(e) {
|
||||
// Only show the context menu in text editors.
|
||||
if (!e.target.closest('textarea, input, [contenteditable="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedText = window.getSelection().toString();
|
||||
var isMisspelled = selectedText && simpleChecker.isMisspelled(selectedText);
|
||||
var spellingSuggestions = isMisspelled && simpleChecker.getSuggestions(selectedText).slice(0, 5);
|
||||
var menu = buildEditorContextMenu({
|
||||
isMisspelled: isMisspelled,
|
||||
spellingSuggestions: spellingSuggestions,
|
||||
});
|
||||
|
||||
// The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the
|
||||
// visible selection has changed. Try to wait to show the menu until after that, otherwise the
|
||||
// visible selection will update after the menu dismisses and look weird.
|
||||
setTimeout(function() {
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
}, 30);
|
||||
});
|
||||
})();
|
|
@ -116,6 +116,7 @@
|
|||
"images/**",
|
||||
"fonts/*",
|
||||
"node_modules/**",
|
||||
"!node_modules/spellchecker/vendor/hunspell/**/*",
|
||||
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,test,__tests__,tests,powered-test,example,examples,*.d.ts}",
|
||||
"!**/node_modules/.bin",
|
||||
"!**/node_modules/*/build/**",
|
||||
|
|
64
preload.js
64
preload.js
|
@ -2,7 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
console.log('preload');
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
window.PROTO_ROOT = 'protos';
|
||||
window.config = require('url').parse(window.location.toString(), true).query;
|
||||
|
@ -28,68 +28,10 @@
|
|||
ipc.on('debug-log', function() {
|
||||
Whisper.events.trigger('showDebugLog');
|
||||
});
|
||||
/**
|
||||
* Enables spell-checking and the right-click context menu in text editors.
|
||||
* Electron (`webFrame.setSpellCheckProvider`) only underlines misspelled words;
|
||||
* we must manage the menu ourselves.
|
||||
*
|
||||
* Run this in the renderer process.
|
||||
*/
|
||||
var remote = electron.remote;
|
||||
var webFrame = electron.webFrame;
|
||||
var SpellCheckProvider = require('electron-spell-check-provider');
|
||||
// `remote.require` since `Menu` is a main-process module.
|
||||
var buildEditorContextMenu = remote.require('electron-editor-context-menu');
|
||||
|
||||
var selection;
|
||||
function resetSelection() {
|
||||
selection = {
|
||||
isMisspelled: false,
|
||||
spellingSuggestions: []
|
||||
};
|
||||
}
|
||||
resetSelection();
|
||||
// We pull these dependencies in now, from here, because they have Node.js dependencies
|
||||
|
||||
window.spellChecker = new SpellCheckProvider(window.config.locale).on('misspelling', function(suggestions) {
|
||||
// Prime the context menu with spelling suggestions _if_ the user has selected text. Electron
|
||||
// may sometimes re-run the spell-check provider for an outdated selection e.g. if the user
|
||||
// right-clicks some misspelled text and then an image.
|
||||
if (window.getSelection().toString()) {
|
||||
selection.isMisspelled = true;
|
||||
// Take the first three suggestions if any.
|
||||
selection.spellingSuggestions = suggestions.slice(0, 3);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset the selection when clicking around, before the spell-checker runs and the context menu shows.
|
||||
window.addEventListener('mousedown', resetSelection);
|
||||
|
||||
// The spell-checker runs when the user clicks on text and before the 'contextmenu' event fires.
|
||||
// Thus, we may retrieve spell-checking suggestions to put in the menu just before it shows.
|
||||
|
||||
webFrame.setSpellCheckProvider(
|
||||
'en-US',
|
||||
// Not sure what this parameter (`autoCorrectWord`) does: https://github.com/atom/electron/issues/4371
|
||||
// The documentation for `webFrame.setSpellCheckProvider` passes `true` so we do too.
|
||||
true,
|
||||
spellChecker
|
||||
);
|
||||
|
||||
window.addEventListener('contextmenu', function(e) {
|
||||
// Only show the context menu in text editors.
|
||||
if (!e.target.closest('textarea, input, [contenteditable="true"]')) return;
|
||||
|
||||
var menu = buildEditorContextMenu(selection);
|
||||
|
||||
// The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the
|
||||
// visible selection has changed. Try to wait to show the menu until after that, otherwise the
|
||||
// visible selection will update after the menu dismisses and look weird.
|
||||
setTimeout(function() {
|
||||
menu.popup(remote.getCurrentWindow());
|
||||
}, 30);
|
||||
});
|
||||
|
||||
// we have to pull this in this way because it references node APIs
|
||||
require('./js/spell_check');
|
||||
require('./js/backup');
|
||||
|
||||
})();
|
||||
|
|
Loading…
Add table
Reference in a new issue