FIX: Show a correct diff when editing consecutive paragraphs (#8177)

FIX: Show a correct diff when editing consecutive paragraphs (#8177)

diff --git a/lib/discourse_diff.rb b/lib/discourse_diff.rb
index 6445ac8..c2b3171 100644
--- a/lib/discourse_diff.rb
+++ b/lib/discourse_diff.rb
@@ -12,7 +12,7 @@ class DiscourseDiff
     before_markdown = tokenize_line(CGI::escapeHTML(@before))
     after_markdown = tokenize_line(CGI::escapeHTML(@after))
 
-    @block_by_block_diff = ONPDiff.new(before_html, after_html).diff
+    @block_by_block_diff = ONPDiff.new(before_html, after_html).paragraph_diff
     @line_by_line_diff = ONPDiff.new(before_markdown, after_markdown).short_diff
   end
 
diff --git a/lib/onpdiff.rb b/lib/onpdiff.rb
index ef4799f..6c86a79 100644
--- a/lib/onpdiff.rb
+++ b/lib/onpdiff.rb
@@ -25,6 +25,10 @@ class ONPDiff
     @short_diff ||= build_short_edit_script(compose)
   end
 
+  def paragraph_diff
+    @paragraph_diff ||= build_paragraph_edit_script(diff)
+  end
+
   private
 
   def compose
@@ -156,4 +160,71 @@ class ONPDiff
     ses
   end
 
+  def build_paragraph_edit_script(ses)
+    paragraph_ses = []
+    i = 0
+    while i < ses.size
+      if ses[i][1] == :common
+        paragraph_ses << ses[i]
+      else
+        if ses[i][1] == :add
+          op_code = :add
+          opposite_op_code = :delete
+        else
+          op_code = :delete
+          opposite_op_code = :add
+        end
+        j = i + 1
+
+        while j < ses.size && ses[j][1] == op_code
+          j += 1
+        end
+
+        if j >= ses.size
+          paragraph_ses = paragraph_ses.concat(ses[i..j])
+          i = j
+        else
+          k = j
+          j -= 1
+
+          while k < ses.size && ses[k][1] == opposite_op_code
+            k += 1
+          end
+          k -= 1
+
+          num_before = j - i + 1
+          num_after = k - j
+          if num_after > 1
+            if num_before > num_after
+              i2 = i + num_before - num_after
+              paragraph_ses = paragraph_ses.concat(ses[i..i2 - 1])
+              i = i2
+            elsif num_after > num_before
+              k -= num_after - num_before
+            end
+            paragraph_ses = paragraph_ses.concat(pair_paragraphs(ses, i, j))
+          else
+            paragraph_ses = paragraph_ses.concat(ses[i..k])
+          end
+          i = k
+        end
+      end
+      i += 1
+    end
+
+    paragraph_ses
+  end
+
+  def pair_paragraphs(ses, i, j)
+    pairs = []
+    num_pairs = j - i + 1
+    num_pairs.times do
+      pairs << ses[i]
+      pairs << ses[i + num_pairs]
+      i += 1
+    end
+
+    pairs
+  end
+
 end
diff --git a/spec/components/discourse_diff_spec.rb b/spec/components/discourse_diff_spec.rb
index a7aefc5..d1158ae 100644
--- a/spec/components/discourse_diff_spec.rb
+++ b/spec/components/discourse_diff_spec.rb
@@ -76,6 +76,13 @@ describe DiscourseDiff do
       expect(DiscourseDiff.new(before, after).side_by_side_html).to eq("<div class=\"revision-content\"><p>this is a paragraph</p></div><div class=\"revision-content\"><p>this is a <ins>great </ins>paragraph</p></div>")
     end
 
+    it "adds <ins> and <del> tags on consecutive paragraphs", :focus do
+      before = "<p>this is one paragraph</p><p>here is yet another</p>"
+      after = "<p>this is one great paragraph</p><p>here is another</p>"
+      got = DiscourseDiff.new(before, after).side_by_side_html
+      expect(got).to eq("<div class=\"revision-content\"><p>this is one paragraph</p><p>here is <del>yet </del>another</p></div><div class=\"revision-content\"><p>this is one <ins>great </ins>paragraph</p><p>here is another</p></div>")
+    end
+
     it "adds <del> tags around removed text on the left div" do
       before = "<p>this is a great paragraph</p>"
       after = "<p>this is a paragraph</p>"
diff --git a/spec/components/onpdiff_spec.rb b/spec/components/onpdiff_spec.rb
index 67c5b0b..60b4039 100644
--- a/spec/components/onpdiff_spec.rb
+++ b/spec/components/onpdiff_spec.rb
@@ -39,4 +39,38 @@ describe ONPDiff do
 
   end
 
+  describe "paragraph_diff" do
+
+    it "returns an empty array when there is no content to diff" do
+      expect(ONPDiff.new("", "").paragraph_diff).to eq([])
+    end
+
+    it "returns an array with the operation code for each element" do
+      expect(ONPDiff.new("abc", "acd").paragraph_diff).to eq([["a", :common], ["b", :delete], ["c", :common], ["d", :add]])
+    end
+
+    it "pairs as many elements as possible", :focus do
+      expect(ONPDiff.new("abcd", "abef").paragraph_diff).to eq([
+        ["a", :common], ["b", :common],
+        ["e", :add], ["c", :delete],
+        ["f", :add], ["d", :delete]
+      ])
+
+      expect(ONPDiff.new("abcde", "abfg").paragraph_diff).to eq([
+        ["a", :common], ["b", :common],
+        ["c", :delete],
+        ["d", :delete], ["f", :add],
+        ["e", :delete], ["g", :add]
+      ])
+
+      expect(ONPDiff.new("abcd", "abefg").paragraph_diff).to eq([
+        ["a", :common], ["b", :common],
+        ["e", :add],
+        ["f", :add], ["c", :delete],
+        ["g", :add], ["d", :delete]
+      ])
+    end
+
+  end
+
 end

GitHub sha: 7d2f5240

1 Like