Defect #44623 » 0001-fix.patch
| app/helpers/application_helper.rb | ||
|---|---|---|
| 990 | 990 | |
| 991 | 991 |
@parsed_headings = [] |
| 992 | 992 |
@heading_anchors = {}
|
| 993 |
@current_section = 0 if options[:edit_section_links] |
|
| 993 |
if options[:edit_section_links] |
|
| 994 |
@current_section = 0 |
|
| 995 |
# Output of macros whose headings are not sections of the page (eg. include) |
|
| 996 |
# is hidden while parsing sections so that it does not get edit links |
|
| 997 |
hidden_macros = @hidden_macros = [] |
|
| 998 |
end |
|
| 994 | 999 | |
| 995 |
text = parse_non_pre_blocks(text, obj, macros, options) do |txt| |
|
| 996 |
[:parse_wiki_links, :parse_redmine_links].each do |method_name| |
|
| 997 |
send method_name, txt, project, obj, attr, only_path, options |
|
| 1000 |
begin |
|
| 1001 |
text = parse_non_pre_blocks(text, obj, macros, options) do |txt| |
|
| 1002 |
[:parse_wiki_links, :parse_redmine_links].each do |method_name| |
|
| 1003 |
send method_name, txt, project, obj, attr, only_path, options |
|
| 1004 |
end |
|
| 998 | 1005 |
end |
| 1006 |
parse_sections(text, project, obj, attr, only_path, options) |
|
| 1007 |
ensure |
|
| 1008 |
@hidden_macros = nil if hidden_macros |
|
| 999 | 1009 |
end |
| 1000 |
parse_sections(text, project, obj, attr, only_path, options)
|
|
| 1010 |
text = restore_hidden_macros(text, hidden_macros) if hidden_macros
|
|
| 1001 | 1011 |
parse_headings(text, project, obj, attr, only_path, options) |
| 1002 | 1012 | |
| 1003 | 1013 |
if @parsed_headings.any? |
| ... | ... | |
| 1507 | 1517 |
if execute && orig && orig =~ MACROS_RE |
| 1508 | 1518 |
esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) |
| 1509 | 1519 |
if esc.nil? |
| 1510 |
h(exec_macro(macro, obj, args, block, options) || all)
|
|
| 1520 |
hide_macro_output(macro) {h(exec_macro(macro, obj, args, block, options) || all)}
|
|
| 1511 | 1521 |
else |
| 1512 | 1522 |
h(all) |
| 1513 | 1523 |
end |
| ... | ... | |
| 1519 | 1529 |
end |
| 1520 | 1530 |
end |
| 1521 | 1531 | |
| 1532 |
HIDDEN_MACRO_RE = /\0(\d+)\0/ unless const_defined?(:HIDDEN_MACRO_RE) |
|
| 1533 | ||
| 1534 |
# Replaces the output of a macro with a placeholder while sections are parsed, |
|
| 1535 |
# unless the headings it contains are sections of the page (eg. collapse) |
|
| 1536 |
def hide_macro_output(macro) |
|
| 1537 |
hidden_macros = @hidden_macros |
|
| 1538 |
if hidden_macros.nil? || Redmine::WikiFormatting::Macros.available_macros.dig(macro.to_sym, :sections) |
|
| 1539 |
return yield |
|
| 1540 |
end |
|
| 1541 | ||
| 1542 |
# The whole output is hidden, macros nested in this one don't need to be |
|
| 1543 |
@hidden_macros = nil |
|
| 1544 |
hidden_macros << yield.to_str |
|
| 1545 |
"\0#{hidden_macros.size - 1}\0"
|
|
| 1546 |
ensure |
|
| 1547 |
@hidden_macros = hidden_macros |
|
| 1548 |
end |
|
| 1549 | ||
| 1550 |
# Puts back the output of the macros hidden by hide_macro_output |
|
| 1551 |
def restore_hidden_macros(text, outputs) |
|
| 1552 |
text.gsub(HIDDEN_MACRO_RE) {outputs[$1.to_i] || $&}
|
|
| 1553 |
end |
|
| 1554 | ||
| 1522 | 1555 |
TOC_RE = /<p>\{\{((<|<)|(>|>))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
|
| 1523 | 1556 | |
| 1524 | 1557 |
# Renders the TOC with given headings |
| lib/redmine/wiki_formatting/macros.rb | ||
|---|---|---|
| 100 | 100 |
# * :desc - A description of the macro |
| 101 | 101 |
# * :parse_args => false - Disables arguments parsing (the whole arguments |
| 102 | 102 |
# string is passed to the macro) |
| 103 |
# * :sections => true - Headings in the macro output are sections of the |
|
| 104 |
# page and get section edit links (eg. the block of text is part of the page) |
|
| 103 | 105 |
# |
| 104 | 106 |
# Macro blocks accept 2 or 3 arguments: |
| 105 | 107 |
# * obj: the object that is rendered (eg. an Issue, a WikiContent...) |
| ... | ... | |
| 157 | 159 |
# |
| 158 | 160 |
# If a block of text is given, the closing tag }} must be at the start of a new line. |
| 159 | 161 |
def macro(name, options={}, &block)
|
| 160 |
options.assert_valid_keys(:desc, :parse_args) |
|
| 162 |
options.assert_valid_keys(:desc, :parse_args, :sections)
|
|
| 161 | 163 |
unless /\A\w+\z/.match?(name.to_s) |
| 162 | 164 |
raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
|
| 163 | 165 |
end |
| ... | ... | |
| 297 | 299 |
desc "Inserts of collapsed block of text. Examples:\n\n" + |
| 298 | 300 |
"{{collapse\nThis is a block of text that is collapsed by default.\nIt can be expanded by clicking a link.\n}}\n\n" +
|
| 299 | 301 |
"{{collapse(View details...)\nWith custom link text.\n}}"
|
| 300 |
macro :collapse do |obj, args, text| |
|
| 302 |
macro :collapse, :sections => true do |obj, args, text|
|
|
| 301 | 303 |
html_id = "collapse-#{Redmine::Utils.random_hex(4)}"
|
| 302 | 304 |
show_label = args[0] || l(:button_show) |
| 303 | 305 |
hide_label = args[1] || args[0] || l(:button_hide) |
| test/helpers/application_helper_test.rb | ||
|---|---|---|
| 1685 | 1685 |
end |
| 1686 | 1686 |
end |
| 1687 | 1687 | |
| 1688 |
def test_section_edit_links_should_ignore_headings_from_include_macro |
|
| 1689 |
raw = <<~RAW |
|
| 1690 |
# Test Wiki |
|
| 1691 | ||
| 1692 |
## Some local heading |
|
| 1693 | ||
| 1694 |
Local text |
|
| 1695 | ||
| 1696 |
{{include(Included_page)}}
|
|
| 1697 | ||
| 1698 |
## More local heading |
|
| 1699 | ||
| 1700 |
More local text |
|
| 1701 |
RAW |
|
| 1702 |
@project = Project.find(1) |
|
| 1703 |
set_language_if_valid 'en' |
|
| 1704 |
with_settings :text_formatting => 'common_mark' do |
|
| 1705 |
page = WikiPage.new(:wiki => Wiki.find(1), :title => 'Included_page') |
|
| 1706 |
page.content = WikiContent.new( |
|
| 1707 |
:text => "# Included Heading 1\n\n## Included Heading 2\n\nIncluded text\n", |
|
| 1708 |
:author => User.find(1)) |
|
| 1709 |
page.save! |
|
| 1710 | ||
| 1711 |
result = |
|
| 1712 |
textilizable( |
|
| 1713 |
raw, |
|
| 1714 |
:edit_section_links => |
|
| 1715 |
{:controller => 'wiki', :action => 'edit',
|
|
| 1716 |
:project_id => '1', :id => 'Test'} |
|
| 1717 |
).delete("\n")
|
|
| 1718 | ||
| 1719 |
# Headings of the included page are rendered... |
|
| 1720 |
assert_include 'Included Heading 2', result |
|
| 1721 |
# ...but they are not sections of this page, so they must not get edit links |
|
| 1722 |
assert_equal %w(2 3), result.scan(/edit\?section=(\d+)/).flatten |
|
| 1723 | ||
| 1724 |
# More local heading should have section=3 edit link |
|
| 1725 |
assert_match( |
|
| 1726 |
Regexp.new( |
|
| 1727 |
'<div class="contextual heading-2" title="Edit this section" id="section-3">' \ |
|
| 1728 |
'<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\\?section=3">' \ |
|
| 1729 |
'<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-.*\\.svg#icon--edit"></use></svg>' \ |
|
| 1730 |
'<span class="icon-label">Edit this section</span>' \ |
|
| 1731 |
'</a></div>' \ |
|
| 1732 |
'<a name="More-local-heading"></a>' \ |
|
| 1733 |
'<h2 >More local heading<a href="#More-local-heading" class="wiki-anchor">¶</a></h2>' |
|
| 1734 |
), |
|
| 1735 |
result |
|
| 1736 |
) |
|
| 1737 | ||
| 1738 |
# The section number must match the one used when editing the page |
|
| 1739 |
section, _hash = Redmine::WikiFormatting.formatter.new(raw).get_section(3) |
|
| 1740 |
assert section.start_with?('## More local heading')
|
|
| 1741 |
end |
|
| 1742 |
end |
|
| 1743 | ||
| 1744 |
def test_section_edit_links_should_ignore_headings_from_include_macro_inside_collapse_macro |
|
| 1745 |
raw = <<~RAW |
|
| 1746 |
# Test Wiki |
|
| 1747 | ||
| 1748 |
## Section A |
|
| 1749 | ||
| 1750 |
{{collapse
|
|
| 1751 |
## Section B |
|
| 1752 | ||
| 1753 |
{{include(Included_page)}}
|
|
| 1754 |
}} |
|
| 1755 | ||
| 1756 |
## Section C |
|
| 1757 |
RAW |
|
| 1758 |
@project = Project.find(1) |
|
| 1759 |
set_language_if_valid 'en' |
|
| 1760 |
with_settings :text_formatting => 'common_mark' do |
|
| 1761 |
page = WikiPage.new(:wiki => Wiki.find(1), :title => 'Included_page') |
|
| 1762 |
page.content = WikiContent.new(:text => "## Included Heading\n", :author => User.find(1)) |
|
| 1763 |
page.save! |
|
| 1764 | ||
| 1765 |
result = |
|
| 1766 |
textilizable( |
|
| 1767 |
raw, |
|
| 1768 |
:edit_section_links => |
|
| 1769 |
{:controller => 'wiki', :action => 'edit',
|
|
| 1770 |
:project_id => '1', :id => 'Test'} |
|
| 1771 |
).delete("\n")
|
|
| 1772 | ||
| 1773 |
assert_include 'Included Heading', result |
|
| 1774 |
assert_equal %w(2 3 4), result.scan(/edit\?section=(\d+)/).flatten |
|
| 1775 |
assert_match( |
|
| 1776 |
/id="section-4"><a class="icon-only icon-edit" href="[^"]*section=4">.*?<\/a><\/div><a name="Section-C"><\/a>/, |
|
| 1777 |
result |
|
| 1778 |
) |
|
| 1779 | ||
| 1780 |
section, _hash = Redmine::WikiFormatting.formatter.new(raw).get_section(4) |
|
| 1781 |
assert section.start_with?('## Section C')
|
|
| 1782 |
end |
|
| 1783 |
end |
|
| 1784 | ||
| 1688 | 1785 |
def test_default_formatter |
| 1689 | 1786 |
with_settings :text_formatting => 'unknown' do |
| 1690 | 1787 |
text = 'a *link*: http://www.example.net/' |
- « Previous
- 1
- …
- 3
- 4
- 5
- Next »