Commit 568e05a73b1c4aac814f75da488d3d1ed7d847e9
1 parent
09d00563
Exists in
master
and in
4 other branches
Enable multiline select for blobs.
Holding shift will allow users to click and select multiple lines. The behavior is similar to GitHub. Complete with tests.
Showing
3 changed files
with
205 additions
and
9 deletions
Show diff stats
app/assets/javascripts/blob.js.coffee
1 | class BlobView | 1 | class BlobView |
2 | constructor: -> | 2 | constructor: -> |
3 | + # handle multi-line select | ||
4 | + handleMultiSelect = (e) -> | ||
5 | + [ first_line, last_line ] = parseSelectedLines() | ||
6 | + [ line_number ] = parseSelectedLines($(this).attr("id")) | ||
7 | + hash = "L#{line_number}" | ||
8 | + | ||
9 | + if e.shiftKey and not isNaN(first_line) and not isNaN(line_number) | ||
10 | + if line_number < first_line | ||
11 | + last_line = first_line | ||
12 | + first_line = line_number | ||
13 | + else | ||
14 | + last_line = line_number | ||
15 | + | ||
16 | + hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}" | ||
17 | + | ||
18 | + setHash(hash) | ||
19 | + e.preventDefault() | ||
20 | + | ||
3 | # See if there are lines selected | 21 | # See if there are lines selected |
4 | # "#L12" and "#L34-56" supported | 22 | # "#L12" and "#L34-56" supported |
5 | - highlightBlobLines = -> | ||
6 | - if window.location.hash isnt "" | ||
7 | - matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/) | 23 | + highlightBlobLines = (e) -> |
24 | + [ first_line, last_line ] = parseSelectedLines() | ||
25 | + | ||
26 | + unless isNaN first_line | ||
27 | + $("#tree-content-holder .highlight .line").removeClass("hll") | ||
28 | + $("#LC#{line}").addClass("hll") for line in [first_line..last_line] | ||
29 | + $("#L#{first_line}").ScrollTo() unless e? | ||
30 | + | ||
31 | + # parse selected lines from hash | ||
32 | + # always return first and last line (initialized to NaN) | ||
33 | + parseSelectedLines = (str) -> | ||
34 | + first_line = NaN | ||
35 | + last_line = NaN | ||
36 | + hash = str || window.location.hash | ||
37 | + | ||
38 | + if hash isnt "" | ||
39 | + matches = hash.match(/\#?L(\d+)(\-(\d+))?/) | ||
8 | first_line = parseInt(matches?[1]) | 40 | first_line = parseInt(matches?[1]) |
9 | last_line = parseInt(matches?[3]) | 41 | last_line = parseInt(matches?[3]) |
42 | + last_line = first_line if isNaN(last_line) | ||
43 | + | ||
44 | + [ first_line, last_line ] | ||
45 | + | ||
46 | + setHash = (hash) -> | ||
47 | + hash = hash.replace(/^\#/, "") | ||
48 | + nodes = $("#" + hash) | ||
49 | + # if any nodes are using this id, they must be temporarily changed | ||
50 | + # also, add a temporary div at the top of the screen to prevent scrolling | ||
51 | + if nodes.length > 0 | ||
52 | + scroll_top = $(document).scrollTop() | ||
53 | + nodes.attr("id", "") | ||
54 | + tmp = $("<div></div>") | ||
55 | + .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" }) | ||
56 | + .attr("id", hash) | ||
57 | + .appendTo(document.body) | ||
58 | + | ||
59 | + window.location.hash = hash | ||
60 | + | ||
61 | + # restore the nodes | ||
62 | + if nodes.length > 0 | ||
63 | + tmp.remove() | ||
64 | + nodes.attr("id", hash) | ||
10 | 65 | ||
11 | - unless isNaN first_line | ||
12 | - last_line = first_line if isNaN(last_line) | ||
13 | - $("#tree-content-holder .highlight .line").removeClass("hll") | ||
14 | - $("#LC#{line}").addClass("hll") for line in [first_line..last_line] | ||
15 | - $("#L#{first_line}").ScrollTo() | 66 | + # initialize multi-line select |
67 | + $("#tree-content-holder .line_numbers a[id^=L]").on("click", handleMultiSelect) | ||
16 | 68 | ||
17 | # Highlight the correct lines on load | 69 | # Highlight the correct lines on load |
18 | highlightBlobLines() | 70 | highlightBlobLines() |
19 | 71 | ||
20 | # Highlight the correct lines when the hash part of the URL changes | 72 | # Highlight the correct lines when the hash part of the URL changes |
21 | - $(window).on 'hashchange', highlightBlobLines | 73 | + $(window).on("hashchange", highlightBlobLines) |
22 | 74 | ||
23 | 75 | ||
24 | @BlobView = BlobView | 76 | @BlobView = BlobView |
@@ -0,0 +1,86 @@ | @@ -0,0 +1,86 @@ | ||
1 | +Feature: Project Multiselect Blob | ||
2 | + Background: | ||
3 | + Given I sign in as a user | ||
4 | + And I own project "Shop" | ||
5 | + And I visit project source page | ||
6 | + And I click on "Gemfile.lock" file in repo | ||
7 | + | ||
8 | + @javascript | ||
9 | + Scenario: I click line 1 in file | ||
10 | + When I click line 1 in file | ||
11 | + Then I should see "L1" as URI fragment | ||
12 | + And I should see line 1 highlighted | ||
13 | + | ||
14 | + @javascript | ||
15 | + Scenario: I shift-click line 1 in file | ||
16 | + When I shift-click line 1 in file | ||
17 | + Then I should see "L1" as URI fragment | ||
18 | + And I should see line 1 highlighted | ||
19 | + | ||
20 | + @javascript | ||
21 | + Scenario: I click line 1 then click line 2 in file | ||
22 | + When I click line 1 in file | ||
23 | + Then I should see "L1" as URI fragment | ||
24 | + And I should see line 1 highlighted | ||
25 | + Then I click line 2 in file | ||
26 | + Then I should see "L2" as URI fragment | ||
27 | + And I should see line 2 highlighted | ||
28 | + | ||
29 | + @javascript | ||
30 | + Scenario: I click various line numbers to test multiselect | ||
31 | + Then I click line 1 in file | ||
32 | + Then I should see "L1" as URI fragment | ||
33 | + And I should see line 1 highlighted | ||
34 | + Then I shift-click line 2 in file | ||
35 | + Then I should see "L1-2" as URI fragment | ||
36 | + And I should see lines 1-2 highlighted | ||
37 | + Then I shift-click line 3 in file | ||
38 | + Then I should see "L1-3" as URI fragment | ||
39 | + And I should see lines 1-3 highlighted | ||
40 | + Then I click line 3 in file | ||
41 | + Then I should see "L3" as URI fragment | ||
42 | + And I should see line 3 highlighted | ||
43 | + Then I shift-click line 1 in file | ||
44 | + Then I should see "L1-3" as URI fragment | ||
45 | + And I should see lines 1-3 highlighted | ||
46 | + Then I shift-click line 5 in file | ||
47 | + Then I should see "L1-5" as URI fragment | ||
48 | + And I should see lines 1-5 highlighted | ||
49 | + Then I shift-click line 4 in file | ||
50 | + Then I should see "L1-4" as URI fragment | ||
51 | + And I should see lines 1-4 highlighted | ||
52 | + Then I click line 5 in file | ||
53 | + Then I should see "L5" as URI fragment | ||
54 | + And I should see line 5 highlighted | ||
55 | + Then I shift-click line 3 in file | ||
56 | + Then I should see "L3-5" as URI fragment | ||
57 | + And I should see lines 3-5 highlighted | ||
58 | + Then I shift-click line 1 in file | ||
59 | + Then I should see "L1-3" as URI fragment | ||
60 | + And I should see lines 1-3 highlighted | ||
61 | + Then I shift-click line 1 in file | ||
62 | + Then I should see "L1" as URI fragment | ||
63 | + And I should see line 1 highlighted | ||
64 | + | ||
65 | + @javascript | ||
66 | + Scenario: I multiselect lines 1-5 and then go back and forward in history | ||
67 | + When I click line 1 in file | ||
68 | + And I shift-click line 3 in file | ||
69 | + And I shift-click line 2 in file | ||
70 | + And I shift-click line 5 in file | ||
71 | + Then I should see "L1-5" as URI fragment | ||
72 | + And I should see lines 1-5 highlighted | ||
73 | + Then I go back in history | ||
74 | + Then I should see "L1-2" as URI fragment | ||
75 | + And I should see lines 1-2 highlighted | ||
76 | + Then I go back in history | ||
77 | + Then I should see "L1-3" as URI fragment | ||
78 | + And I should see lines 1-3 highlighted | ||
79 | + Then I go back in history | ||
80 | + Then I should see "L1" as URI fragment | ||
81 | + And I should see line 1 highlighted | ||
82 | + Then I go forward in history | ||
83 | + And I go forward in history | ||
84 | + And I go forward in history | ||
85 | + Then I should see "L1-5" as URI fragment | ||
86 | + And I should see lines 1-5 highlighted | ||
0 | \ No newline at end of file | 87 | \ No newline at end of file |
@@ -0,0 +1,58 @@ | @@ -0,0 +1,58 @@ | ||
1 | +class ProjectMultiselectBlob < Spinach::FeatureSteps | ||
2 | + include SharedAuthentication | ||
3 | + include SharedProject | ||
4 | + include SharedPaths | ||
5 | + | ||
6 | + class << self | ||
7 | + def click_line_steps(*line_numbers) | ||
8 | + line_numbers.each do |line_number| | ||
9 | + step "I click line #{line_number} in file" do | ||
10 | + find("#L#{line_number}").click | ||
11 | + end | ||
12 | + | ||
13 | + step "I shift-click line #{line_number} in file" do | ||
14 | + script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));" | ||
15 | + page.evaluate_script(script) | ||
16 | + end | ||
17 | + end | ||
18 | + end | ||
19 | + | ||
20 | + def check_state_steps(*ranges) | ||
21 | + ranges.each do |range| | ||
22 | + fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}" | ||
23 | + pluralization = range.kind_of?(Array) ? "s" : "" | ||
24 | + | ||
25 | + step "I should see \"#{fragment}\" as URI fragment" do | ||
26 | + URI.parse(current_url).fragment.should == fragment | ||
27 | + end | ||
28 | + | ||
29 | + step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do | ||
30 | + ids = Array(range).map { |n| "LC#{n}" } | ||
31 | + extra = false | ||
32 | + | ||
33 | + highlighted = all("#tree-content-holder .highlight .line.hll") | ||
34 | + highlighted.each do |element| | ||
35 | + extra ||= ids.delete(element[:id]).nil? | ||
36 | + end | ||
37 | + | ||
38 | + extra.should be_false and ids.should be_empty | ||
39 | + end | ||
40 | + end | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | + click_line_steps *Array(1..5) | ||
45 | + check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5) | ||
46 | + | ||
47 | + step 'I go back in history' do | ||
48 | + page.evaluate_script("window.history.back()") | ||
49 | + end | ||
50 | + | ||
51 | + step 'I go forward in history' do | ||
52 | + page.evaluate_script("window.history.forward()") | ||
53 | + end | ||
54 | + | ||
55 | + step 'I click on "Gemfile.lock" file in repo' do | ||
56 | + click_link "Gemfile.lock" | ||
57 | + end | ||
58 | +end | ||
0 | \ No newline at end of file | 59 | \ No newline at end of file |