diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f89b14995..512dc01ed 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -949,6 +949,7 @@ module ApplicationHelper
send method_name, txt, project, obj, attr, only_path, options
end
end
+ unwrap_paragraphs_containing_block_macros(text)
parse_headings(text, project, obj, attr, only_path, options)
if @parsed_headings.any?
@@ -1468,6 +1469,20 @@ module ApplicationHelper
end
end
+ # Fixes invalid HTML where block-level elements from macros (e.g. collapse)
+ # are nested inside
tags. Replaces the
with so that
+ # consecutive paragraphs remain visually separated as block-level elements.
+ # Uses a pattern that treats ...
as atomic
+ # so that inside the div's content does not end the outer match early.
+ def unwrap_paragraphs_containing_block_macros(text)
+ collapsed_div = /]*class="[^"]*collapsed-text[^"]*"[^>]*>(?:(?!<\/div>).)*<\/div>/m
+ non_collapsed = /(?!<\/p>)(?!
]*class="[^"]*collapsed-text)./m
+ text.gsub!(
+ /
(#{non_collapsed}*(?:#{collapsed_div}#{non_collapsed}*)+)<\/p>/m,
+ '
\1
'
+ )
+ end
+
TOC_RE = /
\{\{((<|<)|(>|>))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
# Renders the TOC with given headings
diff --git a/test/unit/lib/redmine/wiki_formatting/macros_test.rb b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
index 8c3011575..dc14ed8c0 100644
--- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb
@@ -314,6 +314,72 @@ class Redmine::WikiFormatting::MacrosTest < Redmine::HelperTest
end
end
+ COLLAPSE_INVALID_HTML_RE = /
(?:(?!<\/p>).)*
]*class="[^"]*collapsed-text/m
+
+ # Example 1: text between collapses in the same paragraph
+ def test_macro_multiple_collapse_with_text_between_should_not_generate_div_inside_p
+ text = <<~RAW
+ 1 {{collapse(1)
+ content 1
+ }}
+ 2 {{collapse(2)
+ content 2
+ }}
+ 3 {{collapse(3)
+ content 3
+ }}
+ RAW
+ with_locale 'en' do
+ with_settings :text_formatting => 'textile' do
+ assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
+ end
+ end
+ end
+
+ # Example 2: adjacent collapses with no text between
+ def test_macro_multiple_collapse_adjacent_should_not_generate_div_inside_p
+ text = <<~RAW
+ {{collapse(1)
+ content 1
+ }}
+ {{collapse(2)
+ content 2
+ }}
+ {{collapse(3)
+ content 3
+ }}
+ RAW
+ with_locale 'en' do
+ with_settings :text_formatting => 'textile' do
+ assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
+ end
+ end
+ end
+
+ # Example 3: multiple groups of collapses separated by a blank line
+ def test_macro_multiple_collapse_groups_should_not_generate_div_inside_p
+ text = <<~RAW
+ 1 {{collapse(1)
+ content 1
+ }}
+ 2 {{collapse(2)
+ content 2
+ }}
+
+ 3 {{collapse(3)
+ content 3
+ }}
+ 4 {{collapse(4)
+ content 4
+ }}
+ RAW
+ with_locale 'en' do
+ with_settings :text_formatting => 'textile' do
+ assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
+ end
+ end
+ end
+
def test_macro_child_pages
expected =
"