]> git.gir.st - VimFx.git/blob - extension/packages/mode-hints/marker.coffee
Merge branch 'develop' into proper-modes
[VimFx.git] / extension / packages / mode-hints / marker.coffee
1 { SerializableBloomFilter
2 , DummyBloomFilter } = require 'mode-hints/bloomfilter'
3
4 { getPref } = require 'prefs'
5
6 HTMLDocument = Ci.nsIDOMHTMLDocument
7 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
8
9 Z_INDEX_START = 99999999 # The highest `z-index` used in style.css
10 _zIndex = Z_INDEX_START
11 # Each marker will get a `z-index` of `_zIndex++`. In theory, `z-index` can be infinitely large. In
12 # practice, Firefox uses a 32-bit signed integer to store it, so the maximum value is 2147483647
13 # (http://www.puidokas.com/max-z-index/). However, we do not need to worry about hitting the limit,
14 # since the user would have to browse through a bit more than 2 billion links in a single Firefox
15 # session before that happens.
16
17 realBloomFilter = new SerializableBloomFilter('hints_bloom_data', 256 * 32, 16)
18 dummyBloomFilter = new DummyBloomFilter()
19
20 # Wraps the markable element and provides methods to manipulate the markers
21 class Marker
22 # Creates the marker DOM node
23 constructor: (@element) ->
24 document = @element.ownerDocument
25 window = document.defaultView
26 @markerElement = document.createElement('div')
27 @markerElement.className = 'VimFxReset VimFxHintMarker'
28
29 Object.defineProperty this, 'bloomFilter',
30 get: -> if getPref('hints_bloom_on') then realBloomFilter else dummyBloomFilter
31
32 show: -> @setVisibility(true)
33 hide: -> @setVisibility(false)
34 setVisibility: (visible) ->
35 method = if visible then 'remove' else 'add'
36 @markerElement.classList[method]('VimFxHiddenHintMarker')
37
38 setPosition: (top, left) ->
39 # The positioning is absulute
40 @markerElement.style.top = "#{ top }px"
41 @markerElement.style.left = "#{ left }px"
42
43 # For quick access
44 @position = {top, left}
45
46 # Each marker gets a unique `z-index`, so that it can be determined if a marker overlaps another.
47 @markerElement.style.setProperty('z-index', _zIndex++, 'important')
48
49 # To be called when the marker has been both assigned a hint and inserted into the DOM, and thus
50 # gotten a height and width.
51 completePosition: ->
52 {
53 position: { top, left }
54 markerElement: { offsetHeight: height, offsetWidth: width }
55 } = this
56 @position = {top, bottom: top + height, left, right: left + width, height, width}
57
58 setHint: (@hintChars) ->
59 # Hint chars that have been matched so far
60 @enteredHintChars = ''
61
62 document = @element.ownerDocument
63
64 while @markerElement.hasChildNodes()
65 @markerElement.removeChild(@markerElement.firstChild)
66
67 fragment = document.createDocumentFragment()
68 for char in @hintChars
69 span = document.createElement('span')
70 span.className = 'VimFxReset'
71 span.textContent = char.toUpperCase()
72 fragment.appendChild(span)
73
74 @markerElement.appendChild(fragment)
75
76 matchHintChar: (char) ->
77 @updateEnteredHintChars(char)
78
79 deleteHintChar: ->
80 @updateEnteredHintChars(false)
81
82 updateEnteredHintChars: (char) ->
83 if char == false
84 method = 'remove'
85 @enteredHintChars = @enteredHintChars[...-1]
86 offset = 0
87 else
88 method = 'add'
89 @enteredHintChars += char.toLowerCase()
90 offset = -1
91
92 @markerElement.children[@enteredHintChars.length + offset]?.classList[method]('VimFxCharMatch')
93 if @hintChars.startsWith(@enteredHintChars) then @show() else @hide()
94
95 isMatched: ->
96 return @hintChars == @enteredHintChars
97
98 reset: ->
99 @setHint(@hintChars)
100 @show()
101
102 # Returns string features of the element that can be used in the bloom filter
103 # in order to add relevance to the hint marker
104 extractBloomFeatures: ->
105 features = {}
106
107 # Class name of an element (walks up the node tree to find first element with at least one class)
108 suffix = ''
109 el = @element
110 while el.classList?.length == 0 and el not instanceof HTMLDocument
111 suffix += " #{ el.tagName }"
112 el = el.parentNode
113 if el?.classList?
114 for className in el.classList
115 features["#{ el.tagName }.#{ className }#{ suffix }"] = 10
116
117 if @element.id
118 features["#{ el.tagName }.#{ @element.id }"] = 5
119
120 if @element instanceof HTMLAnchorElement
121 features["a"] = 20 # Reward links no matter what
122 features["#{ el.tagName }.#{ @element.href }"] = 60
123 features["#{ el.tagName }.#{ @element.title }"] = 40
124
125 return features
126
127 # Returns rating of all present bloom features (plus 1)
128 calcBloomRating: ->
129 rating = 1
130 for feature, weight of @extractBloomFeatures()
131 rating += if @bloomFilter.test(feature) then weight else 0
132
133 return rating
134
135 reward: ->
136 for feature, weight of @extractBloomFeatures()
137 @bloomFilter.add(feature)
138 @bloomFilter.save()
139
140 exports.Marker = Marker
Imprint / Impressum