diff --git a/dot-nylas/keymap.cson b/dot-nylas/keymap.cson index b2d4928d8..413981a1d 100644 --- a/dot-nylas/keymap.cson +++ b/dot-nylas/keymap.cson @@ -1,24 +1,26 @@ # Your keymap # -# Atom keymaps work similarly to stylesheets. Just as stylesheets use selectors -# to apply styles to elements, Atom keymaps use selectors to associate -# keystrokes with events in specific contexts. +# N1 keymaps work in conjunction with the {KeyCommandsRegion} React +# component. # -# You can create a new keybinding in this file by typing "key" and then hitting -# tab. +# A key, or sequence of keys is first mapped to a "command". # -# Here's an example taken from Atom's built-in keymap: +# The "command" is then mapped to a callback function within your React +# component or store. # -# 'atom-text-editor': -# 'enter': 'editor:newline' +# The keyboard -> command mapping is defined in this `.cson` file. Each +# mapping is scoped under the component that it applies to by matching the +# root-level CSS class of that component. # -# '.workspace': -# 'ctrl-shift-p': 'core:move-up' -# 'ctrl-p': 'core:move-down' +# Any global, top-level mappings are scoped under the `body` selector. # -# You can find more information about keymaps in these guides: -# * https://atom.io/docs/latest/customizing-atom#customizing-key-bindings -# * https://atom.io/docs/latest/advanced/keymaps +# For example: +# +# 'body': +# 'ctrl-c': 'application:new-message' +# +# '.my-custom-package': +# 'ctrl-p': 'myPackage:customAction' # # This file uses CoffeeScript Object Notation (CSON). # If you are unfamiliar with CSON, you can read more about it here: diff --git a/examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx b/examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx index 90ba7c94f..694921260 100644 --- a/examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx +++ b/examples/N1-Message-View-on-Github/lib/view-on-github-button.cjsx @@ -1,7 +1,7 @@ shell = require 'shell' GithubStore = require './github-store' {React} = require 'nylas-exports' -{RetinaImg} = require 'nylas-component-kit' +{RetinaImg, KeyCommandsRegion} = require 'nylas-component-kit' ### The `ViewOnGithubButton` displays a button whenever there's a relevant @@ -68,23 +68,24 @@ class ViewOnGithubButton extends React.Component # are cleaned up. Every time the `GithubStore` calls its `trigger` # method, the `_onStoreChanged` callback will be fired. @_unlisten = GithubStore.listen(@_onStoreChanged) - @_keymapUnlisten = atom.commands.add 'body', { - 'github:open': @_openLink - } componentWillUnmount: -> @_unlisten?() - @_keymapUnlisten?.dispose() + + _keymapHandlers: -> + 'github:open': @_openLink render: -> return null unless @state.link - + + + #### Super common N1 Component private methods #### diff --git a/internal_packages/category-picker/lib/category-picker.cjsx b/internal_packages/category-picker/lib/category-picker.cjsx index 4c75086da..5a21c6b4f 100644 --- a/internal_packages/category-picker/lib/category-picker.cjsx +++ b/internal_packages/category-picker/lib/category-picker.cjsx @@ -19,6 +19,7 @@ React = require 'react' {Menu, Popover, RetinaImg, + KeyCommandsRegion, LabelColorizer} = require 'nylas-component-kit' # This changes the category on one or more threads. @@ -37,9 +38,6 @@ class CategoryPicker extends React.Component @unsubscribers.push CategoryStore.listen @_onStoreChanged @unsubscribers.push AccountStore.listen @_onStoreChanged - @_commandUnsubscriber = atom.commands.add 'body', - "application:change-category": @_onOpenCategoryPopover - # If the threads we're picking categories for change, (like when they # get their categories updated), we expect our parents to pass us new # props. We don't listen to the DatabaseStore ourselves. @@ -50,7 +48,9 @@ class CategoryPicker extends React.Component componentWillUnmount: => return unless @unsubscribers unsubscribe() for unsubscribe in @unsubscribers - @_commandUnsubscriber.dispose() + + _keymapHandlers: -> + "application:change-category": @_onOpenCategoryPopover render: => return unless @_account @@ -84,23 +84,25 @@ class CategoryPicker extends React.Component onChange={@_onSearchValueChange}/> ] - - item.id } - itemContent={@_renderItemContent} - onSelect={@_onSelectCategory} - defaultSelectedIndex={if @state.searchValue is "" then -1 else 0} - /> - + + + item.id } + itemContent={@_renderItemContent} + onSelect={@_onSelectCategory} + defaultSelectedIndex={if @state.searchValue is "" then -1 else 0} + /> + + _onOpenCategoryPopover: => return unless @_threads().length > 0 diff --git a/internal_packages/composer/keymaps/composer.cson b/internal_packages/composer/keymaps/composer.cson index a27536f7d..64b5490ce 100644 --- a/internal_packages/composer/keymaps/composer.cson +++ b/internal_packages/composer/keymaps/composer.cson @@ -1,31 +1,16 @@ '.composer-outer-wrap, .composer-outer-wrap input, .composer-outer-wrap div[contenteditable]': - 'cmd-B' : 'composer:show-and-focus-bcc' - 'cmd-C' : 'composer:show-and-focus-cc' - 'ctrl-B' : 'composer:show-and-focus-bcc' - 'ctrl-C' : 'composer:show-and-focus-cc' - 'cmd-T' : 'composer:focus-to' - 'ctrl-T' : 'composer:focus-to' - 'cmd-enter' : 'composer:send-message' - 'ctrl-enter' : 'composer:send-message' + 'cmdctrl-T' : 'composer:focus-to' + 'cmdctrl-C' : 'composer:show-and-focus-cc' + 'cmdctrl-B' : 'composer:show-and-focus-bcc' + 'cmdctrl-F' : 'composer:show-and-focus-from' + 'cmdctrl-enter': 'composer:send-message' + 'cmdctrl-z' : 'composer:undo' + 'cmdctrl-Z' : 'composer:redo' + 'cmdctrl-y' : 'composer:redo' '.composer-outer-wrap': - 'delete' : 'composer:no-op' + 'delete': 'composer:no-op' '.composer-outer-wrap, .composer-outer-wrap div[contenteditable]': - 'escape' : 'composer:delete-empty-draft' - -'body.platform-win32 .composer-outer-wrap *[contenteditable], body.platform-win32 .composer-outer-wrap input': - 'ctrl-z': 'composer:undo' - 'ctrl-Z': 'composer:redo' - 'ctrl-y': 'composer:redo' - -'body.platform-linux .composer-outer-wrap *[contenteditable], body.platform-linux .composer-outer-wrap input': - 'ctrl-z': 'composer:undo' - 'ctrl-Z': 'composer:redo' - 'ctrl-y': 'composer:redo' - -'body.platform-darwin .composer-outer-wrap *[contenteditable], body.platform-darwin .composer-outer-wrap input': - 'cmd-z': 'composer:undo' - 'cmd-Z': 'composer:redo' - 'cmd-y': 'composer:redo' + 'escape': 'composer:delete-empty-draft' diff --git a/internal_packages/composer/lib/collapsed-participants.cjsx b/internal_packages/composer/lib/collapsed-participants.cjsx index 8ad0bf4a9..f5358d642 100644 --- a/internal_packages/composer/lib/collapsed-participants.cjsx +++ b/internal_packages/composer/lib/collapsed-participants.cjsx @@ -20,6 +20,7 @@ class CollapsedParticipants extends React.Component bcc: [] constructor: (@props={}) -> + @_keyPrefix = Utils.generateTempId() @state = numToDisplay: 999 numRemaining: 0 @@ -60,15 +61,17 @@ class CollapsedParticipants extends React.Component return
{str}
- _collapsedContact: (contact) -> + _collapsedContact: (contact) => name = contact.displayName() - {name} - _collapsedBccContact: (contact, i) -> + _collapsedBccContact: (contact, i) => name = contact.displayName() + key = @_keyPrefix + contact.email + contact.name if i is 0 then name = "Bcc: #{name}" - {name} _setNumHiddenParticipants: -> diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx index 0940918e9..3a073451b 100644 --- a/internal_packages/composer/lib/composer-view.cjsx +++ b/internal_packages/composer/lib/composer-view.cjsx @@ -17,6 +17,7 @@ React = require 'react' ScrollRegion, Contenteditable, InjectedComponent, + KeyCommandsRegion, FocusTrackingRegion, InjectedComponentSet} = require 'nylas-component-kit' @@ -77,15 +78,6 @@ class ComposerView extends React.Component @_usubs = [] @_usubs.push FileUploadStore.listen @_onFileUploadStoreChange @_usubs.push AccountStore.listen @_onAccountStoreChanged - @_keymapUnlisten = atom.commands.add '.composer-outer-wrap', { - 'composer:send-message': => @_sendDraft() - 'composer:delete-empty-draft': => @_deleteDraftIfEmpty() - 'composer:show-and-focus-bcc': => @_onChangeEnabledFields(show: [Fields.Bcc], focus: Fields.Bcc) - 'composer:show-and-focus-cc': => @_onChangeEnabledFields(show: [Fields.Cc], focus: Fields.Cc) - 'composer:focus-to': => @_onChangeEnabledFields(show: [Fields.To], focus: Fields.To) - "composer:undo": @undo - "composer:redo": @redo - } @_applyFocusedField() componentWillUnmount: => @@ -93,7 +85,6 @@ class ComposerView extends React.Component @_teardownForDraft() @_deleteDraftIfEmpty() usub() for usub in @_usubs - @_keymapUnlisten.dispose() if @_keymapUnlisten componentDidUpdate: => # We want to use a temporary variable instead of putting this into the @@ -105,6 +96,19 @@ class ComposerView extends React.Component @_applyFocusedField() + _keymapHandlers: -> + 'composer:send-message': => @_sendDraft() + 'composer:delete-empty-draft': => @_deleteDraftIfEmpty() + 'composer:show-and-focus-bcc': => + @_onChangeEnabledFields(show: [Fields.Bcc], focus: Fields.Bcc) + 'composer:show-and-focus-cc': => + @_onChangeEnabledFields(show: [Fields.Cc], focus: Fields.Cc) + 'composer:focus-to': => + @_onChangeEnabledFields(show: [Fields.To], focus: Fields.To) + "composer:show-and-focus-from": => # TODO + "composer:undo": @undo + "composer:redo": @redo + _applyFocusedField: -> if @state.focusedField return unless @refs[@state.focusedField] @@ -130,7 +134,7 @@ class ComposerView extends React.Component @undoManager = new UndoManager DraftStore.sessionForClientId(draftClientId).then(@_setupSession) - _setupSession: (proxy) => + __setupSessionsetupSession: (proxy) => return if @_unmounted return unless proxy.draftClientId is @props.draftClientId @_proxy = proxy @@ -149,20 +153,26 @@ class ComposerView extends React.Component if @_proxy @_proxy.changes.commit() - render: => + render: -> + + {@_renderComposerWrap()} + + + _renderComposerWrap: => if @props.mode is "inline" {@_renderComposer()} else -
+
{@_renderComposer()}
_wrapClasses: => - "message-item-white-wrap composer-outer-wrap #{@props.className ? ""}" + "message-item-white-wrap #{@props.className ? ""}" _renderComposer: => - React.findDOMNode(@refs.composer).getBoundingClientRect() + React.findDOMNode(@refs.composerWrap).getBoundingClientRect() _onScrollToBottom: -> if @props.onRequestScrollTo diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx index 4f69da343..4cbb06154 100644 --- a/internal_packages/composer/spec/composer-view-spec.cjsx +++ b/internal_packages/composer/spec/composer-view-spec.cjsx @@ -133,6 +133,9 @@ describe "populated composer", -> @isSending = {state: false} spyOn(DraftStore, "isSendingDraft").andCallFake => @isSending.state + afterEach -> + DraftStore._cleanupAllSessions() + describe "when sending a new message", -> it 'makes a request with the message contents', -> useDraft.call @ @@ -528,20 +531,21 @@ describe "populated composer", -> useFullDraft.apply(@) makeComposer.call(@) NylasTestUtils.loadKeymap("internal_packages/composer/keymaps/composer") + @$composer = @composer.refs.composerWrap it "sends the draft on cmd-enter", -> - NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@composer)) + NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@$composer)) expect(Actions.sendDraft).toHaveBeenCalled() it "does not send the draft on enter if the button isn't in focus", -> - NylasTestUtils.keyPress("enter", React.findDOMNode(@composer)) + NylasTestUtils.keyPress("enter", React.findDOMNode(@$composer)) expect(Actions.sendDraft).not.toHaveBeenCalled() it "doesn't let you send twice", -> - NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@composer)) + NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@$composer)) @isSending.state = true DraftStore.trigger() - NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@composer)) + NylasTestUtils.keyPress("cmd-enter", React.findDOMNode(@$composer)) expect(Actions.sendDraft).toHaveBeenCalled() expect(Actions.sendDraft.calls.length).toBe 1 diff --git a/internal_packages/message-list/lib/message-list.cjsx b/internal_packages/message-list/lib/message-list.cjsx index 840d5e0ab..f5b789662 100755 --- a/internal_packages/message-list/lib/message-list.cjsx +++ b/internal_packages/message-list/lib/message-list.cjsx @@ -10,18 +10,19 @@ MessageItemContainer = require './message-item-container' MessageStore, DatabaseStore, WorkspaceStore, - ComponentRegistry, ChangeLabelsTask, + ComponentRegistry, ChangeStarredTask} = require("nylas-exports") {Spinner, + RetinaImg, + MailLabel, ScrollRegion, ResizableRegion, - RetinaImg, - InjectedComponentSet, - MailLabel, MailImportantIcon, - InjectedComponent} = require('nylas-component-kit') + InjectedComponent, + KeyCommandsRegion, + InjectedComponentSet} = require('nylas-component-kit') class MessageListScrollTooltip extends React.Component @displayName: 'MessageListScrollTooltip' @@ -73,18 +74,8 @@ class MessageList extends React.Component @_unsubscribers = [] @_unsubscribers.push MessageStore.listen @_onChange - commands = _.extend {}, - 'application:reply': => @_createReplyOrUpdateExistingDraft('reply') - 'application:reply-all': => @_createReplyOrUpdateExistingDraft('reply-all') - 'application:forward': => @_onForward() - 'core:messages-page-up': => @_onScrollByPage(-1) - 'core:messages-page-down': => @_onScrollByPage(1) - - @command_unsubscriber = atom.commands.add('body', commands) - componentWillUnmount: => unsubscribe() for unsubscribe in @_unsubscribers - @command_unsubscriber.dispose() shouldComponentUpdate: (nextProps, nextState) => not Utils.isEqualReact(nextProps, @props) or @@ -97,6 +88,13 @@ class MessageList extends React.Component if newDraftClientIds.length > 0 @_focusDraft(@_getMessageContainer(newDraftClientIds[0])) + _keymapHandlers: -> + 'application:reply': => @_createReplyOrUpdateExistingDraft('reply') + 'application:reply-all': => @_createReplyOrUpdateExistingDraft('reply-all') + 'application:forward': => @_onForward() + 'core:messages-page-up': => @_onScrollByPage(-1) + 'core:messages-page-down': => @_onScrollByPage(1) + _newDraftClientIds: (prevState) => oldDraftIds = _.map(_.filter((prevState.messages ? []), (m) -> m.draft), (m) -> m.clientId) newDraftIds = _.map(_.filter((@state.messages ? []), (m) -> m.draft), (m) -> m.clientId) @@ -191,26 +189,28 @@ class MessageList extends React.Component "messages-wrap": true "ready": not @state.loading -
- - {@_renderSubject()} -
- - -
- {@_messageElements()} -
- -
+ +
+ + {@_renderSubject()} +
+ + +
+ {@_messageElements()} +
+ +
+
_renderSubject: -> subject = @state.currentThread?.subject @@ -300,7 +300,7 @@ class MessageList extends React.Component
{bundle.messages.length} older messages
{lines.map (msg, i) -> -
} +
}
diff --git a/internal_packages/preferences/lib/tabs/preferences-keymaps.cjsx b/internal_packages/preferences/lib/tabs/preferences-keymaps.cjsx index c09162731..335cbe2a4 100644 --- a/internal_packages/preferences/lib/tabs/preferences-keymaps.cjsx +++ b/internal_packages/preferences/lib/tabs/preferences-keymaps.cjsx @@ -12,7 +12,7 @@ DisplayedKeybindings = [ ['application:focus-search', 'Search'], ['application:change-category', 'Change Folder / Labels'], ['core:select-item', 'Select Focused Item'], - ['core:star-item', 'Star Focused Item'], + ['application:star-item', 'Star Focused Item'], ] class PreferencesKeymaps extends React.Component diff --git a/internal_packages/search-bar/lib/search-bar.cjsx b/internal_packages/search-bar/lib/search-bar.cjsx index 061f91406..22b426618 100644 --- a/internal_packages/search-bar/lib/search-bar.cjsx +++ b/internal_packages/search-bar/lib/search-bar.cjsx @@ -1,7 +1,7 @@ React = require 'react/addons' classNames = require 'classnames' {Actions, WorkspaceStore} = require 'nylas-exports' -{Menu, RetinaImg} = require 'nylas-component-kit' +{Menu, RetinaImg, KeyCommandsRegion} = require 'nylas-component-kit' SearchSuggestionStore = require './search-suggestion-store' _ = require 'underscore' @@ -20,20 +20,16 @@ class SearchBar extends React.Component @usub.push SearchSuggestionStore.listen @_onChange @usub.push WorkspaceStore.listen => @setState(focused: false) if @state.focused - @body_unsubscriber = atom.commands.add 'body', { - 'application:focus-search': @_onFocusSearch - } - @search_unsubscriber = atom.commands.add '.search-bar', { - 'search-bar:escape-search': @_clearAndBlur - } # It's important that every React class explicitly stops listening to # atom events before it unmounts. Thank you event-kit # This can be fixed via a Reflux mixin componentWillUnmount: => usub() for usub in @usub - @body_unsubscriber.dispose() - @search_unsubscriber.dispose() + + _keymapHandlers: -> + 'application:focus-search': @_onFocusSearch + 'search-bar:escape-search': @_clearAndBlur render: => inputValue = @_queryToString(@state.query) @@ -74,16 +70,18 @@ class SearchBar extends React.Component else item.label -
- item.id ? item.label } - onSelect={@_onSelectSuggestion} - /> -
+ +
+ item.id ? item.label } + onSelect={@_onSelectSuggestion} + /> +
+
_onFocusSearch: => React.findDOMNode(@refs.searchInput).focus() diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx index 3e314527b..d5c59c0d8 100644 --- a/internal_packages/thread-list/lib/thread-list.cjsx +++ b/internal_packages/thread-list/lib/thread-list.cjsx @@ -5,6 +5,7 @@ classNames = require 'classnames' MultiselectList, RetinaImg, MailLabel, + KeyCommandsRegion, InjectedComponentSet} = require 'nylas-component-kit' {timestamp, subject} = require './formatting-utils' {Actions, @@ -166,24 +167,6 @@ class ThreadList extends React.Component @narrowColumns = [cNarrow] - _shift = ({offset, afterRunning}) => - view = ThreadListStore.view() - focusedId = FocusedContentStore.focusedId('thread') - focusedIdx = Math.min(view.count() - 1, Math.max(0, view.indexOfId(focusedId) + offset)) - item = view.get(focusedIdx) - afterRunning() - Actions.setFocus(collection: 'thread', item: item) - - @commands = - 'core:remove-from-view': @_onRemoveFromView - 'core:archive-item': @_onArchiveItem - 'core:delete-item': @_onDeleteItem - 'core:star-item': @_onStarItem - 'core:remove-and-previous': => - _shift(offset: 1, afterRunning: @_onRemoveFromView) - 'core:remove-and-next': => - _shift(offset: -1, afterRunning: @_onRemoveFromView) - @itemPropsProvider = (item) -> className: classNames 'unread': item.unread @@ -196,12 +179,35 @@ class ThreadList extends React.Component componentWillUnmount: => window.removeEventListener('resize', @_onResize, true) - render: => + _shift: ({offset, afterRunning}) => + view = ThreadListStore.view() + focusedId = FocusedContentStore.focusedId('thread') + focusedIdx = Math.min(view.count() - 1, Math.max(0, view.indexOfId(focusedId) + offset)) + item = view.get(focusedIdx) + afterRunning() + Actions.setFocus(collection: 'thread', item: item) + + _keymapHandlers: -> + 'core:remove-from-view': @_onRemoveFromView + 'application:archive-item': @_onArchiveItem + 'application:delete-item': @_onDeleteItem + 'application:star-item': @_onStarItem + 'application:remove-and-previous': => + @_shift(offset: 1, afterRunning: @_onRemoveFromView) + 'application:remove-and-next': => + @_shift(offset: -1, afterRunning: @_onRemoveFromView) + + render: -> + + {@_renderList()} + + + _renderList: => if @state.style is 'wide' return null unless ThreadListStore.view() focused = FocusedContentStore.focused('thread') diff --git a/keymaps/base-darwin.cson b/keymaps/base-darwin.cson index 03d051484..fc455f7b8 100644 --- a/keymaps/base-darwin.cson +++ b/keymaps/base-darwin.cson @@ -1,39 +1,13 @@ -# Note: For a menu item to have a keyboard equiavalent, it needs -# to be listed in this file. +# Note: Only put keymaps in here that apply to Mac ONLY +# +# Most cross-platform issues can be resolved by using the special N1 +# `cmdctrl` extension before you keymap. This will automatically default +# to using the `cmd` key on Mac and `ctrl` key on Windows. 'body': - # Mac application keys - 'cmd-q': 'application:quit' + 'cmd-m': 'application:minimize' 'cmd-h': 'application:hide' 'cmd-1': 'application:show-main-window' - 'cmd-m': 'application:minimize' - 'cmd-alt-w': 'application:show-work-window' 'cmd-alt-h': 'application:hide-other-applications' - 'alt-cmd-ctrl-m': 'application:zoom' - 'cmd-alt-ctrl-s': 'application:run-all-specs' - - # Mac core keys - 'cmd-z': 'core:undo' - 'cmd-Z': 'core:redo' - 'cmd-y': 'core:redo' - 'cmd-x': 'core:cut' - 'cmd-c': 'core:copy' - 'cmd-a': 'core:select-all' - 'cmd-v': 'core:paste' - - # Mac window keys - 'cmd-w': 'window:close' - 'cmd-=': 'window:increase-font-size' - 'cmd-+': 'window:increase-font-size' - 'cmd--': 'window:decrease-font-size' - 'cmd-_': 'window:decrease-font-size' - 'cmd-0': 'window:reset-font-size' - 'alt-cmd-i': 'window:toggle-dev-tools' 'cmd-ctrl-f': 'window:toggle-full-screen' - 'ctrl-alt-cmd-l': 'window:reload' - 'cmd-alt-ctrl-p': 'application:run-package-specs' - -'body *[contenteditable]': - 'cmd-z': 'native!' - 'cmd-Z': 'native!' - 'cmd-y': 'native!' + 'alt-cmd-ctrl-m': 'application:zoom' diff --git a/keymaps/base-linux.cson b/keymaps/base-linux.cson index 7df44eae7..9ac3c018b 100644 --- a/keymaps/base-linux.cson +++ b/keymaps/base-linux.cson @@ -1,36 +1,11 @@ -# Note: For a menu item to have a keyboard equiavalent, it needs -# to be listed in this file. +# Note: Only put keymaps in here that apply to Linux ONLY +# +# Most cross-platform issues can be resolved by using the special N1 +# `cmdctrl` extension before you keymap. This will automatically default +# to using the `cmd` key on Mac and `ctrl` key on Windows. # Linux email-specific menu items 'body': - # Linux application keys - 'ctrl-q': 'application:quit' - - # Linux core keys - 'ctrl-z': 'core:undo' - 'ctrl-Z': 'core:redo' - 'ctrl-y': 'core:redo' - 'ctrl-x': 'core:cut' - 'ctrl-c': 'core:copy' - 'ctrl-a': 'core:select-all' - 'ctrl-v': 'core:paste' 'ctrl-insert': 'core:copy' 'shift-insert': 'core:paste' - - # Linux window keys - 'ctrl-w': 'core:close' - 'ctrl-=': 'window:increase-font-size' - 'ctrl-+': 'window:increase-font-size' - 'ctrl--': 'window:decrease-font-size' - 'ctrl-_': 'window:decrease-font-size' - 'ctrl-0': 'window:reset-font-size' - 'ctrl-alt-r': 'window:reload' - 'ctrl-shift-i': 'window:toggle-dev-tools' - 'ctrl-alt-p': 'application:run-package-specs' - 'ctrl-alt-s': 'application:run-all-specs' 'F11': 'window:toggle-full-screen' - -'body *[contenteditable]': - 'ctrl-z': 'native!' - 'ctrl-Z': 'native!' - 'ctrl-y': 'native!' diff --git a/keymaps/base-win32.cson b/keymaps/base-win32.cson index fd602681a..c7773b122 100644 --- a/keymaps/base-win32.cson +++ b/keymaps/base-win32.cson @@ -1,43 +1,11 @@ -# Note: For a menu item to have a keyboard equiavalent, it needs -# to be listed in this file. +# Note: Only put keymaps in here that apply to Windows ONLY +# +# Most cross-platform issues can be resolved by using the special N1 +# `cmdctrl` extension before you keymap. This will automatically default +# to using the `cmd` key on Mac and `ctrl` key on Windows. 'body': - # Windows email-specific menu items - 'ctrl-n': 'application:new-message' # Outlook - 'ctrl-r': 'application:reply' # Outlook - 'ctrl-R': 'application:reply-all' # Outlook - 'ctrl-F': 'application:forward' # Outlook - 'ctrl-shift-v': 'application:change-category' # Outlook - - # Windows application keys - 'ctrl-q': 'application:quit' - 'ctrl-alt-s': 'application:run-all-specs' - -# Windows core keys - 'ctrl-z': 'core:undo' - 'ctrl-Z': 'core:redo' - 'ctrl-y': 'core:redo' - 'ctrl-x': 'core:cut' - 'ctrl-c': 'core:copy' - 'ctrl-a': 'core:select-all' - 'ctrl-v': 'core:paste' - 'ctrl-insert': 'core:copy' - 'shift-insert': 'core:paste' - -# Windows window keys - 'ctrl-w': 'window:close' - 'ctrl-=': 'window:increase-font-size' - 'ctrl-+': 'window:increase-font-size' - 'ctrl--': 'window:decrease-font-size' - 'ctrl-_': 'window:decrease-font-size' - 'ctrl-0': 'window:reset-font-size' - 'ctrl-alt-r': 'window:reload' - 'ctrl-alt-i': 'window:toggle-dev-tools' - 'ctrl-alt-p': 'application:run-package-specs' - 'ctrl-alt-s': 'application:run-all-specs' + 'cmd-alt-l': 'window:reload' + 'cmd-alt-i': 'window:toggle-dev-tools' + 'cmd-alt-w': 'application:show-work-window' 'F11': 'window:toggle-full-screen' - -'body *[contenteditable]': - 'ctrl-z': 'native!' - 'ctrl-Z': 'native!' - 'ctrl-y': 'native!' diff --git a/keymaps/base.cson b/keymaps/base.cson index aa96ad687..c8d4498c7 100644 --- a/keymaps/base.cson +++ b/keymaps/base.cson @@ -1,202 +1,97 @@ -# Email-specific core key-mappings +# This is the core set of universal, cross-platform keymaps. This is +# extended in the following places: # -# There are additional mappings in .cson files that bind -# menu items. In the future, we should break these into files like: -# darwin-gmail.cson, darwin-macmail.cson, win32-gmail.cson... +# 1. keymaps/base.cson - (This file) Core, universal keymaps across all platforms +# 2. keymaps/base-darwin.cson - Any universal mac-only keymaps +# 3. keymaps/base-win32.cson - Any universal windows-only keymaps +# 4. keymaps/base-darwin.cson - Any universal linux-only keymaps +# 5. keymaps/templates/Gmail.cson - Gmail key bindings for all platforms +# 6. keymaps/templates/Outlook.cson - Outlook key bindings for all platforms +# 7. keymaps/templates/Apple Mail.cson - Mac Mail key bindings for all platforms +# 8. some/package/keymaps/package.cson - Keymaps for a specific package +# 9. ~/.nylas/keymap.cson - Custom user-specific overrides +# +# NOTE: We have a special N1 extension called `cmdctrl` that automatically +# uses `cmd` on mac and `ctrl` on windows and linux. This covers most +# cross-platform cases. For truely platform-specific features, use the +# platform keymap extensions. 'body': - 'escape' : 'application:pop-sheet' - 'cmd-,' : 'application:open-preferences' - 'up' : 'core:previous-item' - 'down' : 'core:next-item' - 'enter' : 'core:focus-item' - 'delete' : 'core:remove-from-view' - 'backspace': 'core:remove-from-view' - - 'pageup' : 'core:messages-page-up' - 'pagedown' : 'core:messages-page-down' - 'shift-pageup' : 'core:list-page-up' - 'shift-pagedown' : 'core:list-page-down' - - # Default cross-platform core behaviors - 'left': 'core:move-left' - 'right': 'core:move-right' - 'shift-up': 'core:select-up' - 'shift-down': 'core:select-down' - 'shift-left': 'core:select-left' - 'shift-right': 'core:select-right' + ### Core system commands. ### + # These have their default effects, but map to + # commands to allow for custom interactions. + 'cmdctrl-z': 'core:undo' + 'cmdctrl-Z': 'core:redo' + 'cmdctrl-y': 'core:redo' + 'cmdctrl-x': 'core:cut' + 'cmdctrl-c': 'core:copy' + 'cmdctrl-v': 'core:paste' + 'cmdctrl-a': 'core:select-all' 'shift-delete': 'core:cut' -# Inputs are native by default. -# Also make sure not to catch anything intended for a webview -'body input, body textarea, body *[contenteditable], body webview': - 'up': 'native!' - 'left': 'native!' - 'down': 'native!' - 'right': 'native!' - 'cmd-up': 'native!' - 'cmd-left': 'native!' - 'cmd-down': 'native!' - 'cmd-right': 'native!' - 'ctrl-up': 'native!' - 'ctrl-left': 'native!' - 'ctrl-down': 'native!' - 'ctrl-right': 'native!' - 'shift-up': 'native!' - 'shift-left': 'native!' - 'shift-down': 'native!' - 'shift-right': 'native!' - 'escape': 'native!' - 'pageup': 'native!' - 'pagedown': 'native!' - 'shift-pageup': 'native!' - 'shift-pagedown': 'native!' - 'enter': 'native!' - 'cmd-enter': 'native!' - 'ctrl-enter': 'native!' - 'shift-enter': 'native!' - 'backspace': 'native!' - 'shift-backspace': 'native!' - 'delete': 'native!' - 'shift-delete': 'native!' - 'cmd-y': 'native!' - 'cmd-z': 'native!' - 'cmd-Z': 'native!' - 'cmd-x': 'native!' - 'cmd-X': 'native!' - 'cmd-c': 'native!' - 'cmd-C': 'native!' - 'cmd-v': 'native!' - 'cmd-V': 'native!' - 'cmd-a': 'native!' - 'cmd-A': 'native!' - 'cmd-b': 'native!' - 'cmd-i': 'native!' - 'cmd-u': 'native!' - 'ctrl-y': 'native!' - 'ctrl-z': 'native!' - 'ctrl-Z': 'native!' - 'ctrl-x': 'native!' - 'ctrl-X': 'native!' - 'ctrl-c': 'native!' - 'ctrl-C': 'native!' - 'ctrl-v': 'native!' - 'ctrl-V': 'native!' - 'ctrl-a': 'native!' - 'ctrl-A': 'native!' - 'ctrl-b': 'native!' - 'ctrl-i': 'native!' - 'ctrl-u': 'native!' - 'a': 'native!' - 'b': 'native!' - 'c': 'native!' - 'd': 'native!' - 'e': 'native!' - 'f': 'native!' - 'g': 'native!' - 'h': 'native!' - 'i': 'native!' - 'j': 'native!' - 'k': 'native!' - 'l': 'native!' - 'm': 'native!' - 'n': 'native!' - 'o': 'native!' - 'p': 'native!' - 'q': 'native!' - 'r': 'native!' - 's': 'native!' - 't': 'native!' - 'u': 'native!' - 'v': 'native!' - 'w': 'native!' - 'x': 'native!' - 'y': 'native!' - 'z': 'native!' - 'A': 'native!' - 'B': 'native!' - 'C': 'native!' - 'D': 'native!' - 'E': 'native!' - 'F': 'native!' - 'G': 'native!' - 'H': 'native!' - 'I': 'native!' - 'J': 'native!' - 'K': 'native!' - 'L': 'native!' - 'M': 'native!' - 'N': 'native!' - 'O': 'native!' - 'P': 'native!' - 'Q': 'native!' - 'R': 'native!' - 'S': 'native!' - 'T': 'native!' - 'U': 'native!' - 'V': 'native!' - 'W': 'native!' - 'X': 'native!' - 'Y': 'native!' - 'Z': 'native!' - '1': 'native!' - '2': 'native!' - '3': 'native!' - '4': 'native!' - '5': 'native!' - '6': 'native!' - '7': 'native!' - '8': 'native!' - '9': 'native!' - '0': 'native!' - '~': 'native!' - '!': 'native!' - '@': 'native!' - '#': 'native!' - '$': 'native!' - '%': 'native!' - '^': 'native!' - '&': 'native!' - '*': 'native!' - '(': 'native!' - ')': 'native!' - '-': 'native!' - '_': 'native!' - '=': 'native!' - '+': 'native!' - '[': 'native!' - '{': 'native!' - ']': 'native!' - '}': 'native!' - '\\': 'native!' - '|': 'native!' - ';': 'native!' - ':': 'native!' - '\'': 'native!' - '"': 'native!' - '<': 'native!' - ',': 'native!' - '>': 'native!' - '.': 'native!' - '?': 'native!' - '/': 'native!' + 'up' : 'core:previous-item' + 'down' : 'core:next-item' + 'left' : 'core:move-left' + 'right' : 'core:move-right' + 'shift-up' : 'core:select-up' + 'shift-down' : 'core:select-down' + 'shift-left' : 'core:select-left' + 'shift-right': 'core:select-right' -'body input, body textarea': - 'tab': 'core:focus-next' - 'shift-tab': 'core:focus-previous' + ### Core application commands. ### + 'cmdctrl-q' : 'application:quit' + 'cmdctrl-w' : 'window:close' -'body webview': - 'tab': 'native!' - 'shift-tab': 'native!' + ### Universal N1 commands. ### + 'enter' : 'core:focus-item' + 'delete' : 'core:remove-from-view' + 'escape' : 'application:pop-sheet' + 'backspace': 'core:remove-from-view' + 'cmdctrl-,': 'application:open-preferences' -# So our contenteditable control can do its own thing -'body *[contenteditable]': - 'tab': 'native!' - 'shift-tab': 'native!' + 'pageup' : 'core:messages-page-up' + 'pagedown' : 'core:messages-page-down' + 'shift-pageup' : 'core:list-page-up' + 'shift-pagedown': 'core:list-page-down' -# For menus -'body .menu, body .menu, body .menu input': - # and by "native!" I actually mean for it to just let React deal with - # it. - 'tab': 'native!' - 'shift-tab': 'native!' + ### N1 developer commands. ### + 'cmdctrl-alt-l': 'window:reload' + 'cmdctrl-alt-i': 'window:toggle-dev-tools' + 'cmdctrl-alt-w': 'application:show-work-window' + 'cmdctrl-alt-s': 'application:run-all-specs' + 'cmdctrl-alt-p': 'application:run-package-specs' + +'body *[contenteditable].contenteditable': + ### Basic formatting commands ### + 'cmdctrl-u': 'contenteditable:underline' + 'cmdctrl-b': 'contenteditable:bold' + 'cmdctrl-i': 'contenteditable:italic' + 'cmdctrl-k': 'contenteditable:insert-link' + + ### Advanced formatting commands ### + 'cmdctrl-&': 'contenteditable:numbered-list' + 'cmdctrl-#': 'contenteditable:numbered-list' + 'cmdctrl-*': 'contenteditable:bulleted-list' + 'cmdctrl-(': 'contenteditable:quote' + + 'cmdctrl-[': 'contenteditable:outdent' + 'cmdctrl-]': 'contenteditable:indent' + + 'cmdctrl-L': 'contenteditable:align-left' + 'cmdctrl-E': 'contenteditable:align-center' + 'cmdctrl-R': 'contenteditable:align-right' + + 'cmdctrl-,': 'contenteditable:set-right-to-left' + 'cmdctrl-.': 'contenteditable:set-left-to-right' + + 'cmdctrl-\\': 'contenteditable:remove-formatting' + + 'cmdctrl-%': 'contenteditable:previous-font' + 'cmdctrl-^': 'contenteditable:next-font' + 'cmdctrl-+': 'contenteditable:increase-text-size' + 'cmdctrl--': 'contenteditable:decrease-text-size' + + ### Custom Property Navigating ### + 'cmdctrl-;': 'contenteditable:previous-selection' + "cmdctrl-'": 'contenteditable:next-selection' + 'cmdctrl-m': 'contenteditable:open-spelling-suggestions' diff --git a/keymaps/input-reset.cson b/keymaps/input-reset.cson new file mode 100644 index 000000000..d675424d2 --- /dev/null +++ b/keymaps/input-reset.cson @@ -0,0 +1,202 @@ +# We need to explicitly ensure all commands sent to inputs are handled as +# native events. +# +# When users type a key in an input, that event bubbles up. (We +# intentionally don't `stopPropagation` from inputs to allow for cases +# where you do want to catch key events.) +# +# When that keypress bubbles up to the root level, we may capture it +# thinking it's a hot key. While we could attach the special +# `.native-key-bindings` class to the input, we can't guarantee that this +# will be upheld. Furthermore, in some cases we may want to actually +# capture the input. +# +# Once captured, the event's default will be prevented. +# +# We give a higher CSS specificity to the inputs, textareas, and +# contentedtiables to ensure that the `native!` behavior takes precedent. +'body input, body textarea, body *[contenteditable], body webview': + 'up': 'native!' + 'left': 'native!' + 'down': 'native!' + 'right': 'native!' + 'cmd-up': 'native!' + 'cmd-left': 'native!' + 'cmd-down': 'native!' + 'cmd-right': 'native!' + 'ctrl-up': 'native!' + 'ctrl-left': 'native!' + 'ctrl-down': 'native!' + 'ctrl-right': 'native!' + 'shift-up': 'native!' + 'shift-left': 'native!' + 'shift-down': 'native!' + 'shift-right': 'native!' + 'escape': 'native!' + 'pageup': 'native!' + 'pagedown': 'native!' + 'shift-pageup': 'native!' + 'shift-pagedown': 'native!' + 'enter': 'native!' + 'cmd-enter': 'native!' + 'ctrl-enter': 'native!' + 'shift-enter': 'native!' + 'backspace': 'native!' + 'shift-backspace': 'native!' + 'delete': 'native!' + 'shift-delete': 'native!' + 'cmd-y': 'native!' + 'cmd-z': 'native!' + 'cmd-Z': 'native!' + 'cmd-x': 'native!' + 'cmd-X': 'native!' + 'cmd-c': 'native!' + 'cmd-C': 'native!' + 'cmd-v': 'native!' + 'cmd-V': 'native!' + 'cmd-a': 'native!' + 'cmd-A': 'native!' + 'cmd-b': 'native!' + 'cmd-i': 'native!' + 'cmd-u': 'native!' + 'ctrl-y': 'native!' + 'ctrl-z': 'native!' + 'ctrl-Z': 'native!' + 'ctrl-x': 'native!' + 'ctrl-X': 'native!' + 'ctrl-c': 'native!' + 'ctrl-C': 'native!' + 'ctrl-v': 'native!' + 'ctrl-V': 'native!' + 'ctrl-a': 'native!' + 'ctrl-A': 'native!' + 'ctrl-b': 'native!' + 'ctrl-i': 'native!' + 'ctrl-u': 'native!' + 'a': 'native!' + 'b': 'native!' + 'c': 'native!' + 'd': 'native!' + 'e': 'native!' + 'f': 'native!' + 'g': 'native!' + 'h': 'native!' + 'i': 'native!' + 'j': 'native!' + 'k': 'native!' + 'l': 'native!' + 'm': 'native!' + 'n': 'native!' + 'o': 'native!' + 'p': 'native!' + 'q': 'native!' + 'r': 'native!' + 's': 'native!' + 't': 'native!' + 'u': 'native!' + 'v': 'native!' + 'w': 'native!' + 'x': 'native!' + 'y': 'native!' + 'z': 'native!' + 'A': 'native!' + 'B': 'native!' + 'C': 'native!' + 'D': 'native!' + 'E': 'native!' + 'F': 'native!' + 'G': 'native!' + 'H': 'native!' + 'I': 'native!' + 'J': 'native!' + 'K': 'native!' + 'L': 'native!' + 'M': 'native!' + 'N': 'native!' + 'O': 'native!' + 'P': 'native!' + 'Q': 'native!' + 'R': 'native!' + 'S': 'native!' + 'T': 'native!' + 'U': 'native!' + 'V': 'native!' + 'W': 'native!' + 'X': 'native!' + 'Y': 'native!' + 'Z': 'native!' + '1': 'native!' + '2': 'native!' + '3': 'native!' + '4': 'native!' + '5': 'native!' + '6': 'native!' + '7': 'native!' + '8': 'native!' + '9': 'native!' + '0': 'native!' + '~': 'native!' + '`': 'native!' + '!': 'native!' + '@': 'native!' + '#': 'native!' + '$': 'native!' + '%': 'native!' + '^': 'native!' + '&': 'native!' + '*': 'native!' + '(': 'native!' + ')': 'native!' + '-': 'native!' + '_': 'native!' + '=': 'native!' + '+': 'native!' + '[': 'native!' + '{': 'native!' + ']': 'native!' + '}': 'native!' + '\\': 'native!' + '|': 'native!' + ';': 'native!' + ':': 'native!' + '\'': 'native!' + '"': 'native!' + '<': 'native!' + ',': 'native!' + '>': 'native!' + '.': 'native!' + '?': 'native!' + '/': 'native!' + 'g i': 'native!' + 'g s': 'native!' + 'g t': 'native!' + 'g d': 'native!' + 'g a': 'native!' + 'g c': 'native!' + 'g k': 'native!' + 'g l': 'native!' + '* a': 'native!' + '* n': 'native!' + '* r': 'native!' + '* u': 'native!' + '* s': 'native!' + '* t': 'native!' + +# Tabs are a bit different because simple elements (like text inputs) we +# want to use our custom `core:focus-next`. Other more complex ones, like +# `contenteditable`, we want to have a more controlled effect over. +'body input, body textarea': + 'tab': 'core:focus-next' + 'shift-tab': 'core:focus-previous' + +# So our contenteditable control can do its own thing +'body webview, body *[contenteditable]': + 'tab': 'native!' + 'shift-tab': 'native!' + +# For menus +'body .menu, body .menu, body .menu input': + # and by "native!" I actually mean for it to just let React deal with + # it. + 'tab': 'native!' + 'shift-tab': 'native!' diff --git a/keymaps/templates/Apple Mail.cson b/keymaps/templates/Apple Mail.cson index 85853b9ac..cab50d727 100644 --- a/keymaps/templates/Apple Mail.cson +++ b/keymaps/templates/Apple Mail.cson @@ -1,22 +1,12 @@ # Note: For a menu item to have a keyboard equiavalent, it needs # to be listed in this file. -'body.platform-darwin': - 'cmd-n' : 'application:new-message' - 'cmd-r' : 'application:reply' - 'cmd-R' : 'application:reply-all' - 'cmd-F' : 'application:forward' - 'cmd-alt-f': 'application:focus-search' - 'cmd-D': 'application:send-message' - 'cmd-V': 'application:change-category' - 'cmd-e' : 'core:archive-item' - -'body.platform-linux, body.platform-win32': - 'ctrl-n' : 'application:new-message' - 'ctrl-r' : 'application:reply' - 'ctrl-R' : 'application:reply-all' - 'ctrl-F' : 'application:forward' - 'ctrl-alt-f': 'application:focus-search' - 'ctrl-D': 'application:send-message' - 'ctrl-shift-v': 'application:change-category' - 'ctrl-e' : 'core:archive-item' +'body': + 'cmdctrl-n' : 'application:new-message' + 'cmdctrl-r' : 'application:reply' + 'cmdctrl-R' : 'application:reply-all' + 'cmdctrl-F' : 'application:forward' + 'cmdctrl-alt-f': 'application:focus-search' + 'cmdctrl-D': 'application:send-message' + 'cmdctrl-V': 'application:change-category' + 'cmdctrl-e' : 'application:archive-item' diff --git a/keymaps/templates/Gmail.cson b/keymaps/templates/Gmail.cson index 73e99613a..167bfec11 100644 --- a/keymaps/templates/Gmail.cson +++ b/keymaps/templates/Gmail.cson @@ -1,38 +1,83 @@ -# Email-specific core key-mappings -# -# There are additional mappings in .cson files that bind -# menu items. In the future, we should break these into files like: -# darwin-gmail.cson, darwin-macmail.cson, win32-gmail.cson... +# Gmail-specific core key-mappings 'body': - 'c' : 'application:new-message' - '/' : 'application:focus-search' - 'r' : 'application:reply' - 'a' : 'application:reply-all' - 'f' : 'application:forward' - 'l' : 'application:change-category' - 'u' : 'application:pop-sheet' + ### Jumping ### + 'g i' : 'navigation:go-to-inbox' + 'g s' : 'navigation:go-to-starred' + 'g t' : 'navigation:go-to-sent' + 'g d' : 'navigation:go-to-drafts' + 'g a' : 'navigation:go-to-all' + 'g c' : 'navigation:go-to-contacts' + 'g k' : 'navigation:go-to-tasks' + 'g l' : 'navigation:go-to-label' - 'k' : 'core:previous-item' - 'j' : 'core:next-item' - ']' : 'core:remove-and-previous' - '[' : 'core:remove-and-next' - '#' : 'core:delete-item' - 'e' : 'core:archive-item' - 's' : 'core:star-item' - 'x' : 'core:select-item' + ### Threadlist selection ### + '* a': 'thread-list:select-all' + '* n': 'thread-list:deselect-all' + '* r': 'thread-list:select-read' + '* u': 'thread-list:select-unread' + '* s': 'thread-list:select-starred' + '* t': 'thread-list:select-unstarred' -# Gmail also includes some more basic ones that users expect from desktop software. + ### Navigation ### + 'u': 'application:pop-sheet' + 'k': 'core:previous-item' + 'j': 'core:next-item' + 'o': 'core:focus-item' + 'p': 'message-list:previous-message' + 'n': 'message-list:next-message' + '`': 'thread-list:next-inbox' + '~': 'thread-list:previous-inbox' -'body.platform-darwin': - 'cmd-n' : 'application:new-message' - 'cmd-r' : 'application:reply' - 'cmd-R' : 'application:reply-all' - 'cmd-F' : 'application:forward' + ### Application ### + 'c': 'application:new-message' + 'd': 'application:new-message' + '/': 'application:focus-search' + '.': 'application:more-actions' + 'l': 'application:change-category' + 'v': 'application:change-category' + '?': 'application:open-help' -'body.platform-linux, body.platform-win32': - 'ctrl-n' : 'application:new-message' - 'ctrl-r' : 'application:reply' - 'ctrl-R' : 'application:reply-all' - 'ctrl-F' : 'application:forward' + ### Actions ### + ',': 'application:focus-toolbar' + 'x': 'core:select-item' + 's': 'application:star-item' + 'y': 'core:remove-from-view' # Remove label + 'e': 'application:archive-item' + 'm': 'application:mute-conversation' + '!': 'application:report-as-spam' + '#': 'application:delete-item' + + 'r': 'application:reply' + 'R': 'application:reply-new-window' + 'a': 'application:reply-all' + 'A': 'application:reply-all-new-window' + 'f': 'application:forward' + 'F': 'application:forward-new-window' + + 'N': 'application:update-conversation' + + ']': 'application:remove-and-previous' + '}': 'application:remove-and-previous' + '[': 'application:remove-and-next' + '{': 'application:remove-and-next' + + 'z': 'core:undo' + + 'I': 'application:mark-as-read' + 'U': 'application:mark-as-unread' + '_': 'application:mark-as-unread' # Mark unread from the selected message + '+': 'application:mark-important' + '=': 'application:mark-important' + '-': 'application:mark-unimportant' + + ';': 'message-list:expand-all' + ':': 'message-list:collapse-all' + + # While not stock Gmail, we add the following standard keymaps that + # desktop application users would expect: + 'cmdctrl-n' : 'application:new-message' + 'cmdctrl-r' : 'application:reply' + 'cmdctrl-R' : 'application:reply-all' + 'cmdctrl-F' : 'application:forward' diff --git a/keymaps/templates/Outlook.cson b/keymaps/templates/Outlook.cson index 8047a77fe..ce6d4c409 100644 --- a/keymaps/templates/Outlook.cson +++ b/keymaps/templates/Outlook.cson @@ -1,34 +1,18 @@ # Note: For a menu item to have a keyboard equiavalent, it needs # to be listed in this file. -'body.platform-darwin': +'body': # Windows email-specific menu items - 'cmd-shift-v': 'application:change-category' # Outlook + 'cmdctrl-shift-v': 'application:change-category' # Outlook 'F3': 'application:focus-search' - 'cmd-e': 'application:focus-search' - 'cmd-f': 'application:forward' - 'cmd-shift-v': 'application:change-category' - 'cmd-d': 'core:delete-item' + 'cmdctrl-e': 'application:focus-search' + 'cmdctrl-f': 'application:forward' + 'cmdctrl-shift-v': 'application:change-category' + 'cmdctrl-d': 'application:delete-item' 'alt-backspace':'core:undo' 'alt-s': 'application:send-message' - 'cmd-r': 'application:reply' - 'cmd-shift-r': 'application:reply-all' - 'cmd-n' : 'application:new-message' - 'cmd-shift-m': 'application:new-message' - 'cmd-enter': 'send' - -'body.platform-linux, body.platform-win32': - # Windows email-specific menu items - 'ctrl-shift-v': 'application:change-category' # Outlook - 'F3': 'application:focus-search' - 'ctrl-e': 'application:focus-search' - 'ctrl-f': 'application:forward' - 'ctrl-shift-v': 'application:change-category' - 'ctrl-d': 'core:delete-item' - 'alt-backspace':'core:undo' - 'alt-s': 'application:send-message' - 'ctrl-r': 'application:reply' - 'ctrl-shift-r': 'application:reply-all' - 'ctrl-n' : 'application:new-message' - 'ctrl-shift-m': 'application:new-message' - 'ctrl-enter': 'send' + 'cmdctrl-r': 'application:reply' + 'cmdctrl-shift-r': 'application:reply-all' + 'cmdctrl-n' : 'application:new-message' + 'cmdctrl-shift-m': 'application:new-message' + 'cmdctrl-enter': 'send' diff --git a/package.json b/package.json index 1f7833a3c..7420b3ab6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "asar": "^0.5.0", "async": "^0.9", - "atom-keymap": "^5.1", + "atom-keymap": "6.1.0", "babel-core": "^6.0.20", "babel-preset-es2015": "^6.0.15", "babel-preset-react": "^6.0.15", diff --git a/spec/test_utils.coffee b/spec/nylas-test-utils.coffee similarity index 67% rename from spec/test_utils.coffee rename to spec/nylas-test-utils.coffee index 77cd54abe..f56e3b401 100644 --- a/spec/test_utils.coffee +++ b/spec/nylas-test-utils.coffee @@ -6,16 +6,14 @@ NylasTestUtils = loadKeymap: (keymapPath) -> {resourcePath} = atom.getLoadSettings() basePath = CSON.resolve("#{resourcePath}/keymaps/base") - baseKeymaps = CSON.readFileSync(basePath) - atom.keymaps.add(basePath, baseKeymaps) + atom.keymaps.loadKeymap(basePath) if keymapPath? keymapPath = CSON.resolve("#{resourcePath}/#{keymapPath}") - keymapFile = CSON.readFileSync(keymapPath) - atom.keymaps.add(keymapPath, keymapFile) + atom.keymaps.loadKeymap(keymapPath) keyPress: (key, target) -> event = KeymapManager.buildKeydownEvent(key, target: target) - document.dispatchEvent(event) + atom.keymaps.handleKeyboardEvent(event) module.exports = NylasTestUtils diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 1d3fa8176..9922f3f48 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -9,7 +9,7 @@ _ = require 'underscore' _str = require 'underscore.string' fs = require 'fs-plus' Grim = require 'grim' -KeymapManager = require '../src/keymap-extensions' +KeymapManager = require '../src/keymap-manager' # FIXME: Remove jquery from this {$} = require '../src/space-pen-extensions' diff --git a/src/atom.coffee b/src/atom.coffee index be1954f83..0044946dc 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -151,7 +151,7 @@ class Atom extends Model @loadTime = null Config = require './config' - KeymapManager = require './keymap-extensions' + KeymapManager = require './keymap-manager' CommandRegistry = require './command-registry' PackageManager = require './package-manager' Clipboard = require './clipboard' @@ -183,11 +183,11 @@ class Atom extends Model @config = new Config({configDirPath, resourcePath}) @keymaps = new KeymapManager({configDirPath, resourcePath}) - @keymaps.subscribeToFileReadFailure() @keymaps.onDidMatchBinding (event) -> - # If the user fired a command with the application: prefix bound to the body, re-fire it - # up into the browser process. This prevents us from needing this crap, which has to be - # updated every time a new application: command is added: + # If the user fired a command with the application: prefix bound to + # the body, re-fire it up into the browser process. This prevents us + # from needing this crap, which has to be updated every time a new + # application: command is added: # https://github.com/atom/atom/blob/master/src/workspace-element.coffee#L119 if event.binding.command.indexOf('application:') is 0 and event.binding.selector.indexOf("body") is 0 ipc.send('command', event.binding.command) diff --git a/src/components/key-commands-region.cjsx b/src/components/key-commands-region.cjsx new file mode 100644 index 000000000..684150ebf --- /dev/null +++ b/src/components/key-commands-region.cjsx @@ -0,0 +1,137 @@ +_ = require 'underscore' +React = require 'react' + +### +Public: Easily respond to keyboard shortcuts + +A keyboard shortcut has two parts to it: + +1. A mapping between keyboard actions and a command +2. A mapping between a command and a callback handler + + +## Mapping keys to commands (not handled by this component) + +The **keyboard -> command** mapping is defined in a separate `.cson` file. +A majority of the commands your component would want to listen to you have +already been defined by core N1 defaults, as well as custom user +overrides. See 'keymaps/base.cson' for more information. + +You can define additional, custom keyboard -> command mappings in your own +package-specific keymap `.cson` file. The file can be named anything but +must exist in a folder called `keymaps` in the root of your package's +directory. + + +## Mapping commands to callbacks (handled by this component) + +When a keystroke sequence matches a binding in a given context, a custom +DOM event with a type based on the command is dispatched on the target of +the keyboard event. + +That custom DOM event (whose type is the command you want to listen to) +will propagate up from its original target. That original target may or +may not be a descendent of your component. + +Frequently components will want to listen to a keyboard command regardless +of where it was fired from. For those, use the `globalHandlers` prop. The +DOM event will NOT be passed to `globalHandlers` callbacks. + +Components may also want to listen to keyboard commands that originate +within one of their descendents. For those use the `localHandlers` prop. +The DOM event WILL be passed to `localHandlers` callback because it is +sometimes valuable to call `stopPropagataion` on the custom command event. + +Props: + +- `localHandlers` A mapping between key commands and callbacks for key command events that originate within a descendent of this component. +- `globalHandlers` A mapping between key commands and callbacks for key +commands that originate from anywhere and are global in scope. +- `className` The unique class name that shows up in your keymap.cson + +Example: + +In `my-package/lib/my-component.cjsx`: + +```coffee +class MyComponent extends React.Component + render: -> + +
... sweet component ...
+
+ + globalHandlers: -> + "core:moveDown": @onMoveDown + "core:selectItem": @onSelectItem + + localHandlers: -> + "custom:send": (event) => @onSelectItem(); event.stopPropagation() + "custom:move": @onCustomMove +``` + +In `my-package/keymaps/my-package.cson`: + +```coffee +".my-component": + "cmd-t": "selectItem" + "cmd-enter": "sendMessage" +``` + +### +class KeyCommandsRegion extends React.Component + @displayName: "KeyCommandsRegion" + + @propTypes: + className: React.PropTypes.string + localHandlers: React.PropTypes.object + globalHandlers: React.PropTypes.object + + @defaultProps: + className: "" + localHandlers: {} + globalHandlers: {} + + componentWillReceiveProps: (newProps) -> + @_unmountListeners() + @_setupListeners(newProps) + + componentDidMount: -> + @_mounted = true + @_setupListeners(@props) + + componentWillUnmount: -> + @_unmountListeners() + + # When the {KeymapManager} finds a valid keymap in a `.cson` file, it + # will create a CustomEvent with the command name as its type. That + # custom event will be fired at the originating target and propogate + # updwards until it reaches the root window level. + # + # An event is scoped in the `.cson` files. Since we use that to + # determine which keymappings can fire a particular command in a + # particular scope, we simply need to listen at the root window level + # here for all commands coming in. + _setupListeners: (props) -> + _.each props.globalHandlers, (callback, handler) -> + window.addEventListener(handler, callback) + + return unless @_mounted + $el = React.findDOMNode(@) + _.each props.localHandlers, (callback, handler) -> + $el.addEventListener(handler, callback) + + _unmountListeners: -> + _.each @props.globalHandlers, (callback, handler) -> + window.removeEventListener(handler, callback) + + return unless @_mounted + $el = React.findDOMNode(@) + _.each @props.localHandlers, (callback, handler) -> + $el.removeEventListener(handler, callback) + + render: -> +
+ {@props.children} +
+ +module.exports = KeyCommandsRegion diff --git a/src/components/multiselect-list.cjsx b/src/components/multiselect-list.cjsx index f7dcc3015..be3c9f712 100644 --- a/src/components/multiselect-list.cjsx +++ b/src/components/multiselect-list.cjsx @@ -8,6 +8,7 @@ Spinner = require './spinner' WorkspaceStore, FocusedContentStore, AccountStore} = require 'nylas-exports' +{KeyCommandsRegion} = require 'nylas-component-kit' EventEmitter = require('events').EventEmitter MultiselectListInteractionHandler = require './multiselect-list-interaction-handler' @@ -30,7 +31,6 @@ class MultiselectList extends React.Component @propTypes = className: React.PropTypes.string.isRequired collection: React.PropTypes.string.isRequired - commands: React.PropTypes.object.isRequired columns: React.PropTypes.array.isRequired dataStore: React.PropTypes.object.isRequired itemPropsProvider: React.PropTypes.func.isRequired @@ -66,30 +66,23 @@ class MultiselectList extends React.Component teardownForProps: => return unless @unsubscribers unsubscribe() for unsubscribe in @unsubscribers - @command_unsubscriber.dispose() setupForProps: (props) => - commands = _.extend {}, - 'core:focus-item': => @_onEnter() - 'core:select-item': => @_onSelect() - 'core:next-item': => @_onShift(1) - 'core:previous-item': => @_onShift(-1) - 'core:select-down': => @_onShift(1, {select: true}) - 'core:select-up': => @_onShift(-1, {select: true}) - 'core:list-page-up': => @_onScrollByPage(-1) - 'core:list-page-down': => @_onScrollByPage(1) - 'application:pop-sheet': => @_onDeselect() - - Object.keys(props.commands).forEach (key) => - commands[key] = => - context = {focusedId: @state.focusedId} - props.commands[key](context) - @unsubscribers = [] @unsubscribers.push props.dataStore.listen @_onChange @unsubscribers.push WorkspaceStore.listen @_onChange @unsubscribers.push FocusedContentStore.listen @_onChange - @command_unsubscriber = atom.commands.add('body', commands) + + _keymapHandlers: -> + 'core:focus-item': => @_onEnter() + 'core:select-item': => @_onSelect() + 'core:next-item': => @_onShift(1) + 'core:previous-item': => @_onShift(-1) + 'core:select-down': => @_onShift(1, {select: true}) + 'core:select-up': => @_onShift(-1, {select: true}) + 'core:list-page-up': => @_onScrollByPage(-1) + 'core:list-page-down': => @_onScrollByPage(1) + 'application:pop-sheet': => @_onDeselect() render: => # IMPORTANT: DO NOT pass inline functions as props. _.isEqual thinks these @@ -122,19 +115,21 @@ class MultiselectList extends React.Component spinnerElement = -
- - {spinnerElement} - {emptyElement} -
+ +
+ + {spinnerElement} + {emptyElement} +
+
else
diff --git a/src/flux/stores/focused-mail-view-store.coffee b/src/flux/stores/focused-mail-view-store.coffee index a8cb1b934..e02d57f8f 100644 --- a/src/flux/stores/focused-mail-view-store.coffee +++ b/src/flux/stores/focused-mail-view-store.coffee @@ -1,4 +1,5 @@ NylasStore = require 'nylas-store' +WorkspaceStore = require './workspace-store' MailViewFilter = require '../../mail-view-filter' CategoryStore = require './category-store' AccountStore = require './account-store' @@ -18,8 +19,9 @@ class FocusedMailViewStore extends NylasStore else if not CategoryStore.byId(@_mailView.categoryId()) @_setMailView(@_defaultMailView()) - _onFocusMailView: (filter) -> + _onFocusMailView: (filter) => return if filter.isEqual(@_mailView) + Actions.selectRootSheet(WorkspaceStore.Sheet.Threads) Actions.searchQueryCommitted('') @_setMailView(filter) @@ -36,7 +38,7 @@ class FocusedMailViewStore extends NylasStore @_mailViewBeforeSearch = null _defaultMailView: -> - category = CategoryStore.getStandardCategory('inbox') + category = CategoryStore.getStandardCategory("inbox") return null unless category MailViewFilter.forCategory(category) diff --git a/src/flux/stores/workspace-store.coffee b/src/flux/stores/workspace-store.coffee index e69be15f4..3e170aa82 100644 --- a/src/flux/stores/workspace-store.coffee +++ b/src/flux/stores/workspace-store.coffee @@ -1,6 +1,8 @@ _ = require 'underscore' Actions = require '../actions' AccountStore = require './account-store' +CategoryStore = require './category-store' +MailViewFilter = require '../../mail-view-filter' NylasStore = require 'nylas-store' Sheet = {} @@ -41,8 +43,38 @@ class WorkspaceStore extends NylasStore @popToRootSheet() @trigger() - atom.commands.add 'body', - 'application:pop-sheet': => @popSheet() + atom.commands.add 'body', @_navigationCommands() + + _navigationCommands: -> + 'application:pop-sheet' : => @popSheet() + 'navigation:go-to-inbox' : => @_setMailViewByName("inbox") + 'navigation:go-to-starred' : => @_selectStarredView() + 'navigation:go-to-sent' : => @_setMailViewByName("sent") + 'navigation:go-to-drafts' : => @_selectDraftsSheet() + 'navigation:go-to-all' : => @_selectAllView() + 'navigation:go-to-contacts': => ## TODO + 'navigation:go-to-tasks' : => ## TODO + 'navigation:go-to-label' : => ## TODO + + _setMailViewByName: (categoryName) -> + category = CategoryStore.getStandardCategory(categoryName) + return unless category + view = MailViewFilter.forCategory(category) + return unless view + Actions.focusMailView(view) + + _selectDraftsSheet: -> + Actions.selectRootSheet(@Sheet.Drafts) + + _selectAllView: -> + category = CategoryStore.getArchiveCategory() + return unless category + view = MailViewFilter.forCategory(category) + return unless view + Actions.focusMailView(view) + + _selectStarredView: -> + Actions.focusMailView MailViewFilter.forStarred() _resetInstanceVars: => @Location = Location = {} diff --git a/src/global/nylas-component-kit.coffee b/src/global/nylas-component-kit.coffee index 91eca954e..6f58079ed 100644 --- a/src/global/nylas-component-kit.coffee +++ b/src/global/nylas-component-kit.coffee @@ -23,6 +23,7 @@ class NylasComponentKit @load "ButtonDropdown", 'button-dropdown' @load "Contenteditable", 'contenteditable/contenteditable' @load "MultiselectList", 'multiselect-list' + @load "KeyCommandsRegion", 'key-commands-region' @load "InjectedComponent", 'injected-component' @load "TokenizingTextField", 'tokenizing-text-field' @load "MultiselectActionBar", 'multiselect-action-bar' diff --git a/src/global/nylas-exports.coffee b/src/global/nylas-exports.coffee index 15727e53a..1ba7ed65e 100644 --- a/src/global/nylas-exports.coffee +++ b/src/global/nylas-exports.coffee @@ -146,6 +146,6 @@ class NylasExports @get "APMWrapper", -> require('../apm-wrapper') # Testing - @get "NylasTestUtils", -> require '../../spec/test_utils' + @get "NylasTestUtils", -> require '../../spec/nylas-test-utils' module.exports = NylasExports diff --git a/src/keymap-extensions.coffee b/src/keymap-extensions.coffee deleted file mode 100644 index 3995c2422..000000000 --- a/src/keymap-extensions.coffee +++ /dev/null @@ -1,83 +0,0 @@ -fs = require 'fs-plus' -path = require 'path' -KeymapManager = require 'atom-keymap' -CSON = require 'season' -{jQuery} = require 'space-pen' -Grim = require 'grim' - -KeymapManager::onDidLoadBundledKeymaps = (callback) -> - @emitter.on 'did-load-bundled-keymaps', callback - -KeymapManager::loadBundledKeymaps = -> - # Load the base keymap and the base.platform keymap - baseKeymap = fs.resolve(path.join(@resourcePath, 'keymaps'), 'base', ['cson', 'json']) - basePlatformKeymap = fs.resolve(path.join(@resourcePath, 'keymaps'), "base-#{process.platform}", ['cson', 'json']) - @loadKeymap(baseKeymap) - @loadKeymap(basePlatformKeymap) - - # Load the template keymap (Gmail, Mail.app, etc.) the user has chosen - templateConfigKey = 'core.keymapTemplate' - templateKeymapPath = null - reloadTemplateKeymap = => - @removeBindingsFromSource(templateKeymapPath) if templateKeymapPath - templateFile = atom.config.get(templateConfigKey) - if templateFile - templateKeymapPath = fs.resolve(path.join(@resourcePath, 'keymaps', 'templates'), templateFile, ['cson', 'json']) - if fs.existsSync(templateKeymapPath) - @loadKeymap(templateKeymapPath) - @emitter.emit('did-reload-keymap', {path: templateKeymapPath}) - else - console.warn("Could not find #{templateKeymapPath}") - - atom.config.observe(templateConfigKey, reloadTemplateKeymap) - reloadTemplateKeymap() - - @emit 'bundled-keymaps-loaded' if Grim.includeDeprecatedAPIs - @emitter.emit 'did-load-bundled-keymaps' - -KeymapManager::getUserKeymapPath = -> - if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap')) - userKeymapPath - else - path.join(@configDirPath, 'keymap.cson') - -KeymapManager::loadUserKeymap = -> - userKeymapPath = @getUserKeymapPath() - return unless fs.isFileSync(userKeymapPath) - - try - @loadKeymap(userKeymapPath, watch: true, suppressErrors: true) - catch error - if error.message.indexOf('Unable to watch path') > -1 - message = """ - Unable to watch path: `#{path.basename(userKeymapPath)}`. Make sure you - have permission to read `#{userKeymapPath}`. - - On linux there are currently problems with watch sizes. See - [this document][watches] for more info. - [watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path - """ - console.error(message, {dismissable: true}) - else - detail = error.path - stack = error.stack - atom.notifications.addFatalError(error.message, {detail, stack, dismissable: true}) - -KeymapManager::subscribeToFileReadFailure = -> - @onDidFailToReadFile (error) => - userKeymapPath = @getUserKeymapPath() - message = "Failed to load `#{userKeymapPath}`" - - detail = if error.location? - error.stack - else - error.message - - console.error(message, {detail: detail, dismissable: true}) - -# This enables command handlers registered via jQuery to call -# `.abortKeyBinding()` on the `jQuery.Event` object passed to the handler. -jQuery.Event::abortKeyBinding = -> - @originalEvent?.abortKeyBinding?() - -module.exports = KeymapManager diff --git a/src/keymap-manager.coffee b/src/keymap-manager.coffee new file mode 100644 index 000000000..f66afa479 --- /dev/null +++ b/src/keymap-manager.coffee @@ -0,0 +1,97 @@ +fs = require 'fs-plus' +path = require 'path' +CSON = require 'season' +AtomKeymap = require 'atom-keymap' + +class KeymapManager extends AtomKeymap + + constructor: -> + super + @subscribeToFileReadFailure() + + onDidLoadBundledKeymaps: (callback) -> + @emitter.on 'did-load-bundled-keymaps', callback + + # N1 adds the `cmdctrl` extension. This will use `cmd` or `ctrl` on a + # mac, and `ctrl` only on windows and linux. + readKeymap: (args...) -> + re = /(cmdctrl|ctrlcmd)/i + keymap = super(args...) + for selector, keyBindings of keymap + normalizedBindings = {} + for keystrokes, command of keyBindings + if re.test keystrokes + if process.platform is "darwin" + newKeystrokes1= keystrokes.replace(re, "ctrl") + newKeystrokes2= keystrokes.replace(re, "cmd") + normalizedBindings[newKeystrokes1] = command + normalizedBindings[newKeystrokes2] = command + else + newKeystrokes = keystrokes.replace(re, "ctrl") + normalizedBindings[newKeystrokes] = command + else + normalizedBindings[keystrokes] = command + keymap[selector] = normalizedBindings + + return keymap + + loadBundledKeymaps: -> + # Load the base keymap and the base.platform keymap + baseKeymap = fs.resolve(path.join(@resourcePath, 'keymaps'), 'base', ['cson', 'json']) + inputResetKeymap = fs.resolve(path.join(@resourcePath, 'keymaps'), 'input-reset', ['cson', 'json']) + basePlatformKeymap = fs.resolve(path.join(@resourcePath, 'keymaps'), "base-#{process.platform}", ['cson', 'json']) + @loadKeymap(baseKeymap) + @loadKeymap(inputResetKeymap) + @loadKeymap(basePlatformKeymap) + + # Load the template keymap (Gmail, Mail.app, etc.) the user has chosen + templateConfigKey = 'core.keymapTemplate' + templateKeymapPath = null + reloadTemplateKeymap = => + @removeBindingsFromSource(templateKeymapPath) if templateKeymapPath + templateFile = atom.config.get(templateConfigKey) + if templateFile + templateKeymapPath = fs.resolve(path.join(@resourcePath, 'keymaps', 'templates'), templateFile, ['cson', 'json']) + if fs.existsSync(templateKeymapPath) + @loadKeymap(templateKeymapPath) + @emitter.emit('did-reload-keymap', {path: templateKeymapPath}) + else + console.warn("Could not find #{templateKeymapPath}") + + atom.config.observe(templateConfigKey, reloadTemplateKeymap) + reloadTemplateKeymap() + + @emitter.emit 'did-load-bundled-keymaps' + + getUserKeymapPath: -> + if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap')) + userKeymapPath + else + path.join(@configDirPath, 'keymap.cson') + + loadUserKeymap: -> + userKeymapPath = @getUserKeymapPath() + return unless fs.isFileSync(userKeymapPath) + + try + @loadKeymap(userKeymapPath, watch: true, suppressErrors: true) + catch error + message = """ + Unable to watch path: `#{path.basename(userKeymapPath)}`. Make sure you + have permission to read `#{userKeymapPath}`. + """ + console.error(message, {dismissable: true}) + + subscribeToFileReadFailure: -> + @onDidFailToReadFile (error) => + userKeymapPath = @getUserKeymapPath() + message = "Failed to load `#{userKeymapPath}`" + + detail = if error.location? + error.stack + else + error.message + + console.error(message, {detail: detail, dismissable: true}) + +module.exports = KeymapManager diff --git a/src/mail-view-filter.coffee b/src/mail-view-filter.coffee index 65e3f3140..24415b660 100644 --- a/src/mail-view-filter.coffee +++ b/src/mail-view-filter.coffee @@ -21,6 +21,9 @@ class MailViewFilter @forSearch: (query) -> new SearchMailViewFilter(query) + @forAll: -> + new AllMailViewFilter() + # Instance Methods constructor: -> @@ -83,6 +86,31 @@ class SearchMailViewFilter extends MailViewFilter categoryId: -> null +class AllMailViewFilter extends MailViewFilter + constructor: -> + @name = "All" + @iconName = "all-mail.png" + @ + + isEqual: (other) -> + super(other) and other.searchQuery is @searchQuery + + matchers: -> + account = AccountStore.current() + [Thread.attributes.accountId.equal(account.id)] + + canApplyToThreads: -> + true + + canArchiveThreads: -> + false + + canTrashThreads: -> + false + + categoryId: -> + CategoryStore.getStandardCategory("all")?.id + class StarredMailViewFilter extends MailViewFilter constructor: -> diff --git a/src/package.coffee b/src/package.coffee index 99d95f926..6fcaf9524 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -245,7 +245,7 @@ class Package if @bundledPackage and packagesCache[@name]? @keymaps = (["#{atom.packages.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of packagesCache[@name].keymaps) else - @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath) ? {}] + @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, atom.keymaps.readKeymap(keymapPath) ? {}] loadMenus: -> if @bundledPackage and packagesCache[@name]? diff --git a/static/components/key-commands-region.less b/static/components/key-commands-region.less new file mode 100644 index 000000000..9989db013 --- /dev/null +++ b/static/components/key-commands-region.less @@ -0,0 +1,5 @@ +.key-commands-region { + position: relative; + height: 100%; + width: 100%; +} diff --git a/static/index.less b/static/index.less index 48750e5e5..ec0b68b68 100644 --- a/static/index.less +++ b/static/index.less @@ -25,3 +25,4 @@ @import "components/spinner"; @import "components/generated-form"; @import "components/unsafe"; +@import "components/key-commands-region";