summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Hollingsworth <jhworth.developer@gmail.com>2013-11-27 12:24:27 -0600
committerJason Hollingsworth <jhworth.developer@gmail.com>2013-11-27 20:08:37 -0600
commit568e05a73b1c4aac814f75da488d3d1ed7d847e9 (patch)
tree94226bd1af711c2cc540106899fd3cb9bf8c379d
parent09d00563af8168de14daca9193a6f708e5563870 (diff)
downloadgitlab-ce-568e05a73b1c4aac814f75da488d3d1ed7d847e9.tar.gz
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.
-rw-r--r--app/assets/javascripts/blob.js.coffee70
-rw-r--r--features/project/source/multiselect_blob.feature86
-rw-r--r--features/steps/project/project_multiselect_blob.rb58
3 files changed, 205 insertions, 9 deletions
diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee
index 03e280f8976..99cb1cec911 100644
--- a/app/assets/javascripts/blob.js.coffee
+++ b/app/assets/javascripts/blob.js.coffee
@@ -1,24 +1,76 @@
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 = ->
- if window.location.hash isnt ""
- matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
+ 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]
+ $("#L#{first_line}").ScrollTo() 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)
- unless isNaN first_line
- last_line = first_line if isNaN(last_line)
- $("#tree-content-holder .highlight .line").removeClass("hll")
- $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
- $("#L#{first_line}").ScrollTo()
+ # 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
+ $(window).on("hashchange", highlightBlobLines)
@BlobView = BlobView
diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature
new file mode 100644
index 00000000000..3038c0814ad
--- /dev/null
+++ b/features/project/source/multiselect_blob.feature
@@ -0,0 +1,86 @@
+Feature: Project Multiselect Blob
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And I visit project source page
+ And I click on "Gemfile.lock" file in repo
+
+ @javascript
+ Scenario: I click line 1 in file
+ When I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I shift-click line 1 in file
+ When I shift-click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I click line 1 then click line 2 in file
+ When I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I click line 2 in file
+ Then I should see "L2" as URI fragment
+ And I should see line 2 highlighted
+
+ @javascript
+ Scenario: I click various line numbers to test multiselect
+ Then I click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I shift-click line 2 in file
+ Then I should see "L1-2" as URI fragment
+ And I should see lines 1-2 highlighted
+ Then I shift-click line 3 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I click line 3 in file
+ Then I should see "L3" as URI fragment
+ And I should see line 3 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I shift-click line 5 in file
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted
+ Then I shift-click line 4 in file
+ Then I should see "L1-4" as URI fragment
+ And I should see lines 1-4 highlighted
+ Then I click line 5 in file
+ Then I should see "L5" as URI fragment
+ And I should see line 5 highlighted
+ Then I shift-click line 3 in file
+ Then I should see "L3-5" as URI fragment
+ And I should see lines 3-5 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I shift-click line 1 in file
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+
+ @javascript
+ Scenario: I multiselect lines 1-5 and then go back and forward in history
+ When I click line 1 in file
+ And I shift-click line 3 in file
+ And I shift-click line 2 in file
+ And I shift-click line 5 in file
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted
+ Then I go back in history
+ Then I should see "L1-2" as URI fragment
+ And I should see lines 1-2 highlighted
+ Then I go back in history
+ Then I should see "L1-3" as URI fragment
+ And I should see lines 1-3 highlighted
+ Then I go back in history
+ Then I should see "L1" as URI fragment
+ And I should see line 1 highlighted
+ Then I go forward in history
+ And I go forward in history
+ And I go forward in history
+ Then I should see "L1-5" as URI fragment
+ And I should see lines 1-5 highlighted \ No newline at end of file
diff --git a/features/steps/project/project_multiselect_blob.rb b/features/steps/project/project_multiselect_blob.rb
new file mode 100644
index 00000000000..3d330e837c1
--- /dev/null
+++ b/features/steps/project/project_multiselect_blob.rb
@@ -0,0 +1,58 @@
+class ProjectMultiselectBlob < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+
+ class << self
+ def click_line_steps(*line_numbers)
+ line_numbers.each do |line_number|
+ step "I click line #{line_number} in file" do
+ find("#L#{line_number}").click
+ end
+
+ step "I shift-click line #{line_number} in file" do
+ script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));"
+ page.evaluate_script(script)
+ end
+ end
+ end
+
+ def check_state_steps(*ranges)
+ ranges.each do |range|
+ fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}"
+ pluralization = range.kind_of?(Array) ? "s" : ""
+
+ step "I should see \"#{fragment}\" as URI fragment" do
+ URI.parse(current_url).fragment.should == fragment
+ end
+
+ step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do
+ ids = Array(range).map { |n| "LC#{n}" }
+ extra = false
+
+ highlighted = all("#tree-content-holder .highlight .line.hll")
+ highlighted.each do |element|
+ extra ||= ids.delete(element[:id]).nil?
+ end
+
+ extra.should be_false and ids.should be_empty
+ end
+ end
+ end
+ end
+
+ click_line_steps *Array(1..5)
+ check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5)
+
+ step 'I go back in history' do
+ page.evaluate_script("window.history.back()")
+ end
+
+ step 'I go forward in history' do
+ page.evaluate_script("window.history.forward()")
+ end
+
+ step 'I click on "Gemfile.lock" file in repo' do
+ click_link "Gemfile.lock"
+ end
+end \ No newline at end of file