Mailspring/src/window-event-handler.coffee
Evan Morikawa 5ed3b9b4a3 fix(env): add remove unload callback for stores
Also add eslint_d to linter setup.
Developer bar store supports bearer token auth
2016-08-05 12:15:39 -07:00

240 lines
8.6 KiB
CoffeeScript

path = require 'path'
_ = require 'underscore'
{Disposable} = require 'event-kit'
{shell, ipcRenderer, remote} = require 'electron'
fs = require 'fs-plus'
url = require 'url'
# Handles low-level events related to the window.
module.exports =
class WindowEventHandler
constructor: ->
@unloadCallbacks = []
_.defer =>
@showDevModeMessages()
ipcRenderer.on 'update-available', (event, detail) ->
NylasEnv.updateAvailable(detail)
ipcRenderer.on 'browser-window-focus', ->
document.body.classList.remove('is-blurred')
window.dispatchEvent(new Event('browser-window-focus'))
ipcRenderer.on 'browser-window-blur', ->
document.body.classList.add('is-blurred')
window.dispatchEvent(new Event('browser-window-blur'))
ipcRenderer.on 'command', (event, command, args...) ->
NylasEnv.commands.dispatch(command, args[0])
ipcRenderer.on 'scroll-touch-begin', ->
window.dispatchEvent(new Event('scroll-touch-begin'))
ipcRenderer.on 'scroll-touch-end', ->
window.dispatchEvent(new Event('scroll-touch-end'))
window.onbeforeunload = =>
# Don't hide the window here if we don't want the renderer process to be
# throttled in case more work needs to be done before closing
# In Electron, returning any value other than undefined cancels the close.
canCloseWindow = @runUnloadCallbacks()
return undefined if canCloseWindow
return false
window.onunload = =>
NylasEnv.storeWindowDimensions()
NylasEnv.saveStateAndUnloadWindow()
NylasEnv.commands.add document.body, 'window:toggle-full-screen', ->
NylasEnv.toggleFullScreen()
NylasEnv.commands.add document.body, 'window:close', ->
NylasEnv.close()
NylasEnv.commands.add document.body, 'window:reload', =>
NylasEnv.reload()
NylasEnv.commands.add document.body, 'window:toggle-dev-tools', ->
NylasEnv.toggleDevTools()
NylasEnv.commands.add document.body, 'window:open-errorlogger-logs', ->
NylasEnv.errorLogger.openLogs()
NylasEnv.commands.add document.body, 'window:toggle-component-regions', ->
ComponentRegistry = require './component-registry'
ComponentRegistry.toggleComponentRegions()
webContents = NylasEnv.getCurrentWindow().webContents
NylasEnv.commands.add(document.body, 'core:copy', => webContents.copy())
NylasEnv.commands.add(document.body, 'core:cut', => webContents.cut())
NylasEnv.commands.add(document.body, 'core:paste', => webContents.paste())
NylasEnv.commands.add(document.body, 'core:paste-and-match-style', => webContents.pasteAndMatchStyle())
NylasEnv.commands.add(document.body, 'core:undo', => webContents.undo())
NylasEnv.commands.add(document.body, 'core:redo', => webContents.redo())
NylasEnv.commands.add(document.body, 'core:select-all', => webContents.selectAll())
# "Pinch to zoom" on the Mac gets translated by the system into a
# "scroll with ctrl key down". To prevent the page from zooming in,
# prevent default when the ctrlKey is detected.
document.addEventListener 'mousewheel', ->
if event.ctrlKey
event.preventDefault()
document.addEventListener 'drop', @onDrop
document.addEventListener 'dragover', @onDragOver
document.addEventListener 'click', (event) =>
if event.target.nodeName is 'A'
@openLink(event)
document.addEventListener 'contextmenu', (event) =>
if event.target.nodeName is 'INPUT'
@openContextualMenuForInput(event)
# Prevent form submits from changing the current window's URL
document.addEventListener 'submit', (event) =>
if event.target.nodeName is 'FORM'
event.preventDefault()
@openContextualMenuForInput(event)
addUnloadCallback: (callback) ->
@unloadCallbacks.push(callback)
removeUnloadCallback: (callback) ->
@unloadCallbacks = @unloadCallbacks.filter (cb) -> cb isnt callback
runUnloadCallbacks: ->
hasReturned = false
unloadCallbacksRunning = 0
unloadCallbackComplete = =>
unloadCallbacksRunning -= 1
if unloadCallbacksRunning is 0 and hasReturned
@runUnloadFinished()
for callback in @unloadCallbacks
returnValue = callback(unloadCallbackComplete)
if returnValue is false
unloadCallbacksRunning += 1
else if returnValue isnt true
console.warn "You registered an `onBeforeUnload` callback that does not return either exactly `true` or `false`. It returned #{returnValue}", callback
# In Electron, returning false cancels the close.
hasReturned = true
return (unloadCallbacksRunning is 0)
runUnloadFinished: ->
{remote} = require('electron')
_.defer ->
if remote.getGlobal('application').isQuitting()
remote.app.quit()
else
NylasEnv.close()
# Important: even though we don't do anything here, we need to catch the
# drop event to prevent the browser from navigating the to the "url" of the
# file and completely leaving the app.
onDrop: (event) ->
event.preventDefault()
event.stopPropagation()
onDragOver: (event) ->
event.preventDefault()
event.stopPropagation()
openLink: ({href, target, currentTarget, metaKey}) ->
if not href
if target instanceof Element
href = target.getAttribute('href')
else if currentTarget instanceof Element
href = currentTarget.getAttribute('href')
return unless href
return if target?.closest('.no-open-link-events')
schema = url.parse(href).protocol
return unless schema
if schema is 'mailto:'
# We sometimes get mailto URIs that are not escaped properly, or have been only partially escaped.
# (T1927) Be sure to escape them once, and completely, before we try to open them. This logic
# *might* apply to http/https as well but it's unclear.
href = encodeURI(decodeURI(href))
remote.getGlobal('application').openUrl(href)
else if schema in ['http:', 'https:', 'tel:']
shell.openExternal(href, activate: !metaKey)
return
openContextualMenuForInput: (event) ->
event.preventDefault()
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, MenuItem} = remote
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
click: => document.execCommand('cut')
}))
menu.append(new MenuItem({
label: 'Copy'
enabled: hasSelectedText
click: => document.execCommand('copy')
}))
menu.append(new MenuItem({
label: 'Paste',
click: => document.execCommand('paste')
}))
menu.popup(remote.getCurrentWindow())
showDevModeMessages: ->
return unless NylasEnv.isMainWindow()
if NylasEnv.inDevMode()
Actions = require './flux/actions'
Actions.postNotification
icon: 'fa-flask'
type: 'developer'
tag: 'developer'
sticky: true
actions: [{label: 'Thanks', id: 'ok', dismisses: true, default: true}]
message: "N1 is running with debug flags enabled (slower). Packages in
~/.nylas/dev/packages will be loaded. Have fun!"
else
console.log("%c Welcome to N1! If you're exploring the source or building a
plugin, you should enable debug flags. It's slower, but
gives you better exceptions, the debug version of React,
and more. Choose %c Developer > Run with Debug Flags %c
from the menu. Also, check out https://nylas.github.io/N1/docs/
for documentation and sample code!",
"background-color: antiquewhite;",
"background-color: antiquewhite; font-weight:bold;",
"background-color: antiquewhite; font-weight:normal;")