OSDN Git Service

Enable multiline select for blobs.
authorJason Hollingsworth <jhworth.developer@gmail.com>
Wed, 27 Nov 2013 18:24:27 +0000 (12:24 -0600)
committerJason Hollingsworth <jhworth.developer@gmail.com>
Thu, 28 Nov 2013 02:08:37 +0000 (20:08 -0600)
Holding shift will allow users to click and select multiple lines.
The behavior is similar to GitHub.
Complete with tests.

app/assets/javascripts/blob.js.coffee
features/project/source/multiselect_blob.feature [new file with mode: 0644]
features/steps/project/project_multiselect_blob.rb [new file with mode: 0644]

index 03e280f..99cb1ce 100644 (file)
@@ -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 (file)
index 0000000..3038c08
--- /dev/null
@@ -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 (file)
index 0000000..3d330e8
--- /dev/null
@@ -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