{DraftStoreExtension, AccountStore} = require 'nylas-exports' _ = require 'underscore' SpellcheckCache = {} class SpellcheckDraftStoreExtension extends DraftStoreExtension @isMisspelled: (word) -> @spellchecker ?= require('spellchecker') SpellcheckCache[word] ?= @spellchecker.isMisspelled(word) SpellcheckCache[word] @ensureSetup: -> @walkTreeNodes ?= [] @walkTreesDebounced ?= _.debounce(@walkTrees, 200) @onInput: (editableNode, event) -> @ensureSetup() @walkTreeNodes.push(editableNode) @walkTreesDebounced() @onSubstitutionPerformed: (editableNode) -> @ensureSetup() @walkTreeNodes.push(editableNode) @walkTreesDebounced() @walkTrees: (nodes) => _.each(_.uniq(@walkTreeNodes), @walkTree) @walkTree: (editableNode) => treeWalker = document.createTreeWalker(editableNode, NodeFilter.SHOW_TEXT) nodeList = [] selection = document.getSelection() selectionSnapshot = anchorNode: selection.anchorNode anchorOffset: selection.anchorOffset focusNode: selection.focusNode focusOffset: selection.focusOffset selectionImpacted = false while (treeWalker.nextNode()) nodeList.push(treeWalker.currentNode) while (node = nodeList.pop()) str = node.textContent # https://regex101.com/r/bG5yC4/1 wordRegexp = /(\w[\w'’-]*\w|\w)/g while ((match = wordRegexp.exec(str)) isnt null) spellingSpan = null if node.parentNode and node.parentNode.nodeName is 'SPELLING' if match[0] is str spellingSpan = node.parentNode else node.parentNode.classList.remove('misspelled') misspelled = @isMisspelled(match[0]) markedAsMisspelled = spellingSpan?.classList.contains('misspelled') if misspelled and not markedAsMisspelled if spellingSpan spellingSpan.classList.add('misspelled') else if match.index is 0 matchNode = node else matchNode = node.splitText(match.index) afterMatchNode = matchNode.splitText(match[0].length) spellingSpan = document.createElement('spelling') spellingSpan.classList.add('misspelled') spellingSpan.innerText = match[0] matchNode.parentNode.replaceChild(spellingSpan, matchNode) for prop in ['anchor', 'focus'] if selectionSnapshot["#{prop}Node"] is node if selectionSnapshot["#{prop}Offset"] > match.index + match[0].length selectionImpacted = true selectionSnapshot["#{prop}Node"] = afterMatchNode selectionSnapshot["#{prop}Offset"] -= match.index + match[0].length else if selectionSnapshot["#{prop}Offset"] > match.index selectionImpacted = true selectionSnapshot["#{prop}Node"] = spellingSpan.childNodes[0] selectionSnapshot["#{prop}Offset"] -= match.index nodeList.unshift(afterMatchNode) break else if not misspelled and markedAsMisspelled spellingSpan.classList.remove('misspelled') if selectionImpacted selection.setBaseAndExtent(selectionSnapshot.anchorNode, selectionSnapshot.anchorOffset, selectionSnapshot.focusNode, selectionSnapshot.focusOffset) @finalizeSessionBeforeSending: (session) -> body = session.draft().body clean = body.replace(/<\/?spelling[^>]*>/g, '') if body != clean session.changes.add(body: clean) SpellcheckDraftStoreExtension.SpellcheckCache = SpellcheckCache module.exports = SpellcheckDraftStoreExtension