]> git.gir.st - VimFx.git/blob - extension/packages/marker.coffee
Fix frames handling
[VimFx.git] / extension / packages / marker.coffee
1 { interfaces: Ci } = Components
2 XPathResult = Ci.nsIDOMXPathResult
3
4 # All elements that have one or more of the following properties
5 # qualify for their own marker in hints mode
6 MARKABLE_ELEMENT_PROPERTIES = [
7 "@tabindex"
8 "@onclick"
9 "@onmousedown"
10 "@onmouseup"
11 "@oncommand"
12 "@role='link'"
13 "@role='button'"
14 "contains(@class, 'button')"
15 "@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true'"
16 ]
17
18 # All the following elements qualify for their own marker in hints mode
19 MARKABLE_ELEMENTS = [
20 "a"
21 "iframe"
22 "area[@href]"
23 "textarea"
24 "button"
25 "select"
26 "input[not(@type='hidden' or @disabled or @readonly)]"
27 "embed"
28 "object"
29 ]
30
31 # Marker class wraps the markable element and provides
32 # methods to manipulate the markers
33 class Marker
34 # Creates the marker DOM node
35 constructor: (@element) ->
36 document = @element.ownerDocument
37 window = document.defaultView
38 @markerElement = document.createElement 'div'
39 @markerElement.className = 'VimFxReset VimFxHintMarker'
40
41 # Shows the marker
42 show: -> @markerElement.className = 'VimFxReset VimFxHintMarker'
43
44 # Hides the marker
45 hide: -> @markerElement.className = 'VimFxReset VimFxHiddenHintMarker'
46
47 # Positions the marker on the page. The positioning is absulute
48 setPosition: (rect) ->
49 @markerElement.style.left = rect.left + 'px'
50 @markerElement.style.top = rect.top + 'px'
51
52 # Assigns hint string to the marker
53 setHint: (@hintChars) ->
54 # number of hint chars that have been matched so far
55 @enteredHintChars = ''
56
57 document = @element.ownerDocument
58
59 while @markerElement.hasChildNodes()
60 @markerElement.removeChild @markedElement.firstChild
61
62 fragment = document.createDocumentFragment()
63 for char in @hintChars
64 span = document.createElement 'span'
65 span.className = 'VimFxReset'
66 span.textContent = char.toUpperCase()
67
68 fragment.appendChild span
69
70 @markerElement.appendChild fragment
71
72 # Add another char to the `enteredHintString`,
73 # see if it still matches `hintString`, apply classes to
74 # the distinct hint characters and show/hide marker when
75 # the entered string partially (not) matches the hint string
76 matchHintChar: (char) ->
77 # Handle backspace key by removing a previously entered hint char
78 # and resetting its class
79 if char == 'Backspace'
80 if @enteredHintChars.length > 0
81 @enteredHintChars = @enteredHintChars.slice(0, -1)
82 @markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset'
83 # Otherwise append hint char and change hint class
84 else
85 @markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset VimFxCharMatch'
86 @enteredHintChars += char.toLowerCase()
87
88 # If entered hint chars no longer partially match the hint chars
89 # then hide the marker. Othersie show it back
90 if @hintChars.search(@enteredHintChars) == 0 then @show() else @hide()
91
92 # Checks if the marker will be matched if the next character entered is `char`
93 willMatch: (char) ->
94 char == 'Backspace' or @hintChars.search(@enteredHintChars + char.toLowerCase()) == 0
95
96 # Checks if enterd hint chars completely match the hint chars
97 isMatched: ->
98 return @hintChars == @enteredHintChars
99
100
101 # Selects all markable elements on the page, creates markers
102 # for each of them. The markers are then positioned on the page.
103 # Note that the markers are not given any hints at this point.
104 #
105 # The array of markers is returned
106 Marker.createMarkers = (document) ->
107 set = getMarkableElements(document)
108
109 markers = []
110 for i in [0...set.snapshotLength] by 1
111 element = set.snapshotItem(i)
112 if rect = getElementRect element
113 marker = new Marker(element)
114 marker.setPosition rect
115 marker.weight = rect.area
116 markers.push marker
117
118 return markers
119
120
121 # Returns elements that qualify for hint markers in hints mode.
122 # Generates and memoizes an XPath query internally
123 getMarkableElements = do ->
124 # Some preparations done on startup
125 elements = Array.concat \
126 MARKABLE_ELEMENTS,
127 ["*[#{ MARKABLE_ELEMENT_PROPERTIES.join(" or ") }]"]
128
129 xpath = elements.reduce((m, rule) ->
130 m.concat(["//#{ rule }", "//xhtml:#{ rule }"])
131 , []).join(' | ')
132
133 namespaceResolver = (namespace) ->
134 if (namespace == "xhtml") then "http://www.w3.org/1999/xhtml" else null
135
136 # The actual function that will return the desired elements
137 return (document, resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) ->
138 document.evaluate xpath, document.documentElement, namespaceResolver, resultType, null
139
140 # Checks if the given TextRectangle object qualifies
141 # for its own Marker with respect to the `window` object
142 isRectOk = (rect, window) ->
143 rect.width > 2 and rect.height > 2 and \
144 rect.top > -2 and rect.left > -2 and \
145 rect.top < window.innerHeight - 2 and \
146 rect.left < window.innerWidth - 2
147
148 # Will scan through `element.getClientRects()` and look for
149 # the first visible rectange. If there are no visible rectangles, then
150 # will look at the children of the markable node.
151 #
152 # The logic has been copied over from Vimiun
153 getElementRect = (element) ->
154 document = element.ownerDocument
155 window = document.defaultView
156 docElem = document.documentElement
157 body = document.body
158
159 clientTop = docElem.clientTop || body?.clientTop || 0;
160 clientLeft = docElem.clientLeft || body?.clientLeft || 0;
161 scrollTop = window.pageYOffset || docElem.scrollTop;
162 scrollLeft = window.pageXOffset || docElem.scrollLeft;
163
164 clientRect = element.getBoundingClientRect()
165 rects = [rect for rect in element.getClientRects()]
166 rects.push clientRect
167
168 for rect in rects
169 if isRectOk rect, window
170 return {
171 top: rect.top + scrollTop - clientTop
172 left: rect.left + scrollLeft - clientLeft
173 width: rect.width
174 height: rect.height
175 area: clientRect.width * clientRect.height
176 }
177
178 # If the element has 0 dimentions then check what's inside.
179 # Floated or absolutely positioned elements are of particular interest
180 for rect in rects
181 if rect.width == 0 or rect.height == 0
182 for childElement in element.children
183 if computedStyle = window.getComputedStyle childElement, null
184 if computedStyle.getPropertyValue('float') != 'none' or \
185 computedStyle.getPropertyValue('position') == 'absolute'
186
187 return childRect if childRect = getElementRect childElement
188
189 return undefined
190
191 exports.Marker = Marker
Imprint / Impressum