Mailspring/internal_packages/composer-spellcheck/lib/draft-extension.coffee

112 lines
3.7 KiB
CoffeeScript
Raw Normal View History

{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()
@onLearnSpelling: (editableNode, word) ->
delete SpellcheckCache[word]
@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