]> git.gir.st - VimFx.git/blob - extension/packages/mode-hints/marker.coffee
Try points one pixel into the elements from the edges
[VimFx.git] / extension / packages / mode-hints / marker.coffee
1 { createElement } = require 'utils'
2 { SerializableBloomFilter
3 , DummyBloomFilter } = require 'mode-hints/bloomfilter'
4
5 { getPref } = require 'prefs'
6
7 HTMLDocument = Ci.nsIDOMHTMLDocument
8 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
9
10 realBloomFilter = new SerializableBloomFilter('hints_bloom_data', 256 * 32, 16)
11 dummyBloomFilter = new DummyBloomFilter()
12
13 # Wraps the markable element and provides methods to manipulate the markers.
14 class Marker
15 # Creates the marker DOM node.
16 constructor: (@element, @elementShape) ->
17 document = @element.ownerDocument
18 @markerElement = createElement(document, 'div', {class: 'VimFxHintMarker'})
19
20 Object.defineProperty this, 'bloomFilter',
21 get: -> if getPref('hints_bloom_on') then realBloomFilter else dummyBloomFilter
22
23 show: -> @setVisibility(true)
24 hide: -> @setVisibility(false)
25 setVisibility: (visible) ->
26 @markerElement.classList.toggle('VimFxHiddenHintMarker', !visible)
27 updateVisibility: ->
28 if @hintChars.startsWith(@enteredHintChars) then @show() else @hide()
29
30 # To be called when the marker has been both assigned a hint and inserted
31 # into the DOM, and thus gotten a height and width.
32 setPosition: (viewport) ->
33 {
34 markerElement: { offsetHeight: height, offsetWidth: width }
35 elementShape: { nonCoveredPoint: { x: left, y: top, offset, rect } }
36 } = this
37
38 # The `nonCoveredPoint` is 1px from the edges of the element (see
39 # hints.coffee). It looks nicer if the marker is placed exactly at the
40 # edge, though.
41 left -= 1
42 top -= 1
43
44 # Center the marker vertically on the non-covered point.
45 top -= height / 2
46
47 # Make sure that the marker stays within its element (vertically).
48 top = Math.min(top, rect.bottom - height)
49 top = Math.max(top, rect.top)
50
51 # Make the position relative to the top frame.
52 left += offset.left
53 top += offset.top
54
55 # Make sure that the marker stays within the viewport.
56 left = Math.min(left, viewport.right - width)
57 top = Math.min(top, viewport.bottom - height)
58 left = Math.max(left, viewport.left)
59 top = Math.max(top, viewport.top)
60
61 # Make the position relative to the document, rather than to the viewport.
62 left += viewport.scrollX
63 top += viewport.scrollY
64
65 # The positioning is absolute.
66 @markerElement.style.left = "#{ left }px"
67 @markerElement.style.top = "#{ top }px"
68
69 # For quick access.
70 @position = {
71 left, right: left + width,
72 top, bottom: top + height,
73 height, width
74 }
75
76 setHint: (@hintChars) ->
77 # Hint chars that have been matched so far.
78 @enteredHintChars = ''
79
80 document = @element.ownerDocument
81
82 while @markerElement.hasChildNodes()
83 @markerElement.firstChild.remove()
84
85 fragment = document.createDocumentFragment()
86 for char in @hintChars
87 charContainer = createElement(document, 'span')
88 charContainer.textContent = char.toUpperCase()
89 fragment.appendChild(charContainer)
90
91 @markerElement.appendChild(fragment)
92
93 matchHintChar: (char) ->
94 @toggleLastHintChar(true)
95 @enteredHintChars += char.toLowerCase()
96 @updateVisibility()
97
98 deleteHintChar: ->
99 @enteredHintChars = @enteredHintChars[...-1]
100 @toggleLastHintChar(false)
101 @updateVisibility()
102
103 toggleLastHintChar: (visible) ->
104 @markerElement.children[@enteredHintChars.length]
105 ?.classList.toggle('VimFxCharMatch', visible)
106
107 isMatched: ->
108 return @hintChars == @enteredHintChars
109
110 reset: ->
111 @setHint(@hintChars)
112 @show()
113
114 # Returns string features of the element that can be used in the bloom filter
115 # in order to add relevance to the hint marker.
116 extractBloomFeatures: ->
117 features = {}
118
119 # Class name of an element (walks up the node tree to find first element
120 # with at least one class).
121 suffix = ''
122 el = @element
123 while el.classList?.length == 0 and el not instanceof HTMLDocument
124 suffix += " #{ el.tagName }"
125 el = el.parentNode
126 if el?.classList?
127 for className in el.classList
128 features["#{ el.tagName }.#{ className }#{ suffix }"] = 10
129
130 if @element.id
131 features["#{ el.tagName }.#{ @element.id }"] = 5
132
133 if @element instanceof HTMLAnchorElement
134 features["a"] = 20 # Reward links no matter what.
135 features["#{ el.tagName }.#{ @element.href }"] = 60
136 features["#{ el.tagName }.#{ @element.title }"] = 40
137
138 return features
139
140 # Returns rating of all present bloom features (plus 1).
141 calcBloomRating: ->
142 rating = 1
143 for feature, weight of @extractBloomFeatures()
144 rating += if @bloomFilter.test(feature) then weight else 0
145
146 return rating
147
148 reward: ->
149 for feature, weight of @extractBloomFeatures()
150 @bloomFilter.add(feature)
151 @bloomFilter.save()
152
153 exports.Marker = Marker
Imprint / Impressum