Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 20 additions & 0 deletions lib/reverse_markdown/converters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ def escape_keychars(string)
string.gsub(/(?<!\\)[*_]/, '*' => '\*', '_' => '\_')
end

# Wrap content with markers (e.g., ** or _), splitting at paragraph breaks
# so markers don't span across breaks (which breaks markdown rendering)
def wrap_with_markers(content, marker)
# Split on paragraph breaks, preserving the breaks
segments = content.split(/(\s*\n\s*\n\s*)/)

segments.map.with_index do |segment, i|
if i.odd? # This is a break segment (captured delimiter)
segment
elsif segment.strip.empty?
segment
else
# Wrap with markers, preserving border whitespace
leading = segment[/\A\s*/]
trailing = segment[/\s*\z/]
"#{leading}#{marker}#{segment.strip}#{marker}#{trailing}"
end
end.join
end

def extract_title(node)
title = escape_keychars(node['title'].to_s)
title.empty? ? '' : %[ "#{title}"]
Expand Down
2 changes: 1 addition & 1 deletion lib/reverse_markdown/converters/em.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def convert(node, state = {})
if content.strip.empty? || state[:already_italic]
content
else
"#{content[/^\s*/]}_#{content.strip}_#{content[/\s*$/]}"
wrap_with_markers(content, '_')
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion lib/reverse_markdown/converters/h.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ module Converters
class H < Base
def convert(node, state = {})
prefix = '#' * node.name[/\d/].to_i
["\n", prefix, ' ', treat_children(node, state), "\n"].join
content = treat_children(node, state).strip
# Merge lines into one (markdown headings can't span multiple lines)
content = content.split(/\s*\n\s*/).join(' ')
"\n#{prefix} #{content}\n"
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/reverse_markdown/converters/strong.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def convert(node, state = {})
if content.strip.empty? || state[:already_strong]
content
else
"#{content[/^\s*/]}**#{content.strip}**#{content[/\s*$/]}"
wrap_with_markers(content, '**')
end
end
end
Expand Down
28 changes: 28 additions & 0 deletions spec/lib/reverse_markdown/converters/em_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'spec_helper'

describe ReverseMarkdown::Converters::Em do
let(:converter) { ReverseMarkdown::Converters::Em.new }

it 'returns an empty string if the node is empty' do
input = node_for('<em></em>')
expect(converter.convert(input)).to eq ''
end

it 'returns just the content if the em tag is nested in another em' do
input = node_for('<em><em>foo</em></em>')
expect(converter.convert(input.children.first, already_italic: true)).to eq 'foo'
end

it 'moves border whitespaces outside of the delimiters tag' do
input = node_for("<em> \n foo </em>")
expect(converter.convert(input)).to eq " _foo_ "
end

it 'splits markers at paragraph breaks' do
# Issue #95: <br><br> inside em creates a paragraph break
# Markers must be split so markdown renders correctly
result = ReverseMarkdown.convert('<em>hello<br><br>world</em>')
expect(result).to include('_hello_')
expect(result).to include('_world_')
end
end
16 changes: 16 additions & 0 deletions spec/lib/reverse_markdown/converters/h_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require 'spec_helper'

describe ReverseMarkdown::Converters::H do
let(:converter) { ReverseMarkdown::Converters::H.new }

it 'merges line breaks into single line' do
# Markdown headings can't span multiple lines, so merge them
result = ReverseMarkdown.convert('<h1>foo<br>bar</h1>')
expect(result.strip).to eq '# foo bar'
end

it 'handles multiple line breaks' do
result = ReverseMarkdown.convert('<h2>a<br>b<br>c</h2>')
expect(result.strip).to eq '## a b c'
end
end
8 changes: 8 additions & 0 deletions spec/lib/reverse_markdown/converters/strong_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@
input = node_for("<strong> \n foo </strong>")
expect(converter.convert(input)).to eq " **foo** "
end

it 'splits markers at paragraph breaks' do
# Issue #95: <br><br> inside strong creates a paragraph break
# Markers must be split so markdown renders correctly
result = ReverseMarkdown.convert('<strong>hello<br><br>world</strong>')
expect(result).to include('**hello**')
expect(result).to include('**world**')
end
end