]> git.gir.st - VimFx.git/blob - extension/packages/marker.coffee
Merge branch 'develop' into hints-overlap-fix
[VimFx.git] / extension / packages / marker.coffee
1 { SerializableBloomFilter
2 , DummyBloomFilter } = require 'bloomfilter'
3
4 { getPref } = require 'prefs'
5
6 HTMLDocument = Ci.nsIDOMHTMLDocument
7 HTMLAnchorElement = Ci.nsIDOMHTMLAnchorElement
8
9 Z_INDEX_START = 99999999 # As set by style.css for the class `.VimFxReset`
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: -> @markerElement.className = 'VimFxReset VimFxHintMarker'
33 hide: -> @markerElement.className = 'VimFxReset VimFxHiddenHintMarker'
34
35 setPosition: (top, left) ->
36 # The positioning is absulute
37 @markerElement.style.top = top + 'px'
38 @markerElement.style.left = left + 'px'
39
40 # For quick access
41 @position = {top, left}
42
43 # Each marker gets a unique `z-index`, so that it can be determined if a marker overlaps another.
44 @markerElement.style.setProperty('z-index', _zIndex++, 'important')
45
46 setHint: (@hintChars) ->
47 # Hint chars that have been matched so far
48 @enteredHintChars = ''
49
50 document = @element.ownerDocument
51
52 while @markerElement.hasChildNodes()
53 @markerElement.removeChild(@markerElement.firstChild)
54
55 fragment = document.createDocumentFragment()
56 for char in @hintChars
57 span = document.createElement('span')
58 span.className = 'VimFxReset'
59 span.textContent = char.toUpperCase()
60 fragment.appendChild(span)
61
62 @markerElement.appendChild(fragment)
63
64 # Add another char to the `enteredHintString`,
65 # see if it still matches `hintString`, apply classes to
66 # the distinct hint characters and show/hide marker when
67 # the entered string partially (not) matches the hint string.
68 matchHintChar: (char) ->
69 if char == 'Backspace'
70 # Handle backspace key by removing a previously entered hint char and resetting its class
71 if @enteredHintChars.length > 0
72 @enteredHintChars = @enteredHintChars[0...-1]
73 @markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset'
74 else
75 # Otherwise append hint char and change hint class
76 @markerElement.children[@enteredHintChars.length]?.className = 'VimFxReset VimFxCharMatch'
77 @enteredHintChars += char.toLowerCase()
78
79 # If entered hint chars no longer partially match the hint chars then hide the marker,
80 # otherwise show it back
81 if @hintChars.search(@enteredHintChars) == 0 then @show() else @hide()
82
83 # Checks if the marker will be matched if the next character entered is `char`
84 willMatch: (char) ->
85 char == 'Backspace' or @hintChars.search(@enteredHintChars + char.toLowerCase()) == 0
86
87 isMatched: ->
88 return @hintChars == @enteredHintChars
89
90 # Returns string features of the element that can be used in the bloom filter
91 # in order to add relevance to the hint marker
92 extractBloomFeatures: ->
93 features = {}
94
95 # Class name of an element (walks up the node tree to find first element with at least one class)
96 suffix = ''
97 el = @element
98 while el.classList?.length == 0 and el not instanceof HTMLDocument
99 suffix = "#{ suffix } #{ el.tagName }"
100 el = el.parentNode
101 if el and el.classList
102 for className in el.classList
103 features["#{ el.tagName }.#{ className }#{ suffix }"] = 10
104
105 # Element id
106 if @element.id
107 features["#{ el.tagName }.#{ @element.id }"] = 5
108
109 if @element instanceof HTMLAnchorElement
110 features["a"] = 20 # Reward links no matter what
111 features["#{ el.tagName }.#{ @element.href }"] = 60
112 features["#{ el.tagName }.#{ @element.title }"] = 40
113
114 return features
115
116 # Returns rating of all present bloom features (plus 1)
117 calcBloomRating: ->
118 rating = 1
119 for feature, weight of @extractBloomFeatures()
120 rating += if @bloomFilter.test(feature) then weight else 0
121
122 return rating
123
124 reward: ->
125 for feature, weight of @extractBloomFeatures()
126 @bloomFilter.add(feature)
127 @bloomFilter.save()
128
129 exports.Marker = Marker
Imprint / Impressum