aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/link_hints.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'content_scripts/link_hints.coffee')
-rw-r--r--content_scripts/link_hints.coffee385
1 files changed, 196 insertions, 189 deletions
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index 2bcc7508..107a292e 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -19,36 +19,35 @@ OPEN_INCOGNITO = name: "incognito"
DOWNLOAD_LINK_URL = name: "download"
LinkHints =
+ activateMode: (mode = OPEN_IN_CURRENT_TAB) -> new LinkHintsMode mode
+
+ activateModeToOpenInNewTab: -> @activateMode OPEN_IN_NEW_BG_TAB
+ activateModeToOpenInNewForegroundTab: -> @activateMode OPEN_IN_NEW_FG_TAB
+ activateModeToCopyLinkUrl: -> @activateMode COPY_LINK_URL
+ activateModeWithQueue: -> @activateMode OPEN_WITH_QUEUE
+ activateModeToOpenIncognito: -> @activateMode OPEN_INCOGNITO
+ activateModeToDownloadLink: -> @activateMode DOWNLOAD_LINK_URL
+
+class LinkHintsMode
hintMarkerContainingDiv: null
- # one of the enums listed at the top of this file
+ # One of the enums listed at the top of this file.
mode: undefined
- # function that does the appropriate action on the selected link
+ # Function that does the appropriate action on the selected link.
linkActivator: undefined
# While in delayMode, all keypresses have no effect.
delayMode: false
- # Handle the link hinting marker generation and matching. Must be initialized after Settings have been
- # loaded, so that we can retrieve the option setting.
- getMarkerMatcher: ->
- if Settings.get("filterLinkHints") then filterHints else alphabetHints
- # lock to ensure only one instance runs at a time
+ # Lock to ensure only one instance runs at a time.
isActive: false
+ # The link-hints "mode" (in the key-handler, indicator sense).
+ hintMode: null
# Call this function on exit (if defined).
onExit: null
+ # A count of the number of Tab presses since the last non-Tab keyboard event.
+ tabCount: 0
- # We need this as a top-level function because our command system doesn't yet support arguments.
- activateModeToOpenInNewTab: -> @activateMode(OPEN_IN_NEW_BG_TAB)
- activateModeToOpenInNewForegroundTab: -> @activateMode(OPEN_IN_NEW_FG_TAB)
- activateModeToCopyLinkUrl: -> @activateMode(COPY_LINK_URL)
- activateModeWithQueue: -> @activateMode(OPEN_WITH_QUEUE)
- activateModeToOpenIncognito: -> @activateMode(OPEN_INCOGNITO)
- activateModeToDownloadLink: -> @activateMode(DOWNLOAD_LINK_URL)
-
- activateMode: (mode = OPEN_IN_CURRENT_TAB) ->
+ constructor: (mode = OPEN_IN_CURRENT_TAB) ->
# we need documentElement to be ready in order to append links
return unless document.documentElement
-
- if @isActive
- return
@isActive = true
elements = @getVisibleClickableElements()
@@ -62,33 +61,38 @@ LinkHints =
length = (el) -> el.element.innerHTML?.length ? 0
elements.sort (a,b) -> length(a) - length b
hintMarkers = (@createMarkerFor(el) for el in elements)
- @getMarkerMatcher().fillInMarkers(hintMarkers)
+ @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints)
+ @markerMatcher.fillInMarkers hintMarkers
@hintMode = new Mode
name: "hint/#{mode.name}"
indicator: false
passInitialKeyupEvents: true
+ suppressAllKeyboardEvents: true
+ exitOnEscape: true
+ exitOnClick: true
+ exitOnScroll: true
keydown: @onKeyDownInMode.bind this, hintMarkers
- # Trap all other key events.
- keypress: -> false
- keyup: -> false
+ keypress: @onKeyPressInMode.bind this, hintMarkers
+
+ @hintMode.onExit =>
+ @deactivateMode() if @isActive
@setOpenLinkMode mode
# Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
- # because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat
- # that if you scroll the page and the link has position=fixed, the marker will not stay fixed.
- @hintMarkerContainingDiv = DomUtils.addElementList(hintMarkers,
- { id: "vimiumHintMarkerContainer", className: "vimiumReset" })
+ # because some clickable elements cannot contain children, e.g. submit buttons.
+ @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers,
+ id: "vimiumHintMarkerContainer", className: "vimiumReset"
setOpenLinkMode: (@mode) ->
if @mode is OPEN_IN_NEW_BG_TAB or @mode is OPEN_IN_NEW_FG_TAB or @mode is OPEN_WITH_QUEUE
if @mode is OPEN_IN_NEW_BG_TAB
- @hintMode.setIndicator "Open link in new tab"
+ @hintMode.setIndicator "Open link in new tab."
else if @mode is OPEN_IN_NEW_FG_TAB
- @hintMode.setIndicator "Open link in new tab and switch to it"
+ @hintMode.setIndicator "Open link in new tab and switch to it."
else
- @hintMode.setIndicator "Open multiple links in a new tab"
+ @hintMode.setIndicator "Open multiple links in new tabs."
@linkActivator = (link) ->
# When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on
# windows) to open it in a new tab if necessary.
@@ -98,7 +102,7 @@ LinkHints =
ctrlKey: KeyboardUtils.platform != "Mac"
altKey: false
else if @mode is COPY_LINK_URL
- @hintMode.setIndicator "Copy link URL to Clipboard"
+ @hintMode.setIndicator "Copy link URL to Clipboard."
@linkActivator = (link) =>
if link.href?
chrome.runtime.sendMessage handler: "copyToClipboard", data: link.href
@@ -108,15 +112,15 @@ LinkHints =
else
@onExit = -> HUD.showForDuration "No link to yank.", 2000
else if @mode is OPEN_INCOGNITO
- @hintMode.setIndicator "Open link in incognito window"
+ @hintMode.setIndicator "Open link in incognito window."
@linkActivator = (link) ->
chrome.runtime.sendMessage handler: 'openUrlInIncognito', url: link.href
else if @mode is DOWNLOAD_LINK_URL
- @hintMode.setIndicator "Download link URL"
+ @hintMode.setIndicator "Download link URL."
@linkActivator = (link) ->
DomUtils.simulateClick link, altKey: true, ctrlKey: false, metaKey: false
else # OPEN_IN_CURRENT_TAB
- @hintMode.setIndicator "Open link in current tab"
+ @hintMode.setIndicator "Open link in current tab."
@linkActivator = (link) -> DomUtils.simulateClick.bind(DomUtils, link)()
#
@@ -257,62 +261,85 @@ LinkHints =
nonOverlappingElements
- #
- # Handles shift and esc keys. The other keys are passed to getMarkerMatcher().matchHintsByKey.
- #
+ # Handles <Shift> and <Ctrl>.
onKeyDownInMode: (hintMarkers, event) ->
return if @delayMode or event.repeat
+ @keydownKeyChar = KeyboardUtils.getKeyChar(event).toLowerCase()
+
+ previousTabCount = @tabCount
+ @tabCount = 0
+
+ if event.keyCode in [ keyCodes.shiftKey, keyCodes.ctrlKey ] and
+ @mode in [ OPEN_IN_CURRENT_TAB, OPEN_WITH_QUEUE, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB ]
+ @tabCount = previousTabCount
+ # Toggle whether to open the link in a new or current tab.
+ previousMode = @mode
+ keyCode = event.keyCode
+
+ switch keyCode
+ when keyCodes.shiftKey
+ @setOpenLinkMode(if @mode is OPEN_IN_CURRENT_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_CURRENT_TAB)
+ when keyCodes.ctrlKey
+ @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)
+
+ handlerStack.push
+ keyup: (event) =>
+ if event.keyCode == keyCode
+ handlerStack.remove()
+ @setOpenLinkMode previousMode if @isActive
+
+ else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
+ if @markerMatcher.popKeyChar()
+ @updateVisibleMarkers hintMarkers
+ else
+ @deactivateMode()
+
+ else if event.keyCode == keyCodes.enter
+ # Activate the active hint, if there is one. Only FilterHints uses an active hint.
+ @activateLink @markerMatcher.activeHintMarker if @markerMatcher.activeHintMarker
- if ((event.keyCode == keyCodes.shiftKey or event.keyCode == keyCodes.ctrlKey) and
- (@mode == OPEN_IN_CURRENT_TAB or
- @mode == OPEN_WITH_QUEUE or
- @mode == OPEN_IN_NEW_BG_TAB or
- @mode == OPEN_IN_NEW_FG_TAB))
- # Toggle whether to open the link in a new or current tab.
- previousMode = @mode
- keyCode = event.keyCode
-
- switch keyCode
- when keyCodes.shiftKey
- @setOpenLinkMode(if @mode is OPEN_IN_CURRENT_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_CURRENT_TAB)
- when keyCodes.ctrlKey
- @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)
-
- handlerStack.push
- keyup: (event) =>
- if event.keyCode == keyCode
- handlerStack.remove()
- @setOpenLinkMode previousMode if @isActive
- true
-
- # TODO(philc): Ignore keys that have modifiers.
- if (KeyboardUtils.isEscape(event))
- DomUtils.suppressKeyupAfterEscape handlerStack
+ else if event.keyCode == keyCodes.tab
+ @tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
+ @updateVisibleMarkers hintMarkers, @tabCount
+
+ else
+ return
+
+ # We've handled the event, so suppress it.
+ DomUtils.suppressEvent event
+
+ # Handles normal input.
+ onKeyPressInMode: (hintMarkers, event) ->
+ return if @delayMode or event.repeat
+
+ keyChar = String.fromCharCode(event.charCode).toLowerCase()
+ if keyChar
+ @markerMatcher.pushKeyChar keyChar, @keydownKeyChar
+ @updateVisibleMarkers hintMarkers
+
+ # We've handled the event, so suppress it.
+ DomUtils.suppressEvent event
+
+ updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
+ keyResult = @markerMatcher.getMatchingHints hintMarkers, tabCount
+ linksMatched = keyResult.linksMatched
+ if linksMatched.length == 0
@deactivateMode()
- else if (event.keyCode != keyCodes.shiftKey and event.keyCode != keyCodes.ctrlKey)
- keyResult = @getMarkerMatcher().matchHintsByKey(hintMarkers, event)
- linksMatched = keyResult.linksMatched
- delay = keyResult.delay ? 0
- if (linksMatched.length == 0)
- @deactivateMode()
- else if (linksMatched.length == 1)
- @activateLink(linksMatched[0], delay)
- else
- for marker in hintMarkers
- @hideMarker(marker)
- for matched in linksMatched
- @showMarker(matched, @getMarkerMatcher().hintKeystrokeQueue.length)
- false # We've handled this key, so prevent propagation.
+ else if linksMatched.length == 1
+ @activateLink linksMatched[0], keyResult.delay ? 0
+ else
+ @hideMarker marker for marker in hintMarkers
+ @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched
#
# When only one link hint remains, this function activates it in the appropriate way.
#
- activateLink: (matchedLink, delay) ->
+ activateLink: (matchedLink, delay = 0) ->
@delayMode = true
clickEl = matchedLink.clickableItem
if (DomUtils.isSelectable(clickEl))
DomUtils.simulateSelect(clickEl)
- @deactivateMode(delay, -> LinkHints.delayMode = false)
+ @deactivateMode delay
else
# TODO figure out which other input elements should not receive focus
if (clickEl.nodeName.toLowerCase() == "input" and clickEl.type not in ["button", "submit"])
@@ -320,18 +347,15 @@ LinkHints =
DomUtils.flashRect(matchedLink.rect)
@linkActivator(clickEl)
if @mode is OPEN_WITH_QUEUE
- @deactivateMode delay, ->
- LinkHints.delayMode = false
- LinkHints.activateModeWithQueue()
+ @deactivateMode delay, -> LinkHints.activateModeWithQueue()
else
- @deactivateMode(delay, -> LinkHints.delayMode = false)
+ @deactivateMode delay
#
# Shows the marker, highlighting matchingCharCount characters.
#
showMarker: (linkMarker, matchingCharCount) ->
linkMarker.style.display = ""
- # TODO(philc):
for j in [0...linkMarker.childNodes.length]
if (j < matchingCharCount)
linkMarker.childNodes[j].classList.add("matchingCharacter")
@@ -340,37 +364,41 @@ LinkHints =
hideMarker: (linkMarker) -> linkMarker.style.display = "none"
- #
- # If called without arguments, it executes immediately. Othewise, it
- # executes after 'delay' and invokes 'callback' when it is finished.
- #
- deactivateMode: (delay, callback) ->
+ deactivateMode: (delay = 0, callback = null) ->
deactivate = =>
- if (LinkHints.getMarkerMatcher().deactivate)
- LinkHints.getMarkerMatcher().deactivate()
- if (LinkHints.hintMarkerContainingDiv)
- DomUtils.removeElement LinkHints.hintMarkerContainingDiv
- LinkHints.hintMarkerContainingDiv = null
- @hintMode.exit()
+ DomUtils.removeElement @hintMarkerContainingDiv if @hintMarkerContainingDiv
+ @hintMarkerContainingDiv = null
+ @markerMatcher = null
+ @isActive = false
+ @hintMode?.exit()
+ @hintMode = null
@onExit?()
@onExit = null
- @isActive = false
+ @tabCount = 0
- # we invoke the deactivate() function directly instead of using setTimeout(callback, 0) so that
- # deactivateMode can be tested synchronously
- if (!delay)
- deactivate()
- callback() if (callback)
- else
- setTimeout(->
+ if delay
+ Utils.setTimeout delay, ->
deactivate()
- callback() if callback
- delay)
+ callback?()
+ else
+ # We invoke deactivate() directly (instead of setting a timeout of 0) so that deactivateMode() can be
+ # tested synchronously.
+ deactivate()
+ callback?()
-alphabetHints =
- hintKeystrokeQueue: []
+# Use characters for hints, and do not filter links by their text.
+class AlphabetHints
logXOfBase: (x, base) -> Math.log(x) / Math.log(base)
+ constructor: ->
+ @linkHintCharacters = Settings.get "linkHintCharacters"
+ # We use the keyChar from keydown if the link-hint characters are all "a-z0-9". This is the default
+ # settings value, and preserves the legacy behavior (which always used keydown) for users which are
+ # familiar with that behavior. Otherwise, we use keyChar from keypress, which admits non-Latin
+ # characters. See #1722.
+ @useKeydown = /^[a-z0-9]*$/.test @linkHintCharacters
+ @hintKeystrokeQueue = []
+
fillInMarkers: (hintMarkers) ->
hintStrings = @hintStrings(hintMarkers.length)
for marker, idx in hintMarkers
@@ -384,27 +412,26 @@ alphabetHints =
# may be of different lengths.
#
hintStrings: (linkCount) ->
- linkHintCharacters = Settings.get("linkHintCharacters")
# Determine how many digits the link hints will require in the worst case. Usually we do not need
# all of these digits for every link single hint, so we can show shorter hints for a few of the links.
- digitsNeeded = Math.ceil(@logXOfBase(linkCount, linkHintCharacters.length))
+ digitsNeeded = Math.ceil(@logXOfBase(linkCount, @linkHintCharacters.length))
# Short hints are the number of hints we can possibly show which are (digitsNeeded - 1) digits in length.
shortHintCount = Math.floor(
- (Math.pow(linkHintCharacters.length, digitsNeeded) - linkCount) /
- linkHintCharacters.length)
+ (Math.pow(@linkHintCharacters.length, digitsNeeded) - linkCount) /
+ @linkHintCharacters.length)
longHintCount = linkCount - shortHintCount
hintStrings = []
if (digitsNeeded > 1)
for i in [0...shortHintCount]
- hintStrings.push(numberToHintString(i, linkHintCharacters, digitsNeeded - 1))
+ hintStrings.push(numberToHintString(i, @linkHintCharacters, digitsNeeded - 1))
- start = shortHintCount * linkHintCharacters.length
+ start = shortHintCount * @linkHintCharacters.length
for i in [start...(start + longHintCount)]
- hintStrings.push(numberToHintString(i, linkHintCharacters, digitsNeeded))
+ hintStrings.push(numberToHintString(i, @linkHintCharacters, digitsNeeded))
- @shuffleHints(hintStrings, linkHintCharacters.length)
+ @shuffleHints(hintStrings, @linkHintCharacters.length)
#
# This shuffles the given set of hints so that they're scattered -- hints starting with the same character
@@ -419,26 +446,22 @@ alphabetHints =
result = result.concat(bucket)
result
- matchHintsByKey: (hintMarkers, event) ->
- # If a shifted-character is typed, treat it as lowerase for the purposes of matching hints.
- keyChar = KeyboardUtils.getKeyChar(event).toLowerCase()
-
- if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey)
- if (!@hintKeystrokeQueue.pop())
- return { linksMatched: [] }
- else if keyChar
- @hintKeystrokeQueue.push(keyChar)
+ getMatchingHints: (hintMarkers) ->
+ matchString = @hintKeystrokeQueue.join ""
+ linksMatched: hintMarkers.filter (linkMarker) -> linkMarker.hintString.startsWith matchString
- matchString = @hintKeystrokeQueue.join("")
- linksMatched = hintMarkers.filter((linkMarker) -> linkMarker.hintString.indexOf(matchString) == 0)
- { linksMatched: linksMatched }
+ pushKeyChar: (keyChar, keydownKeyChar) ->
+ @hintKeystrokeQueue.push (if @useKeydown then keydownKeyChar else keyChar)
+ popKeyChar: -> @hintKeystrokeQueue.pop()
- deactivate: -> @hintKeystrokeQueue = []
-
-filterHints =
- hintKeystrokeQueue: []
- linkTextKeystrokeQueue: []
- labelMap: {}
+# Use numbers (usually) for hints, and also filter links by their text.
+class FilterHints
+ constructor: ->
+ @linkHintNumbers = Settings.get "linkHintNumbers"
+ @hintKeystrokeQueue = []
+ @linkTextKeystrokeQueue = []
+ @labelMap = {}
+ @activeHintMarker = null
#
# Generate a map of input element => label
@@ -455,7 +478,7 @@ filterHints =
@labelMap[forElement] = labelText
generateHintString: (linkHintNumber) ->
- (numberToHintString linkHintNumber + 1, Settings.get "linkHintNumbers").toUpperCase()
+ numberToHintString linkHintNumber + 1, @linkHintNumbers.toUpperCase()
generateLinkText: (element) ->
linkText = ""
@@ -496,74 +519,58 @@ filterHints =
marker.showLinkText = linkTextObject.show
@renderMarker(marker)
+ @activeHintMarker = hintMarkers[0]
+ @activeHintMarker?.classList.add "vimiumActiveHintMarker"
+
hintMarkers
- matchHintsByKey: (hintMarkers, event) ->
- keyChar = KeyboardUtils.getKeyChar(event)
+ getMatchingHints: (hintMarkers, tabCount = 0) ->
delay = 0
- userIsTypingLinkText = false
-
- if (event.keyCode == keyCodes.enter)
- # activate the lowest-numbered link hint that is visible
- for marker in hintMarkers
- if (marker.style.display != "none")
- return { linksMatched: [ marker ] }
- else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey)
- # backspace clears hint key queue first, then acts on link text key queue.
- # if both queues are empty. exit hinting mode
- if (!@hintKeystrokeQueue.pop() && !@linkTextKeystrokeQueue.pop())
- return { linksMatched: [] }
- else if (keyChar)
- if (Settings.get("linkHintNumbers").indexOf(keyChar) >= 0)
- @hintKeystrokeQueue.push(keyChar)
- else
- # since we might renumber the hints, the current hintKeyStrokeQueue
- # should be rendered invalid (i.e. reset).
- @hintKeystrokeQueue = []
- @linkTextKeystrokeQueue.push(keyChar)
- userIsTypingLinkText = true
- # at this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest
+ # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest
# input. use them to filter the link hints accordingly.
- linksMatched = @filterLinkHints(hintMarkers)
- matchString = @hintKeystrokeQueue.join("")
- linksMatched = linksMatched.filter((linkMarker) ->
- !linkMarker.filtered && linkMarker.hintString.indexOf(matchString) == 0)
-
- if (linksMatched.length == 1 && userIsTypingLinkText)
- # In filter mode, people tend to type out words past the point
- # needed for a unique match. Hence we should avoid passing
- # control back to command mode immediately after a match is found.
+ matchString = @hintKeystrokeQueue.join ""
+ linksMatched = @filterLinkHints hintMarkers
+ linksMatched = linksMatched.filter (linkMarker) -> linkMarker.hintString.startsWith matchString
+
+ if linksMatched.length == 1 && @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length
+ # In filter mode, people tend to type out words past the point needed for a unique match. Hence we
+ # should avoid passing control back to command mode immediately after a match is found.
delay = 200
+ # Visually highlight of the active hint (that is, the one that will be activated if the user
+ # types <Enter>).
+ tabCount = ((linksMatched.length * Math.abs tabCount) + tabCount) % linksMatched.length
+ @activeHintMarker?.classList.remove "vimiumActiveHintMarker"
+ @activeHintMarker = linksMatched[tabCount]
+ @activeHintMarker?.classList.add "vimiumActiveHintMarker"
+
{ linksMatched: linksMatched, delay: delay }
- #
- # Marks the links that do not match the linkText search string with the 'filtered' DOM property. Renumbers
- # the remainder if necessary.
- #
- filterLinkHints: (hintMarkers) ->
- linksMatched = []
- linkSearchString = @linkTextKeystrokeQueue.join("")
+ pushKeyChar: (keyChar, keydownKeyChar) ->
+ # For filtered hints, we *always* use the keyChar value from keypress, because there is no obvious and
+ # easy-to-understand meaning for choosing one of keyChar or keydownKeyChar (as there is for alphabet
+ # hints).
+ if 0 <= @linkHintNumbers.indexOf keyChar
+ @hintKeystrokeQueue.push keyChar
+ else
+ # Since we might renumber the hints, we should reset the current hintKeyStrokeQueue.
+ @hintKeystrokeQueue = []
+ @linkTextKeystrokeQueue.push keyChar
- for linkMarker in hintMarkers
- matchedLink = linkMarker.linkText.toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0
+ popKeyChar: ->
+ @hintKeystrokeQueue.pop() or @linkTextKeystrokeQueue.pop()
- if (!matchedLink)
- linkMarker.filtered = true
- else
- linkMarker.filtered = false
- oldHintString = linkMarker.hintString
- linkMarker.hintString = @generateHintString(linksMatched.length)
- @renderMarker(linkMarker) if (linkMarker.hintString != oldHintString)
- linksMatched.push(linkMarker)
-
- linksMatched
+ # Filter link hints by search string, renumbering the hints as necessary.
+ filterLinkHints: (hintMarkers) ->
+ idx = 0
+ linkSearchString = @linkTextKeystrokeQueue.join("").toLowerCase()
- deactivate: (delay, callback) ->
- @hintKeystrokeQueue = []
- @linkTextKeystrokeQueue = []
- @labelMap = {}
+ for linkMarker in hintMarkers
+ continue unless 0 <= linkMarker.linkText.toLowerCase().indexOf linkSearchString
+ linkMarker.hintString = @generateHintString idx++
+ @renderMarker linkMarker
+ linkMarker
#
# Make each hint character a span, so that we can highlight the typed characters as you type them.