Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
b3f9be06
Commit
b3f9be06
authored
Jun 06, 2015
by
Robert Speicher
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor and spec BlobView JS
parent
74a6732c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
365 additions
and
74 deletions
+365
-74
app/assets/javascripts/blob/blob.js.coffee
app/assets/javascripts/blob/blob.js.coffee
+185
-72
app/views/shared/_file_highlight.html.haml
app/views/shared/_file_highlight.html.haml
+2
-2
spec/javascripts/blob/blob_spec.js.coffee
spec/javascripts/blob/blob_spec.js.coffee
+169
-0
spec/javascripts/fixtures/blob.html.haml
spec/javascripts/fixtures/blob.html.haml
+9
-0
No files found.
app/assets/javascripts/blob/blob.js.coffee
View file @
b3f9be06
# BlobView
#
# Handles single- and multi-line selection and highlight for blob views.
#
#= require jquery.scrollTo
#
# ### Example Markup
#
# <div id="tree-content-holder">
# <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>
class
@
BlobView
constructor
:
->
# handle multi-line select
handleMultiSelect
=
(
e
)
->
[
first_line
,
last_line
]
=
parseSelectedLines
()
[
line_number
]
=
parseSelectedLines
(
$
(
this
).
attr
(
"id"
))
hash
=
"L
#{
line_number
}
"
if
e
.
shiftKey
and
not
isNaN
(
first_line
)
and
not
isNaN
(
line_number
)
if
line_number
<
first_line
last_line
=
first_line
first_line
=
line_number
else
last_line
=
line_number
hash
=
if
first_line
==
last_line
then
"L
#{
first_line
}
"
else
"L
#{
first_line
}
-
#{
last_line
}
"
setHash
(
hash
)
e
.
preventDefault
()
# See if there are lines selected
# "#L12" and "#L34-56" supported
highlightBlobLines
=
(
e
)
->
[
first_line
,
last_line
]
=
parseSelectedLines
()
unless
isNaN
first_line
$
(
"#tree-content-holder .highlight .line"
).
removeClass
(
"hll"
)
$
(
"#LC
#{
line
}
"
).
addClass
(
"hll"
)
for
line
in
[
first_line
..
last_line
]
$
.
scrollTo
(
"#L
#{
first_line
}
"
,
offset
:
-
50
)
unless
e
?
# parse selected lines from hash
# always return first and last line (initialized to NaN)
parseSelectedLines
=
(
str
)
->
first_line
=
NaN
last_line
=
NaN
hash
=
str
||
window
.
location
.
hash
if
hash
isnt
""
matches
=
hash
.
match
(
/\#?L(\d+)(\-(\d+))?/
)
first_line
=
parseInt
(
matches
?
[
1
])
last_line
=
parseInt
(
matches
?
[
3
])
last_line
=
first_line
if
isNaN
(
last_line
)
[
first_line
,
last_line
]
setHash
=
(
hash
)
->
hash
=
hash
.
replace
(
/^\#/
,
""
)
nodes
=
$
(
"#"
+
hash
)
# if any nodes are using this id, they must be temporarily changed
# also, add a temporary div at the top of the screen to prevent scrolling
if
nodes
.
length
>
0
scroll_top
=
$
(
document
).
scrollTop
()
nodes
.
attr
(
"id"
,
""
)
tmp
=
$
(
"<div></div>"
)
.
css
({
position
:
"absolute"
,
visibility
:
"hidden"
,
top
:
scroll_top
+
"px"
})
.
attr
(
"id"
,
hash
)
.
appendTo
(
document
.
body
)
window
.
location
.
hash
=
hash
# restore the nodes
if
nodes
.
length
>
0
tmp
.
remove
()
nodes
.
attr
(
"id"
,
hash
)
# initialize multi-line select
$
(
"#tree-content-holder .line-numbers a[id^=L]"
).
on
(
"click"
,
handleMultiSelect
)
# Highlight the correct lines on load
highlightBlobLines
()
# Highlight the correct lines when the hash part of the URL changes
$
(
window
).
on
(
"hashchange"
,
highlightBlobLines
)
# Internal copy of location.hash so we're not dependent on `location` in tests
@
_hash
=
''
# Initialize a BlobView object
#
# hash - String URL hash for dependency injection in tests
constructor
:
(
hash
=
location
.
hash
)
->
@
_hash
=
hash
@
bindEvents
()
unless
hash
==
''
range
=
@
hashToRange
(
hash
)
unless
isNaN
(
range
[
0
])
@
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
:
->
$
(
'#tree-content-holder'
).
on
'mousedown'
,
'a[data-line-number]'
,
@
clickHandler
# 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.
$
(
'#tree-content-holder'
).
on
'click'
,
'a[data-line-number]'
,
(
event
)
->
event
.
preventDefault
()
clickHandler
:
(
event
)
=>
event
.
preventDefault
()
lineNumber
=
$
(
event
.
target
).
data
(
'line-number'
)
current
=
@
hashToRange
(
@
_hash
)
# Unhighlight previously highlighted lines
$
(
'.hll'
).
removeClass
(
'hll'
)
if
isNaN
(
current
[
0
])
or
!
event
.
shiftKey
# 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
)
# Convert a URL hash String into line numbers
#
# hash - Hash String
#
# Examples:
#
# hashToRange('#L5') # => [5, NaN]
# hashToRange('#L5-15') # => [5, 15]
# hashToRange('#foo') # => [NaN, NaN]
#
# Returns an Array
hashToRange
:
(
hash
)
->
first
=
parseInt
(
hash
.
replace
(
/^#L(\d+)/
,
'$1'
))
last
=
parseInt
(
hash
.
replace
(
/^#L\d+-(\d+)/
,
'$1'
))
[
first
,
last
]
# Highlight a single line
#
# lineNumber - Number to highlight. Must be parsable as an Integer.
#
# Returns undefined if lineNumber is not parsable as an Integer.
highlightLine
:
(
lineNumber
)
->
return
if
isNaN
(
parseInt
(
lineNumber
))
$
(
"#LC
#{
lineNumber
}
"
).
addClass
(
'hll'
)
# Highlight all lines within a range
#
# range - An Array of starting and ending line numbers.
#
# Examples:
#
# # Highlight lines 5 through 15
# highlightRange([5, 15])
#
# # The first value is required, and must be a number
# highlightRange(['foo', 15]) # Invalid, returns undefined
# highlightRange([NaN, NaN]) # Invalid, returns undefined
#
# # The second value is optional; if omitted, only highlights the first line
# highlightRange([5, NaN]) # Valid
#
# Returns undefined if the first line is NaN.
highlightRange
:
(
range
)
->
return
if
isNaN
(
range
[
0
])
if
isNaN
(
range
[
1
])
@
highlightLine
(
range
[
0
])
else
for
lineNumber
in
[
range
[
0
]..
range
[
1
]]
@
highlightLine
(
lineNumber
)
setHash
:
(
firstLineNumber
,
lastLineNumber
)
=>
return
if
isNaN
(
parseInt
(
firstLineNumber
))
if
isNaN
(
parseInt
(
lastLineNumber
))
hash
=
"#L
#{
firstLineNumber
}
"
else
hash
=
"#L
#{
firstLineNumber
}
-
#{
lastLineNumber
}
"
@
setHashWithoutScroll
(
hash
)
# Prevents the page from scrolling when `location.hash` is set
#
# This is accomplished by removing the `id` attribute of the matching element,
# creating a temporary div at the top of the current viewport, setting the
# hash, and then removing the div and restoring the `id` attribute.
#
# See http://stackoverflow.com/a/1489802/223897
#
# FIXME (rspeicher): This is still super buggy for me.
setHashWithoutScroll
:
(
hash
)
->
@
_hash
=
hash
# Extract the first ID, in case we were given a range
firstID
=
hash
.
replace
(
/-\d+$/
,
''
)
$node
=
$
(
firstID
)
$node
.
removeAttr
(
'id'
)
$tmp
=
$
(
'<div></div>'
)
.
css
(
position
:
'absolute'
top
:
"
#{
$
(
window
).
scrollTop
()
}
px"
visibility
:
'hidden'
)
.
attr
(
'id'
,
firstID
)
.
appendTo
(
$
(
'body'
))
@
__setLocationHash__
(
hash
)
$tmp
.
remove
()
$node
.
attr
(
'id'
,
firstID
)
# Make the actual `location.hash` change
#
# This method is stubbed in tests.
__setLocationHash__
:
(
value
)
->
location
.
hash
=
value
app/views/shared/_file_highlight.html.haml
View file @
b3f9be06
...
...
@@ -4,8 +4,8 @@
-
blob
.
data
.
lines
.
to_a
.
size
.
times
do
|
index
|
-
offset
=
defined?
(
first_line_number
)
?
first_line_number
:
1
-
i
=
index
+
offset
/
We're not using `link_to` because it is too slow once we get to thousands of lines.
%a
{
href:
"#L#{i}"
,
id:
"L#{i}"
,
rel:
"#L#{i}"
}
-#
We're not using `link_to` because it is too slow once we get to thousands of lines.
%a
{
href:
"#L#{i}"
,
id:
"L#{i}"
,
'data-line-number'
=>
i
}
%i
.fa.fa-link
=
i
:preserve
...
...
spec/javascripts/blob/blob_spec.js.coffee
0 → 100644
View file @
b3f9be06
#= require blob/blob
describe
'BlobView'
,
->
fixture
.
preload
(
'blob.html'
)
clickLine
=
(
number
,
eventData
=
{})
->
if
$
.
isEmptyObject
(
eventData
)
$
(
"#L
#{
number
}
"
).
mousedown
().
click
()
else
e
=
$
.
Event
'mousedown'
,
eventData
$
(
"#L
#{
number
}
"
).
trigger
(
e
).
click
()
beforeEach
->
fixture
.
load
(
'blob.html'
)
@
class
=
new
BlobView
()
@
spies
=
{
__setLocationHash__
:
spyOn
(
@
class
,
'__setLocationHash__'
).
and
.
callFake
->
}
describe
'behavior'
,
->
it
'highlights one line given in the URL hash'
,
->
new
BlobView
(
'#L13'
)
expect
(
$
(
'#LC13'
)).
toHaveClass
(
'hll'
)
it
'highlights a range of lines given in the URL hash'
,
->
new
BlobView
(
'#L5-25'
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
21
)
expect
(
$
(
"#LC
#{
line
}
"
)).
toHaveClass
(
'hll'
)
for
line
in
[
5
..
25
]
it
'scrolls to the first highlighted line on initial load'
,
->
spy
=
spyOn
(
$
,
'scrollTo'
)
new
BlobView
(
'#L5-25'
)
expect
(
spy
).
toHaveBeenCalledWith
(
'#L5'
,
jasmine
.
anything
())
it
'discards click events'
,
->
spy
=
spyOnEvent
(
'a[data-line-number]'
,
'click'
)
clickLine
(
13
)
expect
(
spy
).
toHaveBeenPrevented
()
it
'handles garbage input from the hash'
,
->
func
=
->
new
BlobView
(
'#tree-content-holder'
)
expect
(
func
).
not
.
toThrow
()
describe
'#clickHandler'
,
->
it
'discards the mousedown event'
,
->
spy
=
spyOnEvent
(
'a[data-line-number]'
,
'mousedown'
)
clickLine
(
13
)
expect
(
spy
).
toHaveBeenPrevented
()
describe
'without shiftKey'
,
->
it
'highlights one line when clicked'
,
->
clickLine
(
13
)
expect
(
$
(
'#LC13'
)).
toHaveClass
(
'hll'
)
it
'unhighlights previously highlighted lines'
,
->
clickLine
(
13
)
clickLine
(
20
)
expect
(
$
(
'#LC13'
)).
not
.
toHaveClass
(
'hll'
)
expect
(
$
(
'#LC20'
)).
toHaveClass
(
'hll'
)
it
'sets the hash'
,
->
spy
=
spyOn
(
@
class
,
'setHash'
).
and
.
callThrough
()
clickLine
(
13
)
expect
(
spy
).
toHaveBeenCalledWith
(
13
)
describe
'with shiftKey'
,
->
it
'sets the hash'
,
->
spy
=
spyOn
(
@
class
,
'setHash'
).
and
.
callThrough
()
clickLine
(
13
)
clickLine
(
20
,
shiftKey
:
true
)
expect
(
spy
).
toHaveBeenCalledWith
(
13
)
expect
(
spy
).
toHaveBeenCalledWith
(
13
,
20
)
describe
'without existing highlight'
,
->
it
'highlights the clicked line'
,
->
clickLine
(
13
,
shiftKey
:
true
)
expect
(
$
(
'#LC13'
)).
toHaveClass
(
'hll'
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
1
)
it
'sets the hash'
,
->
spy
=
spyOn
(
@
class
,
'setHash'
)
clickLine
(
13
,
shiftKey
:
true
)
expect
(
spy
).
toHaveBeenCalledWith
(
13
)
describe
'with existing single-line highlight'
,
->
it
'uses existing line as last line when target is lesser'
,
->
clickLine
(
20
)
clickLine
(
15
,
shiftKey
:
true
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
6
)
expect
(
$
(
"#LC
#{
line
}
"
)).
toHaveClass
(
'hll'
)
for
line
in
[
15
..
20
]
it
'uses existing line as first line when target is greater'
,
->
clickLine
(
5
)
clickLine
(
10
,
shiftKey
:
true
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
6
)
expect
(
$
(
"#LC
#{
line
}
"
)).
toHaveClass
(
'hll'
)
for
line
in
[
5
..
10
]
describe
'with existing multi-line highlight'
,
->
beforeEach
->
clickLine
(
10
,
shiftKey
:
true
)
clickLine
(
13
,
shiftKey
:
true
)
it
'uses target as first line when it is less than existing first line'
,
->
clickLine
(
5
,
shiftKey
:
true
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
6
)
expect
(
$
(
"#LC
#{
line
}
"
)).
toHaveClass
(
'hll'
)
for
line
in
[
5
..
10
]
it
'uses target as last line when it is greater than existing first line'
,
->
clickLine
(
15
,
shiftKey
:
true
)
expect
(
$
(
'.hll'
).
length
).
toBe
(
6
)
expect
(
$
(
"#LC
#{
line
}
"
)).
toHaveClass
(
'hll'
)
for
line
in
[
10
..
15
]
describe
'#hashToRange'
,
->
beforeEach
->
@
subject
=
@
class
.
hashToRange
it
'extracts a single line number from the hash'
,
->
expect
(
@
subject
(
'#L5'
)).
toEqual
([
5
,
NaN
])
it
'extracts a range of line numbers from the hash'
,
->
expect
(
@
subject
(
'#L5-15'
)).
toEqual
([
5
,
15
])
it
'returns [NaN, NaN] when the hash is not a line number'
,
->
expect
(
@
subject
(
'#foo'
)).
toEqual
([
NaN
,
NaN
])
describe
'#highlightLine'
,
->
beforeEach
->
@
subject
=
@
class
.
highlightLine
it
'highlights the specified line'
,
->
@
subject
(
13
)
expect
(
$
(
'#LC13'
)).
toHaveClass
(
'hll'
)
it
'accepts a String-based number'
,
->
@
subject
(
'13'
)
expect
(
$
(
'#LC13'
)).
toHaveClass
(
'hll'
)
it
'returns undefined when given NaN'
,
->
expect
(
@
subject
(
NaN
)).
toBe
(
undefined
)
expect
(
@
subject
(
'foo'
)).
toBe
(
undefined
)
describe
'#highlightRange'
,
->
beforeEach
->
@
subject
=
@
class
.
highlightRange
it
'returns undefined when first line is NaN'
,
->
expect
(
@
subject
([
NaN
,
15
])).
toBe
(
undefined
)
expect
(
@
subject
([
'foo'
,
15
])).
toBe
(
undefined
)
it
'returns undefined when given an invalid first line'
,
->
expect
(
@
subject
([
'foo'
,
15
])).
toBe
(
undefined
)
expect
(
@
subject
([
NaN
,
NaN
])).
toBe
(
undefined
)
expect
(
@
subject
(
'foo'
)).
toBe
(
undefined
)
describe
'#setHash'
,
->
beforeEach
->
@
subject
=
@
class
.
setHash
it
'returns undefined when given an invalid first line'
,
->
expect
(
@
subject
(
'foo'
,
15
)).
toBe
(
undefined
)
it
'sets the location hash for a single line'
,
->
@
subject
(
5
)
expect
(
@
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'#L5'
)
it
'sets the location hash for a range'
,
->
@
subject
(
5
,
15
)
expect
(
@
spies
.
__setLocationHash__
).
toHaveBeenCalledWith
(
'#L5-15'
)
spec/javascripts/fixtures/blob.html.haml
0 → 100644
View file @
b3f9be06
#tree-content-holder
.file-content
.line-numbers
-
1
.
upto
(
25
)
do
|
i
|
%a
{
href:
"#L#{i}"
,
id:
"L#{i}"
,
'data-line-number'
=>
i
}=
i
%pre
.code.highlight
%code
-
1
.
upto
(
25
)
do
|
i
|
%span
.line
{
id:
"LC#{i}"
}=
"Line
#{
i
}
"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment