From fc59d88337763391fc7e1e4ee5385f5cc1af50eb Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Mon, 14 Mar 2016 10:23:57 -0700 Subject: [PATCH] fix(spellcheck): Enables spellcheck menu for basic inputs (#1600) Summary: Just moves some code so we can easily attach spelling menus to the basic inputs. Test Plan: Updated existing tests Reviewers: juan, drew Reviewed By: drew Differential Revision: https://phab.nylas.com/D2721 --- .../lib/spellcheck-composer-extension.es6 | 59 ++++--------------- .../spellcheck-composer-extension-spec.es6 | 3 +- src/nylas-spellchecker.coffee | 42 ++++++++++--- src/window-event-handler.coffee | 20 +++++++ 4 files changed, 70 insertions(+), 54 deletions(-) diff --git a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6 b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6 index 0356104e1..aef5308c5 100644 --- a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6 +++ b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.es6 @@ -1,58 +1,27 @@ import {DOMUtils, ComposerExtension, NylasSpellchecker} from 'nylas-exports'; -import {remote} from 'electron'; -const MenuItem = remote.require('menu-item'); - -const SpellcheckCache = {}; export default class SpellcheckComposerExtension extends ComposerExtension { - static isMisspelled(word) { - if (SpellcheckCache[word] === undefined) { - SpellcheckCache[word] = NylasSpellchecker.isMisspelled(word); - } - return SpellcheckCache[word]; - } - static onContentChanged({editor}) { SpellcheckComposerExtension.update(editor); } - static onShowContextMenu = ({editor, menu})=> { + static onShowContextMenu = ({editor, menu}) => { const selection = editor.currentSelection(); const range = DOMUtils.Mutating.getRangeAtAndSelectWord(selection, 0); const word = range.toString(); - if (SpellcheckComposerExtension.isMisspelled(word)) { - const corrections = NylasSpellchecker.getCorrectionsForMisspelling(word); - if (corrections.length > 0) { - corrections.forEach((correction)=> { - menu.append(new MenuItem({ - label: correction, - click: SpellcheckComposerExtension.applyCorrection.bind(SpellcheckComposerExtension, editor, range, selection, correction), - })); - }); - } else { - menu.append(new MenuItem({ label: 'No Guesses Found', enabled: false})); - } - - menu.append(new MenuItem({ type: 'separator' })); - menu.append(new MenuItem({ - label: 'Learn Spelling', - click: SpellcheckComposerExtension.learnSpelling.bind(SpellcheckComposerExtension, editor, word), - })); - menu.append(new MenuItem({ type: 'separator' })); - } - } - - static applyCorrection = (editor, range, selection, correction)=> { - DOMUtils.Mutating.applyTextInRange(range, selection, correction); - SpellcheckComposerExtension.update(editor); - } - - static learnSpelling = (editor, word)=> { - NylasSpellchecker.add(word); - delete SpellcheckCache[word]; - SpellcheckComposerExtension.update(editor); + NylasSpellchecker.appendSpellingItemsToMenu({ + menu, + word, + onCorrect: (correction) => { + DOMUtils.Mutating.applyTextInRange(range, selection, correction); + SpellcheckComposerExtension.update(editor); + }, + onDidLearn: () => { + SpellcheckComposerExtension.update(editor); + }, + }); } static update = (editor) => { @@ -141,7 +110,7 @@ export default class SpellcheckComposerExtension extends ComposerExtension { break; } - if (SpellcheckComposerExtension.isMisspelled(match[0])) { + if (NylasSpellchecker.isMisspelled(match[0])) { // The insertion point is currently at the end of this misspelled word. // Do not mark it until the user types a space or leaves. if ((selectionSnapshot.focusNode === node) && (selectionSnapshot.focusOffset === match.index + match[0].length)) { @@ -190,5 +159,3 @@ export default class SpellcheckComposerExtension extends ComposerExtension { return Promise.resolve(); } } - -SpellcheckComposerExtension.SpellcheckCache = SpellcheckCache; diff --git a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6 b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6 index 49c3f686b..31d58b35d 100644 --- a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6 +++ b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.es6 @@ -4,6 +4,7 @@ import fs from 'fs'; import path from 'path'; import SpellcheckComposerExtension from '../lib/spellcheck-composer-extension'; +import {NylasSpellchecker} from 'nylas-exports'; const initialHTML = fs.readFileSync(path.join(__dirname, 'fixtures', 'california-with-misspellings-before.html')).toString(); const expectedHTML = fs.readFileSync(path.join(__dirname, 'fixtures', 'california-with-misspellings-after.html')).toString(); @@ -12,7 +13,7 @@ describe("SpellcheckComposerExtension", ()=> { beforeEach(()=> { // Avoid differences between node-spellcheck on different platforms const spellings = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', 'california-spelling-lookup.json'))); - spyOn(SpellcheckComposerExtension, 'isMisspelled').andCallFake(word=> spellings[word]) + spyOn(NylasSpellchecker, 'isMisspelled').andCallFake(word=> spellings[word]) }); describe("update", ()=> { diff --git a/src/nylas-spellchecker.coffee b/src/nylas-spellchecker.coffee index cf4f06fa0..9335edae7 100644 --- a/src/nylas-spellchecker.coffee +++ b/src/nylas-spellchecker.coffee @@ -1,10 +1,12 @@ path = require('path') - spellchecker = require('spellchecker') +{remote} = require('electron') +MenuItem = remote.require('menu-item') class NylasSpellchecker constructor: -> @languageAvailable = false + @cache = {} lang = navigator.language @setLanguage(lang) @@ -63,15 +65,19 @@ class NylasSpellchecker dict #### spellchecker methods #### - setDictionary: (lang, dict) => @setLanguage(lang, dict) + setDictionary: (lang, dict) => + @setLanguage(lang, dict) - add: spellchecker.add + add: (word) => + delete @cache[word] + spellchecker.add(word) - isMisspelled: (text) => + isMisspelled: (word) => if @languageAvailable - spellchecker.isMisspelled(text) + @cache[word] ?= spellchecker.isMisspelled(word) + @cache[word] else - return false + false getAvailableDictionaries: -> if process.platform is 'linux' @@ -87,6 +93,28 @@ class NylasSpellchecker spellchecker.getCorrectionsForMisspelling(args...) else return [] + appendSpellingItemsToMenu: ({menu, word, onCorrect, onDidLearn}) => + if @isMisspelled(word) + corrections = @getCorrectionsForMisspelling(word) + if corrections.length > 0 + corrections.forEach (correction) => + menu.append(new MenuItem({ + label: correction, + click: -> onCorrect(correction) + })) + else + menu.append(new MenuItem({ label: 'No Guesses Found', enabled: false})) + + menu.append(new MenuItem({ type: 'separator' })) + menu.append(new MenuItem({ + label: 'Learn Spelling', + click: => + @add(word) + onDidLearn?(word) + })) + menu.append(new MenuItem({ type: 'separator' })) + + Spellchecker: => @ -module.exports = new NylasSpellchecker +module.exports = new NylasSpellchecker() diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index f06240e0e..d6ce9596e 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -202,10 +202,30 @@ class WindowEventHandler return unless event.target.type in ['text', 'password', 'email', 'number', 'range', 'search', 'tel', 'url'] hasSelectedText = event.target.selectionStart isnt event.target.selectionEnd + if hasSelectedText + wordStart = event.target.selectionStart + wordEnd = event.target.selectionEnd + else + wordStart = event.target.value.lastIndexOf(" ", event.target.selectionStart) + wordStart = 0 if wordStart is -1 + wordEnd = event.target.value.indexOf(" ", event.target.selectionStart) + wordEnd = event.target.value.length if wordEnd is -1 + word = event.target.value.substr(wordStart, wordEnd - wordStart) + {remote} = require('electron') Menu = remote.require('menu') MenuItem = remote.require('menu-item') menu = new Menu() + + NylasSpellchecker = require('./nylas-spellchecker') + NylasSpellchecker.appendSpellingItemsToMenu + menu: menu, + word: word, + onCorrect: (correction) => + insertionPoint = wordStart + correction.length + event.target.value = event.target.value.replace(word, correction) + event.target.setSelectionRange(insertionPoint, insertionPoint) + menu.append(new MenuItem({ label: 'Cut' enabled: hasSelectedText