Files
atom/src/context-menu-manager.coffee
Nathan Sobo 782f9c609e Add shouldDisplay hook for context menu items
If present, if a falsy value is returned from this function for a given
context menu invocation, the item will not be displayed.
2014-09-30 12:06:09 -06:00

132 lines
4.2 KiB
CoffeeScript

{$} = require './space-pen-extensions'
_ = require 'underscore-plus'
remote = require 'remote'
path = require 'path'
CSON = require 'season'
fs = require 'fs-plus'
{specificity} = require 'clear-cut'
{Disposable} = require 'event-kit'
MenuHelpers = require './menu-helpers'
SpecificityCache = {}
SequenceCount = 0
# Extended: Provides a registry for commands that you'd like to appear in the
# context menu.
#
# An instance of this class is always available as the `atom.contextMenu`
# global.
module.exports =
class ContextMenuManager
constructor: ({@resourcePath, @devMode}) ->
@activeElement = null
@itemSets = []
@definitions = {'.overlayer': []} # TODO: Remove once color picker package stops touching private data
@add '.workspace': [{
label: 'Inspect Element'
command: 'application:inspect'
created: (item, event) ->
{pageX, pageY} = event
item.commandOptions = {x: pageX, y: pageY}
}]
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
loadPlatformItems: ->
menusDirPath = path.join(@resourcePath, 'menus')
platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json'])
map = CSON.readFileSync(platformMenuPath)
atom.contextMenu.add(platformMenuPath, map['context-menu'])
# Public: Creates menu definitions from the object specified by the menu
# JSON API.
#
# * `name` The path of the file that contains the menu definitions.
# * `object` The 'context-menu' object specified in the menu JSON API.
# * `options` An optional {Object} with the following keys:
# * `devMode` Determines whether the entries should only be shown when
# the window is in dev mode.
add: (items) ->
unless typeof arguments[0] is 'object'
legacyItems = arguments[1]
devMode = arguments[2]?.devMode
return @add(@convertLegacyItems(legacyItems, devMode))
itemsBySelector = arguments[0]
devMode = arguments[1]?.devMode ? false
addedItemSets = []
for selector, items of itemsBySelector
itemSet = new ContextMenuItemSet(selector, items.slice())
addedItemSets.push(itemSet)
@itemSets.push(itemSet)
new Disposable =>
for itemSet in addedItemSets
@itemSets.splice(@itemSets.indexOf(itemSet), 1)
templateForElement: (target) ->
@templateForEvent({target})
templateForEvent: (event) ->
template = []
currentTarget = event.target
while currentTarget?
matchingItemSets =
@itemSets
.filter (itemSet) -> currentTarget.webkitMatchesSelector(itemSet.selector)
.sort (a, b) -> a.compare(b)
for {items} in matchingItemSets
for item in items
continue if item.devMode and not @devMode
item = Object.create(item)
if typeof item.shouldDisplay is 'function'
continue unless item.shouldDisplay(event)
item.created?(event)
templateItem = _.pick(item, 'type', 'label', 'command', 'submenu', 'commandOptions')
MenuHelpers.merge(template, templateItem)
currentTarget = currentTarget.parentElement
template
convertLegacyItems: (legacyItems, devMode) ->
itemsBySelector = {}
for selector, commandsByLabel of legacyItems
itemsBySelector[selector] = items = []
for label, commandOrSubmenu of commandsByLabel
if typeof commandOrSubmenu is 'object'
items.push({label, submenu: @convertLegacyItems(commandOrSubmenu, devMode), devMode})
else if commandOrSubmenu is '-'
items.push({type: 'separator'})
else
items.push({label, command: commandOrSubmenu, devMode})
itemsBySelector
# Public: Request a context menu to be displayed.
#
# * `event` A DOM event.
showForEvent: (event) ->
@activeElement = event.target
menuTemplate = @templateForEvent(event)
return unless menuTemplate?.length > 0
# @executeBuildHandlers(event, menuTemplate)
remote.getCurrentWindow().emit('context-menu', menuTemplate)
return
class ContextMenuItemSet
constructor: (@selector, @items) ->
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
@sequenceNumber = SequenceCount++
compare: (other) ->
other.specificity - @specificity or
other.sequenceNumber - @sequenceNumber