fix: Preserve double newlines in text-based messaging channels (#13055)
## Summary
Fixes the issue where double newlines (paragraph breaks) were collapsing
to single newlines in text-based messaging channels (Telegram, WhatsApp,
Instagram, Facebook, LINE, SMS).
### Root Cause
The `preserve_multiple_newlines` method only preserved 3+ consecutive
newlines using the regex `/\n{3,}/`. When users pressed Enter twice
(creating a paragraph break with 2 newlines), CommonMarker would parse
this as separate paragraphs, which then collapsed to a single newline in
the output.
This caused:
- ❌ Normal Enter: Double newlines collapsed to single newline
- ✅ Shift+Enter: Worked (created hard breaks)
### Fix
Changed the regex from `/\n{3,}/` to `/\n{2,}/` to preserve 2+
consecutive newlines. This prevents CommonMarker from collapsing
paragraph breaks.
Now:
- ✅ Single newline (`\n`) → Single newline (handled by softbreak)
- ✅ Double newline (`\n\n`) → Double newline (preserved with
placeholders)
- ✅ Triple+ newlines → Preserved as before
### Test Coverage
Added comprehensive tests for:
- Single newlines preservation
- Double newlines (paragraph breaks) preservation
- Multiple consecutive newlines
- Newlines with varying amounts of whitespace between them (1 space, 3
spaces, 5 spaces, tabs)
All 66 tests passing.
### Impact
This fix affects all text-based messaging channels that use the markdown
renderer:
- Telegram
- WhatsApp
- Instagram
- Facebook
- LINE
- SMS
- Twilio SMS (when configured for WhatsApp)
Fixes
https://linear.app/chatwoot/issue/CW-6135/double-newline-is-breaking
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -96,10 +96,10 @@ class Messages::MarkdownRendererService
|
||||
restore_multiple_newlines(result)
|
||||
end
|
||||
|
||||
# Preserve multiple consecutive newlines (3+) by replacing them with placeholders
|
||||
# Standard markdown treats 2 newlines as paragraph break, we preserve 3+
|
||||
# Preserve multiple consecutive newlines (2+) by replacing them with placeholders
|
||||
# Standard markdown treats 2 newlines as paragraph break which collapses to 1 newline, we preserve 2+
|
||||
def preserve_multiple_newlines(content)
|
||||
content.gsub(/\n{3,}/) do |match|
|
||||
content.gsub(/\n{2,}/) do |match|
|
||||
"{{PRESERVE_#{match.length}_NEWLINES}}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -241,10 +241,37 @@ RSpec.describe Messages::MarkdownRendererService, type: :service do
|
||||
expect(result).to include('<a href="https://example.com">link text</a>')
|
||||
end
|
||||
|
||||
it 'preserves newlines' do
|
||||
it 'preserves single newlines' do
|
||||
content = "line 1\nline 2"
|
||||
result = described_class.new(content, channel_type).render
|
||||
expect(result).to include("\n")
|
||||
expect(result).to include("line 1\nline 2")
|
||||
end
|
||||
|
||||
it 'preserves double newlines (paragraph breaks)' do
|
||||
content = "para 1\n\npara 2"
|
||||
result = described_class.new(content, channel_type).render
|
||||
expect(result.scan("\n").count).to eq(2)
|
||||
expect(result).to include("para 1\n\npara 2")
|
||||
end
|
||||
|
||||
it 'preserves multiple consecutive newlines' do
|
||||
content = "para 1\n\n\n\npara 2"
|
||||
result = described_class.new(content, channel_type).render
|
||||
expect(result.scan("\n").count).to eq(4)
|
||||
expect(result).to include("para 1\n\n\n\npara 2")
|
||||
end
|
||||
|
||||
it 'preserves newlines with varying amounts of whitespace between them' do
|
||||
# Test with 1 space, 3 spaces, 5 spaces, and tabs to ensure it handles any amount of whitespace
|
||||
content = "hello\n \n \n \n\t\nworld"
|
||||
result = described_class.new(content, channel_type).render
|
||||
# Whitespace-only lines are normalized, so we should have at least 5 newlines preserved
|
||||
expect(result.scan("\n").count).to be >= 5
|
||||
expect(result).to include('hello')
|
||||
expect(result).to include('world')
|
||||
# Should not collapse to just 1-2 newlines
|
||||
expect(result.scan("\n").count).to be > 3
|
||||
end
|
||||
|
||||
it 'converts strikethrough to HTML' do
|
||||
|
||||
Reference in New Issue
Block a user