path = require 'path' {$} = require './space-pen-extensions' _ = require 'underscore' {Disposable} = require 'event-kit' {shell, ipcRenderer} = require 'electron' {Subscriber} = require 'emissary' fs = require 'fs-plus' url = require 'url' # Handles low-level events related to the window. module.exports = class WindowEventHandler Subscriber.includeInto(this) constructor: -> @reloadRequested = false _.defer => @showDevModeMessages() @subscribe ipcRenderer, 'open-path', (event, pathToOpen) -> unless NylasEnv.project?.getPaths().length if fs.existsSync(pathToOpen) or fs.existsSync(path.dirname(pathToOpen)) NylasEnv.project?.setPaths([pathToOpen]) unless fs.isDirectorySync(pathToOpen) NylasEnv.workspace?.open(pathToOpen, {}) @subscribe ipcRenderer, 'update-available', (event, detail) -> NylasEnv.updateAvailable(detail) @subscribe ipcRenderer, 'send-feedback', (detail) -> Actions = require './flux/actions' Actions.sendFeedback() @subscribe ipcRenderer, 'browser-window-focus', -> document.body.classList.remove('is-blurred') @subscribe ipcRenderer, 'browser-window-blur', -> document.body.classList.add('is-blurred') @subscribe ipcRenderer, 'command', (event, command, args...) -> activeElement = document.activeElement # Use the workspace element view if body has focus if activeElement is document.body and workspaceElement = document.getElementById("nylas-workspace") activeElement = workspaceElement NylasEnv.commands.dispatch(activeElement, command, args[0]) @subscribe $(window), 'beforeunload', => if NylasEnv.getCurrentWindow().isWebViewFocused() and not @reloadRequested NylasEnv.hide() @reloadRequested = false NylasEnv.storeWindowDimensions() NylasEnv.saveStateAndUnloadWindow() true @subscribe $(window), 'unload', => NylasEnv.windowEventHandler?.unsubscribe() @subscribeToCommand $(window), 'window:toggle-full-screen', -> NylasEnv.toggleFullScreen() @subscribeToCommand $(window), 'window:close', -> NylasEnv.close() @subscribeToCommand $(window), 'window:reload', => @reloadRequested = true NylasEnv.reload() @subscribeToCommand $(window), 'window:toggle-dev-tools', -> NylasEnv.toggleDevTools() @subscribeToCommand $(window), 'window:open-errorlogger-logs', -> NylasEnv.errorLogger.openLogs() @subscribeToCommand $(window), 'window:toggle-component-regions', -> ComponentRegistry = require './component-registry' ComponentRegistry.toggleComponentRegions() @subscribeToCommand $(window), 'window:toggle-react-remote', -> ReactRemote = require './react-remote/react-remote-parent' ReactRemote.toggleContainerVisible() @subscribeToCommand $(document), 'core:focus-next', @focusNext @subscribeToCommand $(document), 'core:focus-previous', @focusPrevious document.addEventListener 'keydown', @onKeydown # "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 @subscribe new Disposable => document.removeEventListener('drop', @onDrop) document.addEventListener 'dragover', @onDragOver @subscribe new Disposable => document.removeEventListener('dragover', @onDragOver) @subscribe $(document), 'click', 'a', @openLink @subscribe $(document), 'contextmenu', 'input', @openContextualMenuForInput # Prevent form submits from changing the current window's URL @subscribe $(document), 'submit', 'form', (e) -> e.preventDefault() @handleNativeKeybindings() # Wire commands that should be handled by Chromium for elements with the # `.override-key-bindings` class. handleNativeKeybindings: -> menu = null webContents = NylasEnv.getCurrentWindow().webContents bindCommandToAction = (command, action) => @subscribe $(document), command, (event) -> unless event.target.webkitMatchesSelector('.override-key-bindings') webContents[action]() true bindCommandToAction('core:copy', 'copy') bindCommandToAction('core:cut', 'cut') bindCommandToAction('core:paste', 'paste') bindCommandToAction('core:paste-and-match-style', 'pasteAndMatchStyle') bindCommandToAction('core:undo', 'undo') bindCommandToAction('core:redo', 'redo') bindCommandToAction('core:select-all', 'selectAll') onKeydown: (event) -> NylasEnv.keymaps.handleKeyboardEvent(event) # 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}) -> if not href href = target?.getAttribute('href') or currentTarget?.getAttribute('href') return unless href 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. shell.openExternal(encodeURI(decodeURI(href))) else if schema in ['http:', 'https:', 'tel:'] shell.openExternal(href) return openContextualMenuForInput: (event) -> event.preventDefault() hasSelectedText = event.target.selectionStart isnt event.target.selectionEnd remote = require('remote') Menu = remote.require('menu') MenuItem = remote.require('menu-item') menu = new Menu() 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()) eachTabIndexedElement: (callback) -> for element in $('[tabindex]') element = $(element) continue if element.isDisabled() tabIndex = parseInt(element.attr('tabindex')) continue unless tabIndex >= 0 callback(element, tabIndex) focusNext: => focusedTabIndex = parseInt($(':focus').attr('tabindex')) or -Infinity nextElement = null nextTabIndex = Infinity lowestElement = null lowestTabIndex = Infinity @eachTabIndexedElement (element, tabIndex) -> if tabIndex < lowestTabIndex lowestTabIndex = tabIndex lowestElement = element if focusedTabIndex < tabIndex < nextTabIndex nextTabIndex = tabIndex nextElement = element if nextElement? nextElement.focus() else if lowestElement? lowestElement.focus() focusPrevious: => focusedTabIndex = parseInt($(':focus').attr('tabindex')) or Infinity previousElement = null previousTabIndex = -Infinity highestElement = null highestTabIndex = -Infinity @eachTabIndexedElement (element, tabIndex) -> if tabIndex > highestTabIndex highestTabIndex = tabIndex highestElement = element if focusedTabIndex > tabIndex > previousTabIndex previousTabIndex = tabIndex previousElement = element if previousElement? previousElement.focus() else if highestElement? highestElement.focus() 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.com/N1/docs for documentation and sample code!", "background-color: antiquewhite;", "background-color: antiquewhite; font-weight:bold;", "background-color: antiquewhite; font-weight:normal;")