marker.coffee | |
---|---|
{ interfaces: Ci } = Components
XPathResult = Ci.nsIDOMXPathResult
{ getPref } = require 'prefs' | |
All elements that have one or more of the following properties qualify for their own marker in hints mode | MARKABLE_ELEMENT_PROPERTIES = [
"@tabindex"
"@onclick"
"@onmousedown"
"@onmouseup"
"@oncommand"
"@role='link'"
"@role='button'"
"contains(@class, 'button')"
"@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true'"
] |
All the following elements qualify for their own marker in hints mode | MARKABLE_ELEMENTS = [
"a"
"area[@href]"
"textarea"
"button"
"select"
"input[not(@type='hidden' or @disabled or @readonly)]"
] |
Marker class wraps the markable element and provides methods to manipulate the markers | class Marker |
Creates the marker DOM node | constructor: (@element) ->
document = @element.ownerDocument
window = document.defaultView
@markerElement = document.createElement 'div'
@markerElement.className = 'VimFxReset VimFxHintMarker' |
Shows the marker | show: -> @markerElement.className = 'VimFxReset VimFxHintMarker' |
Hides the marker | hide: -> @markerElement.className = 'VimFxReset VimFxHiddenHintMarker' |
Positions the marker on the page. The positioning is absulute | setPosition: (rect) ->
@markerElement.style.left = rect.left + 'px'
@markerElement.style.top = rect.top + 'px' |
Assigns hint string to the marker | setHint: (@hintChars) -> |
number of hint chars that have been matched so far | @enteredHintChars = ''
document = @element.ownerDocument
while @markerElement.hasChildNodes()
@markerElement.removeChild @markedElement.firstChild
fragment = document.createDocumentFragment()
for char in @hintChars
span = document.createElement 'span'
span.className = 'VimFxReset'
span.textContent = char.toUpperCase()
fragment.appendChild span
@markerElement.appendChild fragment |
Add another char to the | matchHintChar: (char) -> |
Handle backspace key by removing a previously entered hint char and resetting its class | if char == 'backspace'
if @enteredHintChars.length > 0
@enteredHintChars = @enteredHintChars.slice(0, -1)
@markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset' |
Otherwise append hint char and change hint class | else
@markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset VimFxCharMatch'
@enteredHintChars += char |
If entered hint chars no longer partially match the hint chars then hide the marker. Othersie show it back | if @hintChars.search(@enteredHintChars) == 0 then @show() else @hide() |
Checks if enterd hint chars completely match the hint chars | isMatched: ->
return @hintChars == @enteredHintChars |
Selects all markable elements on the page, creates markers for each of them The markers are then positioned on the page The array of markers is returned | Marker.createMarkers = (document) ->
hintChars = getPref 'hint_chars'
elementsSet = getMarkableElements(document)
markers = [];
j = 0
for i in [0...elementsSet.snapshotLength] by 1
element = elementsSet.snapshotItem(i)
if rect = getElementRect element
hint = indexToHint(j++, hintChars)
marker = new Marker(element)
marker.setPosition rect
marker.setHint hint
markers.push(marker)
return markers |
Function generator that creates a function that returns hint string for supplied numeric index. | indexToHint = do -> |
Helper function that returns a permutation number | f = (i, chars) ->
return '' if i < 0
n = chars.length
l = Math.floor(i / n); k = i % n;
return f(l - 1, chars) + chars[k]
return (i, chars) -> |
split the characters into two groups:
| left = chars[...chars.length / 3]
right = chars[chars.length / 3...]
n = Math.floor(i / left.length)
m = i % left.length
return f(n - 1, right) + left[m]
|
Returns elements that qualify for hint markers in hints mode. Generates and memoizes an XPath query internally | getMarkableElements = do -> |
Some preparations done on startup | elements = Array.concat \
MARKABLE_ELEMENTS,
["*[#{ MARKABLE_ELEMENT_PROPERTIES.join(" or ") }]"]
xpath = elements.reduce((m, rule) ->
m.concat(["//#{ rule }", "//xhtml:#{ rule }"])
, []).join(' | ')
namespaceResolver = (namespace) ->
if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null |
The actual function that will return the desired elements | return (document, resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) ->
document.evaluate xpath, document.documentElement, namespaceResolver, resultType, null |
Checks if the given TextRectangle object qualifies
for its own Marker with respect to the | isRectOk = (rect, window) ->
rect.width > 2 and rect.height > 2 and \
rect.top > -2 and rect.left > -2 and \
rect.top < window.innerHeight - 2 and \
rect.left < window.innerWidth - 2 |
Will scan through The logic has been copied over from Vimiun | getElementRect = (element) ->
document = element.ownerDocument
window = document.defaultView
docElem = document.documentElement
body = document.body
clientTop = docElem.clientTop || body.clientTop || 0;
clientLeft = docElem.clientLeft || body.clientLeft || 0;
scrollTop = window.pageYOffset || docElem.scrollTop;
scrollLeft = window.pageXOffset || docElem.scrollLeft;
rects = [rect for rect in element.getClientRects()]
rects.push element.getBoundingClientRect()
for rect in rects
if isRectOk rect, window
return {
top: rect.top + scrollTop - clientTop
left: rect.left + scrollLeft - clientLeft
width: rect.width
height: rect.height
} |
If the element has 0 dimentions then check what's inside. Floated or absolutely positioned elements are of particular interest | for rect in rects
if rect.width == 0 or rect.height == 0
for childElement in element.children
computedStyle = window.getComputedStyle childElement, null
if computedStyle.getPropertyValue 'float' != 'none' or \
computedStyle.getPropertyValue 'position' == 'absolute'
childRect if childRect = getElementRect childElement
return undefined
exports.Marker = Marker
|