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 = "