]> git.gir.st - VimFx.git/blob - extension/lib/marker.coffee
Fix the `yy` command in Reader View
[VimFx.git] / extension / lib / marker.coffee
1 ###
2 # Copyright Anton Khodakivskiy 2012, 2013.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
4 #
5 # This file is part of VimFx.
6 #
7 # VimFx is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # VimFx is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
19 ###
20
21 # This file contains an abstraction for hint markers. It creates the UI for a
22 # marker and provides methods to manipulate the markers.
23
24 utils = require('./utils')
25
26 class Marker
27 # `@wrapper` is a stand-in for the element that the marker represents. See
28 # `MarkerContainer::injectHints` for more information.
29 constructor: (options) ->
30 {
31 @wrapper, @document, @viewport, @zoom = 1, @isComplementary = false
32 } = options
33 @elementShape = @wrapper.shape
34 @markerElement = utils.createBox(@document, 'marker')
35 @markerElement.setAttribute('data-type', @wrapper.type)
36 @weight = @wrapper.combinedArea
37 @hint = ''
38 @originalHint = null
39 @text = @wrapper.text?.toLowerCase() ? ''
40 @visible = true
41 @highlighted = false
42 @position = null
43 @dx = 0
44 @dy = 0
45
46 reset: ->
47 @setHint(@originalHint)
48 @show()
49 @refreshPosition()
50
51 show: -> @setVisibility(true)
52 hide: -> @setVisibility(false)
53
54 setVisibility: (@visible) ->
55 @markerElement.classList.toggle('marker--hidden', not @visible)
56
57 # To be called when the marker has been both assigned a hint and inserted
58 # into the DOM, and thus gotten a width and height.
59 setPosition: ->
60 {
61 textOffset, isBlock
62 width: elementWidth
63 nonCoveredPoint: {x: left, y: top, offset}
64 } = @elementShape
65
66 rect = @markerElement.getBoundingClientRect()
67
68 # Take movements into account.
69 left += @dx
70 top += @dy
71
72 # Make the position relative to the top frame.
73 left += offset.left
74 top += offset.top
75
76 # Take the current zoom into account.
77 left *= @zoom
78 top *= @zoom
79
80 if textOffset?
81 # Move the marker just to the left of the text of its element, unless the
82 # element is a “block” (see `getElementShape` in markable-elements.coffee)
83 # and the marker wouldn’t cover text.
84 unless isBlock and rect.width < textOffset
85 left += textOffset * @zoom - rect.width
86 else
87 # Otherwise make sure that it doesn’t flow outside the right side of its
88 # element. This is to avoid the following situation (where `+` is a small
89 # button, `Link text` is a (larger) link and `DAG` and `E` are the hints
90 # placed on top of them.) This makes it clearer which hint does what.
91 # Example site: Hackernews.
92 #
93 # z-layer before after
94 # bottom +Link text +Link text
95 # middle DAG DAG
96 # top E E
97 left -= Math.max(0, rect.width - elementWidth * @zoom)
98
99 # Center the marker vertically on the non-covered point.
100 top -= Math.round(rect.height / 2)
101
102 # Make sure that the marker stays within the viewport.
103 left = Math.min(left, @zoom * @viewport.right - rect.width)
104 top = Math.min(top, @zoom * @viewport.bottom - rect.height)
105 left = Math.max(left, @zoom * @viewport.left)
106 top = Math.max(top, @zoom * @viewport.top)
107
108 # Use whole numbers for more deterministic positioning.
109 left = Math.round(left)
110 top = Math.round(top)
111
112 # The positioning is absolute.
113 @markerElement.style.left = "#{left}px"
114 @markerElement.style.top = "#{top}px"
115
116 # For quick access.
117 @position = {
118 left, right: Math.round(left + rect.width)
119 top, bottom: Math.round(top + rect.height)
120 }
121
122 updatePosition: (@dx, @dy) ->
123 @setPosition()
124
125 refreshPosition: ->
126 @setPosition()
127
128 setHint: (@hint) ->
129 @originalHint ?= @hint
130 @markerElement.textContent = ''
131 fragment = @document.createDocumentFragment()
132 utils.createBox(@document, 'marker-char', fragment, char) for char in @hint
133 @markerElement.appendChild(fragment)
134
135 matchHint: (hint) ->
136 return @hint.startsWith(hint)
137
138 matchText: (strings) ->
139 return strings.every((string) => @text.includes(string))
140
141 markMatchedPart: (hint) ->
142 matchEnd = if @matchHint(hint) then hint.length else 0
143 for child, index in @markerElement.children
144 child.classList.toggle('marker-char--matched', index < matchEnd)
145 return
146
147 markMatched: (matched) ->
148 @markerElement.classList.toggle('marker--matched', matched)
149
150 markHighlighted: (@highlighted) ->
151 @markerElement.classList.toggle('marker--highlighted', @highlighted)
152
153 module.exports = Marker
Imprint / Impressum