{$} = require './space-pen-extensions' _ = require 'underscore-plus' remote = require 'remote' # Public: 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: (@devMode=false) -> @definitions = {} @devModeDefinitions = {} @activeElement = null @devModeDefinitions['.workspace'] = [ label: 'Inspect Element' command: 'application:inspect' executeAtBuild: (e) -> @commandOptions = x: e.pageX, y: e.pageY ] # Public: Creates menu definitions from the object specified by the menu # cson API. # # name - The path of the file that contains the menu definitions. # object - The 'context-menu' object specified in the menu cson API. # options - An {Object} with the following keys: # :devMode - Determines whether the entries should only be shown when # the window is in dev mode. # # Returns nothing. add: (name, object, {devMode}={}) -> for selector, items of object for label, commandOrSubmenu of items if typeof commandOrSubmenu is 'object' submenu = [] for submenuLabel, command of commandOrSubmenu submenu.push(@buildMenuItem(submenuLabel, command)) @addBySelector(selector, {label: label, submenu: submenu}, {devMode}) else menuItem = @buildMenuItem(label, commandOrSubmenu) @addBySelector(selector, menuItem, {devMode}) buildMenuItem: (label, command) -> if label is command is '-' {type: 'separator'} else {label, command} # 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. # definition - The object containing keys which match the menu template API. # options - An {Object} with the following keys: # :devMode - Indicates whether this command should only appear while the # editor is in dev mode. addBySelector: (selector, definition, {devMode}={}) -> definitions = if devMode then @devModeDefinitions else @definitions unless _.findWhere(definitions[selector], command: definition.command) (definitions[selector] ?= []).push(definition) # 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 matchedDefinitions # 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. menuTemplateForMostSpecificElement: (element, {devMode}={}) -> menuTemplate = @definitionsForElement(element, {devMode}) if element.parentElement menuTemplate.concat(@menuTemplateForMostSpecificElement(element.parentElement, {devMode})) else menuTemplate # Returns a menu template for both normal entries as well as # development mode entries. combinedMenuTemplateForElement: (element) -> normalItems = @menuTemplateForMostSpecificElement(element) devItems = if @devMode then @menuTemplateForMostSpecificElement(element, devMode: true) else [] menuTemplate = normalItems menuTemplate.push({ type: 'separator' }) if normalItems.length > 0 and devItems.length > 0 menuTemplate.concat(devItems) # 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) return unless menuTemplate?.length > 0 @executeBuildHandlers(event, menuTemplate) remote.getCurrentWindow().emit('context-menu', menuTemplate)