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
|
|
|
|
#
|
2015-10-13 10:41:48 -04:00
|
|
|
# <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: ->
|
2015-10-13 10:41:48 -04:00
|
|
|
$('#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.
|
|
|
|
|
2015-10-13 10:41:48 -04:00
|
|
|
$('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
|
2015-06-06 23:15:32 -04:00
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
clickHandler: (event) =>
|
|
|
|
event.preventDefault()
|
|
|
|
|
2015-06-15 17:31:41 -04:00
|
|
|
@clearHighlight()
|
|
|
|
|
2015-07-17 10:32:17 -04:00
|
|
|
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)
|
|
|
|
|
2015-06-15 17:31:41 -04:00
|
|
|
# Unhighlight previously highlighted lines
|
|
|
|
clearHighlight: ->
|
2015-06-19 02:01:53 -04:00
|
|
|
$(".#{@highlightClass}").removeClass(@highlightClass)
|
2015-06-15 17:31:41 -04:00
|
|
|
|
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)
|
|
|
|
|
2015-06-15 17:31:41 -04:00
|
|
|
# Make the actual hash change in the browser
|
2015-06-06 23:15:32 -04:00
|
|
|
#
|
|
|
|
# This method is stubbed in tests.
|
|
|
|
__setLocationHash__: (value) ->
|
2015-06-15 17:31:41 -04:00
|
|
|
# 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)
|