Rewrite based on feedback

This commit is contained in:
Matt Colyer
2013-09-18 09:53:57 -07:00
parent 2f419a639c
commit 037d39e943
5 changed files with 79 additions and 33 deletions

View File

@@ -129,6 +129,7 @@ class AtomApplication
@on 'application:minimize', -> Menu.sendActionToFirstResponder('performMiniaturize:')
@on 'application:zoom', -> Menu.sendActionToFirstResponder('zoom:')
@on 'application:bring-all-windows-to-front', -> Menu.sendActionToFirstResponder('arrangeInFront:')
@on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y)
app.on 'will-quit', =>
fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.

View File

@@ -115,7 +115,8 @@ class AtomWindow
@sendNativeCommand(command)
sendAtomCommand: (command, args...) ->
ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), 'command', command, args...
action = if args[0]?.contextCommand then 'context-command' else 'command'
ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), action, command, args...
sendNativeCommand: (command) ->
switch command

View File

@@ -1,4 +1,6 @@
$ = require 'jquery'
_ = require 'underscore'
remote = require 'remote'
# Public: Provides a registry for commands that you'd like to appear in the
# context menu.
@@ -8,40 +10,74 @@ module.exports =
class ContextMenuManager
# Private:
constructor: ->
@mappings = {}
@devModeMappings = {}
@definitions = {}
@devModeDefinitions = {}
@activeElement = null
@devModeDefinitions['#root-view'] = [{ type: 'separator' }]
@devModeDefinitions['#root-view'].push
label: 'Inspect Element'
command: 'application:inspect'
executeAtBuild: (e) ->
@.commandOptions = x: e.pageX, y: e.pageY
# Public: Registers a command to be displayed when the relevant item is right
# clicked.
#
# * selector: The css selector for the active element which should include
# the given command in its context menu.
# * label: The text that should appear in the context menu.
# * command: The command string that should be triggered on the activeElement
# which matches your selector.
# * definition: The object containing keys which match the menu template API.
# * options:
# + devMode: Indicates whether this command should only appear while the
# editor is in dev mode.
add: (selector, label, command, {devMode}={}) ->
mappings = if devMode then @devModeMappings else @mappings
mappings[selector] ?= []
mappings[selector].push({label, command})
add: (selector, definition, {devMode}={}) ->
definitions = if devMode then @devModeDefinitions else @definitions
(definitions[selector] ?= []).push(definition)
# Private:
bindingsForElement: (element, {devMode}={}) ->
mappings = if devMode then @devModeMappings else @mappings
items for selector, items of mappings when element.webkitMatchesSelector(selector)
# Private: Returns definitions which match the element and devMode.
definitionsForElement: (element, {devMode}={}) ->
definitions = if devMode then @devModeDefinitions else @definitions
matchedDefinitions = []
for selector, items of definitions when element.webkitMatchesSelector(selector)
matchedDefinitions.push(_.clone(item)) for item in items
# Public: Used to generate the context menu for a specific element.
matchedDefinitions
# Private: Used to generate the context menu for a specific element and it's
# parents.
#
# The menu items are sorted such that menu items that match closest to the
# active element are listed first. The further down the list you go, the higher
# up the ancestor hierarchy they match.
#
# * element: The DOM element to generate the menu template for.
menuTemplateForElement: (element) ->
menuTemplate = []
for devMode in [false, true]
for items in @bindingsForElement(element, {devMode})
for {label, command} in items
template = {label}
template.click = -> $(element).trigger(command)
menuTemplate.push(template)
menuTemplateForMostSpecificElement: (element, {devMode}={}) ->
menuTemplate = @definitionsForElement(element, {devMode})
if element.parentElement
menuTemplate.concat(@menuTemplateForMostSpecificElement(element.parentElement, {devMode}))
else
menuTemplate
menuTemplate
# Private: Returns a menu template for both normal entries as well as
# development mode entries.
combinedMenuTemplateForElement: (element) ->
menuTemplate = @menuTemplateForMostSpecificElement(element)
menuTemplate.concat(@menuTemplateForMostSpecificElement(element, devMode: true))
# Private: Executes `executeAtBuild` if defined for each menu item with
# the provided event and then removes the `executeAtBuild` property from
# the menu item.
#
# This is useful for commands that need to provide data about the event
# to the command.
executeBuildHandlers: (event, menuTemplate) ->
for template in menuTemplate
template?.executeAtBuild?.call(template, event)
delete template.executeAtBuild
# Public: Request a context menu to be displayed.
showForEvent: (event) ->
@activeElement = event.target
menuTemplate = @combinedMenuTemplateForElement(event.target)
@executeBuildHandlers(event, menuTemplate)
remote.getCurrentWindow().emit('context-menu', menuTemplate)

View File

@@ -4,5 +4,17 @@ BrowserWindow = require 'browser-window'
module.exports =
class ContextMenu
constructor: (template) ->
menu = Menu.buildFromTemplate template
template = @createClickHandlers(template)
menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.getFocusedWindow())
# Private: It's necessary to build the event handlers in this process, otherwise
# closures are drug across processes and failed to be garbage collected
# appropriately.
createClickHandlers: (template) ->
for item in template
if item.command
(item.commandOptions ?= {}).contextCommand = true
item.click = do (item) ->
=> global.atomApplication.sendCommand(item.command, item.commandOptions)
item

View File

@@ -14,6 +14,9 @@ class WindowEventHandler
@subscribe ipc, 'command', (command, args...) ->
$(window).trigger(command, args...)
@subscribe ipc, 'context-command', (command, args...) ->
$(atom.contextMenu.activeElement).trigger(command, args...)
@subscribe $(window), 'focus', -> $("body").removeClass('is-blurred')
@subscribe $(window), 'blur', -> $("body").addClass('is-blurred')
@subscribe $(window), 'window:open-path', (event, {pathToOpen, initialLine}) ->
@@ -39,14 +42,7 @@ class WindowEventHandler
@subscribe $(document), 'contextmenu', (e) ->
e.preventDefault()
menuTemplate = atom.contextMenu.menuTemplateForElement(e.target)
# FIXME: This should be registered as a dev binding on
# atom.contextMenu, but I'm not sure where in the source.
menuTemplate.push({ type: 'separator' })
menuTemplate.push({ label: 'Inspect Element', click: -> remote.getCurrentWindow().inspectElement(e.pageX, e.pageY) })
remote.getCurrentWindow().emit('context-menu', menuTemplate)
atom.contextMenu.showForEvent(e)
openLink: (event) =>
location = $(event.target).attr('href')