gitlab-org--gitlab-foss/app/assets/javascripts/line_highlighter.js.coffee

149 lines
4.3 KiB
CoffeeScript
Raw Normal View History

2015-06-15 17:53:39 -04:00
# LineHighlighter
2015-06-06 23:15:32 -04:00
#
# Handles single- and multi-line selection and highlight for blob views.
#
#= require jquery.scrollTo
#
# ### Example Markup
#
# <div id="blob-content-holder">
2015-06-06 23:15:32 -04:00
# <div class="file-content">
# <div class="line-numbers">
# <a href="#L1" id="L1" data-line-number="1">1</a>
# <a href="#L2" id="L2" data-line-number="2">2</a>
# <a href="#L3" id="L3" data-line-number="3">3</a>
# <a href="#L4" id="L4" data-line-number="4">4</a>
# <a href="#L5" id="L5" data-line-number="5">5</a>
# </div>
# <pre class="code highlight">
# <code>
# <span id="LC1" class="line">...</span>
# <span id="LC2" class="line">...</span>
# <span id="LC3" class="line">...</span>
# <span id="LC4" class="line">...</span>
# <span id="LC5" class="line">...</span>
# </code>
# </pre>
# </div>
# </div>
2015-06-19 02:01:53 -04:00
#
2015-06-15 17:53:39 -04:00
class @LineHighlighter
2015-06-19 02:01:53 -04:00
# CSS class applied to highlighted lines
highlightClass: 'hll'
2015-06-06 23:15:32 -04:00
# Internal copy of location.hash so we're not dependent on `location` in tests
2015-06-19 02:01:53 -04:00
_hash: ''
2015-06-06 23:15:32 -04:00
2015-06-15 17:53:39 -04:00
# Initialize a LineHighlighter object
2015-06-06 23:15:32 -04:00
#
# hash - String URL hash for dependency injection in tests
constructor: (hash = location.hash) ->
@_hash = hash
@bindEvents()
unless hash == ''
range = @hashToRange(hash)
2015-06-19 02:01:53 -04:00
if range[0]
2015-06-06 23:15:32 -04:00
@highlightRange(range)
# Scroll to the first highlighted line on initial load
# Offset -50 for the sticky top bar, and another -100 for some context
$.scrollTo("#L#{range[0]}", offset: -150)
bindEvents: ->
$('#blob-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
2015-06-06 23:15:32 -04:00
# While it may seem odd to bind to the mousedown event and then throw away
# the click event, there is a method to our madness.
#
# If not done this way, the line number anchor will sometimes keep its
# active state even when the event is cancelled, resulting in an ugly border
# around the link and/or a persisted underline text decoration.
$('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
2015-06-06 23:15:32 -04:00
event.preventDefault()
clickHandler: (event) =>
event.preventDefault()
@clearHighlight()
lineNumber = $(event.target).closest('a').data('line-number')
2015-06-06 23:15:32 -04:00
current = @hashToRange(@_hash)
2015-06-19 16:43:09 -04:00
unless current[0] && event.shiftKey
2015-06-06 23:15:32 -04:00
# If there's no current selection, or there is but Shift wasn't held,
# treat this like a single-line selection.
@setHash(lineNumber)
@highlightLine(lineNumber)
else if event.shiftKey
if lineNumber < current[0]
range = [lineNumber, current[0]]
else
range = [current[0], lineNumber]
@setHash(range[0], range[1])
@highlightRange(range)
# Unhighlight previously highlighted lines
clearHighlight: ->
2015-06-19 02:01:53 -04:00
$(".#{@highlightClass}").removeClass(@highlightClass)
2015-06-06 23:15:32 -04:00
# Convert a URL hash String into line numbers
#
# hash - Hash String
#
# Examples:
#
2015-06-19 02:01:53 -04:00
# hashToRange('#L5') # => [5, null]
2015-06-06 23:15:32 -04:00
# hashToRange('#L5-15') # => [5, 15]
2015-06-19 02:01:53 -04:00
# hashToRange('#foo') # => [null, null]
2015-06-06 23:15:32 -04:00
#
# Returns an Array
hashToRange: (hash) ->
2015-06-19 02:01:53 -04:00
matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
2015-06-19 16:43:09 -04:00
if matches && matches.length
2015-06-19 02:01:53 -04:00
first = parseInt(matches[1])
2015-06-19 16:43:09 -04:00
last = if matches[2] then parseInt(matches[2]) else null
2015-06-06 23:15:32 -04:00
2015-06-19 02:01:53 -04:00
[first, last]
else
[null, null]
2015-06-06 23:15:32 -04:00
# Highlight a single line
#
2015-06-19 02:01:53 -04:00
# lineNumber - Line number to highlight
highlightLine: (lineNumber) =>
$("#LC#{lineNumber}").addClass(@highlightClass)
2015-06-06 23:15:32 -04:00
# Highlight all lines within a range
#
2015-06-19 02:01:53 -04:00
# range - Array containing the starting and ending line numbers
2015-06-06 23:15:32 -04:00
highlightRange: (range) ->
2015-06-19 02:01:53 -04:00
if range[1]
2015-06-06 23:15:32 -04:00
for lineNumber in [range[0]..range[1]]
@highlightLine(lineNumber)
2015-06-19 02:01:53 -04:00
else
@highlightLine(range[0])
2015-06-06 23:15:32 -04:00
2015-06-19 02:01:53 -04:00
# Set the URL hash string
2015-06-06 23:15:32 -04:00
setHash: (firstLineNumber, lastLineNumber) =>
2015-06-19 02:01:53 -04:00
if lastLineNumber
2015-06-06 23:15:32 -04:00
hash = "#L#{firstLineNumber}-#{lastLineNumber}"
2015-06-19 02:01:53 -04:00
else
hash = "#L#{firstLineNumber}"
2015-06-06 23:15:32 -04:00
@_hash = hash
@__setLocationHash__(hash)
# Make the actual hash change in the browser
2015-06-06 23:15:32 -04:00
#
# This method is stubbed in tests.
__setLocationHash__: (value) ->
# We're using pushState instead of assigning location.hash directly to
# prevent the page from scrolling on the hashchange event
history.pushState({turbolinks: false, url: value}, document.title, value)