Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions lib/htmldiff/diff_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def replace(operation)
# added e.g. <p> becomes <p style="margin: 2px"> due to an editor button
# press. For this, we just show the new version, otherwise it gets messy
# trying to find the closing tag.
# our-embed tags are the exception and will be replaced in full.
if operation.same_tag?
equal(operation)
else
Expand Down Expand Up @@ -99,10 +100,12 @@ def insert_tag(tagname, cssclass, words)
loop do
break if words.empty?

if words.first.standalone_tag?
tag_words = words.extract_consecutive_words! do |word|
word.standalone_tag?
end
# Handle our-embeds and writing blank spans as single blocks
if words.first.closed_embed_or_blank_tag?
tag_words = words.extract_consecutive_words! { |word| word.closed_embed_or_blank_tag? }
@content << wrap_text_in_diff_tag(tag_words.join, tagname, cssclass)
elsif words.first.standalone_tag?
tag_words = words.extract_consecutive_words! { |word| word.standalone_tag? }
@content << wrap_text_in_diff_tag(tag_words.join, tagname, cssclass)
elsif words.first.iframe_tag?
tag_words = words.extract_consecutive_words! { |word| word.iframe_tag? }
Expand All @@ -124,7 +127,7 @@ def insert_tag(tagname, cssclass, words)
wrapped = true
end
@content += words.extract_consecutive_words! do |word|
word.tag? && !word.standalone_tag? && !word.iframe_tag?
word.tag? && !word.standalone_tag? && !word.iframe_tag? && !word.closed_embed_or_blank_tag?
end
else
non_tags = words.extract_consecutive_words! do |word|
Expand Down
38 changes: 38 additions & 0 deletions lib/htmldiff/list_of_words.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def initialize(string, options = {})
@words = string
else
convert_html_to_list_of_words string.chars
group_embed_or_blank_tags!
end
end

Expand Down Expand Up @@ -72,6 +73,43 @@ def contains_unclosed_tag?

private

# Group our-embed tags and Writing Blank spans, which are
# intentionally left blank, into single words
def group_embed_or_blank_tags!
return if @words.empty?
new_words = []
i = 0

while i < @words.length
current_word = @words[i]

if current_word.embed_or_blank_opening_tag?
word_group = [current_word]
tag_name = current_word.to_s.match(/^<(span|our-embed)/)[1]
i += 1

# Collect words until the appropriate closing tag is reached
while i < @words.length
word = @words[i]
word_group << word

if word.to_s.match?(/^<\/#{tag_name}>$/)
i += 1
break
end

i += 1
end
# Create a single word from the entire element
new_words << Word.new(word_group.map(&:to_s).join)
else
new_words << current_word
i += 1
end
end
@words = new_words
end

def convert_html_to_list_of_words(character_array)
@mode = :char
@current_word = Word.new
Expand Down
8 changes: 8 additions & 0 deletions lib/htmldiff/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ class Operation

# Ignores any attributes and tells us if the tag is the same e.g. <p> and
# <p style="margin: 2px;"> are the same.
# The exception to this rule is our-embed tags, where we
# always want a full replacement.
def same_tag?
return false if contains_our_embed_tags?

pattern = /<([^>\s]+)[\s>].*/
first_tagname = pattern.match(old_text) # nil means they are not tags
first_tagname = first_tagname[1] if first_tagname
Expand All @@ -27,6 +31,10 @@ def same_tag?
first_tagname && (first_tagname == second_tagname)
end

def contains_our_embed_tags?
old_text.match?(%r{^<\/?our-embed}) && new_text.match?(%r{^<\/?our-embed})
end

def old_text
old_words.join
end
Expand Down
8 changes: 8 additions & 0 deletions lib/htmldiff/word.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ def iframe_tag?
(@word[0..7].downcase =~ %r{^<\/?iframe ?})
end

def embed_or_blank_opening_tag?
@word =~ Regexp.union(/^<span[^>]*class="[^"]*blank[^"]*"[^>]*>$/i, /^<our-embed[^>]*>$/i)
end

def closed_embed_or_blank_tag?
@word =~ /^<(span[^>]*class="[^"]*blank[^"]*"[^>]*|our-embed[^>]*)><\/(span|our-embed)>$/i
end

def tag?
opening_tag? || closing_tag? || standalone_tag?
end
Expand Down
9 changes: 9 additions & 0 deletions spec/operation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
end
end

context 'with `our-embed` tags that may have different attributes' do
let(:old_tag) { '<our-embed src="gid://123">' }
let(:new_tag) { '<our-embed src="gid://456">' }

it 'returns false for matching and non-matching `our-embed` tags' do
expect(operation.same_tag?).to be_false
end
end

context 'with two different tags' do
let(:old_tag) { '<p>' }
let(:new_tag) { '<b>' }
Expand Down