1 # This file contains an abstraction for hint markers. It creates the UI for a
2 # marker and provides methods to manipulate the markers.
4 utils = require('./utils')
7 # `@wrapper` is a stand-in for the element that the marker represents. See
8 # `MarkerContainer::injectHints` for more information.
9 constructor: (options) ->
11 @wrapper, @document, @viewport, @zoom = 1, @isComplementary = false
13 @elementShape = @wrapper.shape
14 @markerElement = utils.createBox(@document, 'marker')
15 @markerElement.setAttribute('data-type', @wrapper.type)
16 @weight = @wrapper.combinedArea
19 @text = @wrapper.text?.toLowerCase() ? ''
27 @setHint(@originalHint)
31 show: -> @setVisibility(true)
32 hide: -> @setVisibility(false)
34 setVisibility: (@visible) ->
35 @markerElement.classList.toggle('marker--hidden', not @visible)
37 # To be called when the marker has been both assigned a hint and inserted
38 # into the DOM, and thus gotten a width and height.
43 nonCoveredPoint: {x: left, y: top, offset}
46 rect = @markerElement.getBoundingClientRect()
48 # Take movements into account.
52 # Make the position relative to the top frame.
56 # Take the current zoom into account.
61 # Move the marker just to the left of the text of its element, unless the
62 # element is a “block” (see `getElementShape` in markable-elements.coffee)
63 # and the marker wouldn’t cover text.
64 unless isBlock and rect.width < textOffset
65 left += textOffset * @zoom - rect.width
67 # Otherwise make sure that it doesn’t flow outside the right side of its
68 # element. This is to avoid the following situation (where `+` is a small
69 # button, `Link text` is a (larger) link and `DAG` and `E` are the hints
70 # placed on top of them.) This makes it clearer which hint does what.
71 # Example site: Hackernews.
73 # z-layer before after
74 # bottom +Link text +Link text
77 left -= Math.max(0, rect.width - elementWidth * @zoom)
79 # Center the marker vertically on the non-covered point.
80 top -= Math.round(rect.height / 2)
82 # Make sure that the marker stays within the viewport.
83 left = Math.min(left, @zoom * @viewport.right - rect.width)
84 top = Math.min(top, @zoom * @viewport.bottom - rect.height)
85 left = Math.max(left, @zoom * @viewport.left)
86 top = Math.max(top, @zoom * @viewport.top)
88 # Use whole numbers for more deterministic positioning.
89 left = Math.round(left)
92 # The positioning is absolute.
93 @markerElement.style.left = "#{left}px"
94 @markerElement.style.top = "#{top}px"
98 left, right: Math.round(left + rect.width)
99 top, bottom: Math.round(top + rect.height)
102 updatePosition: (@dx, @dy) ->
109 @originalHint ?= @hint
110 @markerElement.textContent = ''
111 fragment = @document.createDocumentFragment()
112 utils.createBox(@document, 'marker-char', fragment, char) for char in @hint
113 @markerElement.appendChild(fragment)
116 return @hint.startsWith(hint)
118 matchText: (strings) ->
119 return strings.every((string) => @text.includes(string))
121 markMatchedPart: (hint) ->
122 matchEnd = if @matchHint(hint) then hint.length else 0
123 for child, index in @markerElement.children
124 child.classList.toggle('marker-char--matched', index < matchEnd)
127 markMatched: (matched) ->
128 @markerElement.classList.toggle('marker--matched', matched)
130 markHighlighted: (@highlighted) ->
131 @markerElement.classList.toggle('marker--highlighted', @highlighted)
133 module.exports = Marker