Honor item specificity while still preserving addition order

Rather than using order to specify item precedence, we now construct
a set of menu items for each element traversing upward from the target.
When merging items for a given element, we pass the specificity to the
merge function, which uses it to decide whether or not to clobber
existing items. When assembling the overall menu, we don’t ever clobber
to ensure that items added for elements closer to the target always win
over items matching further up the tree.
This commit is contained in:
Nathan Sobo
2014-09-30 11:44:48 -06:00
parent cf80b92f9a
commit eb929cb7a2
3 changed files with 22 additions and 18 deletions

View File

@@ -68,6 +68,8 @@ describe "ContextMenuManager", ->
'.grandchild.foo': [{label: 'A', command: 'b'}]
disposable3 = contextMenu.add
'.grandchild': [{label: 'A', command: 'c'}]
disposable4 = contextMenu.add
'.child': [{label: 'A', command: 'd'}]
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'b'}]
@@ -77,6 +79,9 @@ describe "ContextMenuManager", ->
disposable3.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'a'}]
disposable1.dispose()
expect(contextMenu.templateForElement(grandchild)).toEqual [{label: 'A', command: 'd'}]
it "allows multiple separators", ->
contextMenu.add
'.grandchild': [

View File

@@ -10,7 +10,6 @@ Grim = require 'grim'
MenuHelpers = require './menu-helpers'
SpecificityCache = {}
SequenceCount = 0
# Extended: Provides a registry for commands that you'd like to appear in the
# context menu.
@@ -91,7 +90,7 @@ class ContextMenuManager
addedItemSets = []
for selector, items of itemsBySelector
itemSet = new ContextMenuItemSet(selector, items.slice())
itemSet = new ContextMenuItemSet(selector, items)
addedItemSets.push(itemSet)
@itemSets.push(itemSet)
@@ -107,19 +106,21 @@ class ContextMenuManager
currentTarget = event.target
while currentTarget?
currentTargetItems = []
matchingItemSets =
@itemSets
.filter (itemSet) -> currentTarget.webkitMatchesSelector(itemSet.selector)
.sort (a, b) -> a.compare(b)
@itemSets.filter (itemSet) -> currentTarget.webkitMatchesSelector(itemSet.selector)
for {items} in matchingItemSets
for item in items
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(template, MenuHelpers.cloneMenuItem(item))
MenuHelpers.merge(currentTargetItems, MenuHelpers.cloneMenuItem(item), itemSet.specificity)
for item in currentTargetItems
MenuHelpers.merge(template, item, false)
currentTarget = currentTarget.parentElement
@@ -167,9 +168,3 @@ class ContextMenuManager
class ContextMenuItemSet
constructor: (@selector, @items) ->
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
@sequenceNumber = SequenceCount++
# more specific / recent item sets sort later, because we clobber existing menu items
compare: (other) ->
@specificity - other.specificity or
@sequenceNumber - other.sequenceNumber

View File

@@ -1,14 +1,18 @@
_ = require 'underscore-plus'
merge = (menu, item) ->
ItemSpecificities = new WeakMap
merge = (menu, item, itemSpecificity=Infinity) ->
ItemSpecificities.set(item, itemSpecificity) if itemSpecificity
matchingItemIndex = findMatchingItemIndex(menu, item)
matchingItem = menu[matchingItemIndex] unless matchingItemIndex is - 1
if matchingItem?
if item.submenu?
merge(matchingItem.submenu, submenuItem) for submenuItem in item.submenu
else
menu[matchingItemIndex] = item
merge(matchingItem.submenu, submenuItem, itemSpecificity) for submenuItem in item.submenu
else if itemSpecificity
unless itemSpecificity < ItemSpecificities.get(matchingItem)
menu[matchingItemIndex] = item
else
menu.push(item)