mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-25 17:56:06 +08:00
cfdc401c54
Summary: - Rename DraftStoreExtension to ComposerExtension - Rename MessageStoreExtension to MessageViewExtension - Rename ContenteditablePlugin to ContenteditableExtension - Update Contenteditable to use new naming convention - Adds support for extension handlers as props - Add ExtensionRegistry to register extensions: - ContenteditableExtensions will not be registered through the ExtensionRegistry. They are meant for internal use, or if anyone wants to use our Contenteditable component directly in their plugins. - Adds specs - Refactors internal_packages and src to use new names and new ExtensionRegistry api - Adds deprecation util function and deprecation notices for old api methods: - DraftStore.{registerExtension, unregisterExtension} - MessageStore.{registerExtension, unregisterExtension} - DraftStoreExtension.{onMouseUp, onTabDown} - MessageStoreExtension - Adds and updates docs Test Plan: - Unit tests Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2293
129 lines
4.7 KiB
CoffeeScript
129 lines
4.7 KiB
CoffeeScript
{ComposerExtension, AccountStore, DOMUtils} = require 'nylas-exports'
|
||
_ = require 'underscore'
|
||
spellchecker = require('spellchecker')
|
||
remote = require('remote')
|
||
MenuItem = remote.require('menu-item')
|
||
|
||
SpellcheckCache = {}
|
||
|
||
class SpellcheckComposerExtension extends ComposerExtension
|
||
|
||
@isMisspelled: (word) ->
|
||
SpellcheckCache[word] ?= spellchecker.isMisspelled(word)
|
||
SpellcheckCache[word]
|
||
|
||
@onInput: (editableNode) =>
|
||
@walkTree(editableNode)
|
||
|
||
@onShowContextMenu: (event, editableNode, selection, menu) =>
|
||
range = DOMUtils.Mutating.getRangeAtAndSelectWord(selection, 0)
|
||
word = range.toString()
|
||
if @isMisspelled(word)
|
||
corrections = spellchecker.getCorrectionsForMisspelling(word)
|
||
if corrections.length > 0
|
||
corrections.forEach (correction) =>
|
||
menu.append(new MenuItem({
|
||
label: correction,
|
||
click: @applyCorrection.bind(@, editableNode, 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: @learnSpelling.bind(@, editableNode, word)
|
||
}))
|
||
menu.append(new MenuItem({ type: 'separator' }))
|
||
|
||
@applyCorrection: (editableNode, range, selection, correction) =>
|
||
DOMUtils.Mutating.applyTextInRange(range, selection, correction)
|
||
@walkTree(editableNode)
|
||
|
||
@learnSpelling: (editableNode, word) =>
|
||
spellchecker.add(word)
|
||
delete SpellcheckCache[word]
|
||
@walkTree(editableNode)
|
||
|
||
@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
|
||
# 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 is node and selectionSnapshot.focusOffset is match.index + match[0].length
|
||
continue
|
||
|
||
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)
|
||
|
||
SpellcheckComposerExtension.SpellcheckCache = SpellcheckCache
|
||
|
||
module.exports = SpellcheckComposerExtension
|