2015-09-03 04:20:01 +08:00
|
|
|
|
{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()
|
|
|
|
|
|
2015-09-09 08:52:26 +08:00
|
|
|
|
@onLearnSpelling: (editableNode, word) ->
|
|
|
|
|
delete SpellcheckCache[word]
|
|
|
|
|
@ensureSetup()
|
|
|
|
|
@walkTreeNodes.push(editableNode)
|
|
|
|
|
@walkTreesDebounced()
|
|
|
|
|
|
2015-09-03 04:20:01 +08:00
|
|
|
|
@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
|