-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontext-menu-manager.coffee
175 lines (149 loc) · 6.22 KB
/
context-menu-manager.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{$} = 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'
Grim = require 'grim'
MenuHelpers = require './menu-helpers'
SpecificityCache = {}
# 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}) ->
@definitions = {'.overlayer': []} # TODO: Remove once color picker package stops touching private data
@clear()
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(map['context-menu'])
# Public: Add context menu items scoped by CSS selectors.
#
# ## Examples
#
# To add a context menu, pass a selector matching the elements to which you
# want the menu to apply as the top level key, followed by a menu descriptor.
# The invocation below adds a global 'Help' context menu item and a 'History'
# submenu on the editor supporting undo/redo. This is just for example
# purposes and not the way the menu is actually configured in Atom by default.
#
# ```coffee
# atom.contextMenu.add {
# 'atom-workspace': [{label: 'Help', command: 'application:open-documentation'}]
# 'atom-text-editor': [{
# label: 'History',
# submenu: [
# {label: 'Undo': command:'core:undo'}
# {label: 'Redo': command:'core:redo'}
# ]
# }]
# }
# ```
#
# ## Arguments
#
# * `itemsBySelector` An {Object} whose keys are CSS selectors and whose
# values are {Array}s of item {Object}s containing the following keys:
# * `label` (Optional) A {String} containing the menu item's label.
# * `command` (Optional) A {String} containing the command to invoke on the
# target of the right click that invoked the context menu.
# * `submenu` (Optional) An {Array} of additional items.
# * `type` (Optional) If you want to create a separator, provide an item
# with `type: 'separator'` and no other keys.
# * `created` (Optional) A {Function} that is called on the item each time a
# context menu is created via a right click. You can assign properties to
# `this` to dynamically compute the command, label, etc. This method is
# actually called on a clone of the original item template to prevent state
# from leaking across context menu deployments. Called with the following
# argument:
# * `event` The click event that deployed the context menu.
# * `shouldDisplay` (Optional) A {Function} that is called to determine
# whether to display this item on a given context menu deployment. Called
# with the following argument:
# * `event` The click event that deployed the context menu.
add: (itemsBySelector) ->
# Detect deprecated file path as first argument
unless typeof itemsBySelector is 'object'
Grim.deprecate("ContextMenuManage::add has changed to take a single object as its argument. Please consult the documentation.")
itemsBySelector = arguments[1]
devMode = arguments[2]?.devMode
# Detect deprecated format for items object
for key, value of itemsBySelector
unless _.isArray(value)
Grim.deprecate("The format for declaring context menu items has changed. Please consult the documentation.")
itemsBySelector = @convertLegacyItemsBySelector(itemsBySelector, devMode)
addedItemSets = []
for selector, items of itemsBySelector
itemSet = new ContextMenuItemSet(selector, items)
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?
currentTargetItems = []
matchingItemSets =
@itemSets.filter (itemSet) -> currentTarget.webkitMatchesSelector(itemSet.selector)
for itemSet in matchingItemSets
for item in itemSet.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)
MenuHelpers.merge(currentTargetItems, item, itemSet.specificity)
for item in currentTargetItems
MenuHelpers.merge(template, item, false)
currentTarget = currentTarget.parentElement
template
convertLegacyItemsBySelector: (legacyItemsBySelector, devMode) ->
itemsBySelector = {}
for selector, commandsByLabel of legacyItemsBySelector
itemsBySelector[selector] = @convertLegacyItems(commandsByLabel, devMode)
itemsBySelector
convertLegacyItems: (legacyItems, devMode) ->
items = []
for label, commandOrSubmenu of legacyItems
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})
items
# 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
remote.getCurrentWindow().emit('context-menu', menuTemplate)
return
clear: ->
@activeElement = null
@itemSets = []
@add 'atom-workspace': [{
label: 'Inspect Element'
command: 'application:inspect'
devMode: true
created: (event) ->
{pageX, pageY} = event
@commandDetail = {x: pageX, y: pageY}
}]
class ContextMenuItemSet
constructor: (@selector, @items) ->
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))