Project

General

Profile

Feature #20497 » 0001-adds-an-additional-Markdown-format-that-allows-user-.patch

patch that adds a Markdown (with HTML) formatter - Jens Krämer, 2019-10-23 09:19

View differences:

lib/redmine.rb
326 326

  
327 327
Redmine::WikiFormatting.map do |format|
328 328
  format.register :textile
329
  format.register :markdown if Object.const_defined?(:Redcarpet)
329
  if Object.const_defined?(:Redcarpet)
330
    format.register :markdown
331
    format.register :markdown_html, label: 'Markdown (with HTML)'
332
  end
330 333
end
331 334

  
332 335
ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
lib/redmine/wiki_formatting/markdown_html/formatter.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2019  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require 'cgi'
21

  
22
module Redmine
23
  module WikiFormatting
24
    module MarkdownHtml
25

  
26
      class HtmlScrubber < Rails::Html::PermitScrubber
27
        def initialize
28
          super
29
          # default set of tags, plus tables and u
30
          self.tags = Set.new(%w(table tr td thead tfoot tbody u strong em b i p code pre tt samp kbd var sub sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr acronym a img blockquote del ins))
31
          # default set of attributes, plus id (which will still be scrubbed
32
          # in most cases, see scrub_attribute below)
33
          self.attributes = Set.new(%w(id href src width height alt cite datetime title class name xml:lang abbr))
34
        end
35

  
36
        private
37

  
38
        # overridden to only allow some id attribute values on sup and li
39
        # elements for footnotes
40
        def scrub_attribute(node, attr)
41
          if attr.name == 'id' and not footnote_id?(node, attr)
42
            attr.remove
43
          end
44
          super
45
        end
46

  
47
        def footnote_id?(node, attr)
48
          (
49
            node.name == 'sup' &&
50
            attr.value.to_s =~ /\Afnref\d+\z/
51
          ) or (
52
            node.name == 'li' &&
53
            attr.value.to_s =~ /\Afn\d+\z/
54
          )
55
        end
56
      end
57

  
58
      class Formatter < Redmine::WikiFormatting::Markdown::Formatter
59
        include ActionView::Helpers::SanitizeHelper
60

  
61
        def to_html(*_)
62
          sanitize(super, scrubber: HtmlScrubber.new).to_str
63
        end
64

  
65
        private
66

  
67
        def formatter
68
          @@html_formatter ||= Redcarpet::Markdown.new(
69
            Redmine::WikiFormatting::Markdown::HTML.new(
70
              :hard_wrap => true
71
            ),
72
            :autolink => true,
73
            :fenced_code_blocks => true,
74
            :space_after_headers => true,
75
            :tables => true,
76
            :strikethrough => true,
77
            :superscript => true,
78
            :no_intra_emphasis => true,
79
            :footnotes => true,
80
            :lax_spacing => true,
81
            :underline => true
82
          )
83
        end
84
      end
85
    end
86
  end
87
end
lib/redmine/wiki_formatting/markdown_html/helper.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2019  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module Redmine
21
  module WikiFormatting
22
    module MarkdownHtml
23
      Helper = Redmine::WikiFormatting::Markdown::Helper
24
    end
25
  end
26
end
lib/redmine/wiki_formatting/markdown_html/html_parser.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2019  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
module Redmine
21
  module WikiFormatting
22
    module MarkdownHtml
23
      HtmlParser = Redmine::WikiFormatting::Markdown::HtmlParser
24
    end
25
  end
26
end
test/helpers/application_helper_test.rb
182 182
    end
183 183
  end
184 184

  
185
  def test_attached_images_with_markdown_html_and_non_ascii_filename
186
    skip unless Object.const_defined?(:Redcarpet)
187

  
188
    to_test = {
189
      'CAFÉ.JPG' => 'CAF%C3%89.JPG',
190
      'crème.jpg' => 'cr%C3%A8me.jpg',
191
    }
192
    with_settings :text_formatting => 'markdown_html' do
193
      to_test.each do |filename, result|
194
        attachment = Attachment.generate!(:filename => filename)
195
        assert_include %(<img src="/attachments/download/#{attachment.id}/#{result}" alt="">), textilizable("![](#{filename})", :attachments => [attachment])
196
      end
197
    end
198
  end
199

  
185 200
  def test_attached_images_with_hires_naming
186 201
    attachment = Attachment.generate!(:filename => 'image@2x.png')
187 202
    assert_equal %(<p><img src="/attachments/download/#{attachment.id}/image@2x.png" srcset="/attachments/download/#{attachment.id}/image@2x.png 2x" alt="" /></p>),
test/unit/lib/redmine/wiki_formatting/markdown_html_formatter_test.rb
1
# frozen_string_literal: true
2

  
3
# Redmine - project management software
4
# Copyright (C) 2006-2019  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

  
20
require File.expand_path('../../../../../test_helper', __FILE__)
21

  
22
class Redmine::WikiFormatting::MarkdownHtmlFormatterTest < ActionView::TestCase
23
  if Object.const_defined?(:Redcarpet)
24

  
25
  def setup
26
    @formatter = Redmine::WikiFormatting::MarkdownHtml::Formatter
27
  end
28

  
29
  def test_syntax_error_in_image_reference_should_not_raise_exception
30
    assert @formatter.new("!>[](foo.png)").to_html
31
  end
32

  
33
  def test_empty_image_should_not_raise_exception
34
    assert @formatter.new("![]()").to_html
35
  end
36

  
37
  # re-using the formatter after getting above error crashes the
38
  # ruby interpreter. This seems to be related to
39
  # https://github.com/vmg/redcarpet/issues/318
40
  def test_should_not_crash_redcarpet_after_syntax_error
41
    @formatter.new("!>[](foo.png)").to_html rescue nil
42
    assert @formatter.new("![](foo.png)").to_html.present?
43
  end
44

  
45
  def test_inline_style
46
    assert_equal "<p><strong>foo</strong></p>", @formatter.new("**foo**").to_html.strip
47
  end
48

  
49
  def test_not_set_intra_emphasis
50
    assert_equal "<p>foo_bar_baz</p>", @formatter.new("foo_bar_baz").to_html.strip
51
  end
52

  
53
  def test_wiki_links_should_be_preserved
54
    text = 'This is a wiki link: [[Foo]]'
55
    assert_include '[[Foo]]', @formatter.new(text).to_html
56
  end
57

  
58
  def test_redmine_links_with_double_quotes_should_be_preserved
59
    text = 'This is a redmine link: version:"1.0"'
60
    assert_include 'version:"1.0"', @formatter.new(text).to_html
61
  end
62

  
63
  def test_should_support_syntax_highlight
64
    text = <<-STR
65
~~~ruby
66
def foo
67
end
68
~~~
69
STR
70
    assert_select_in @formatter.new(text).to_html, 'pre code.ruby.syntaxhl' do
71
      assert_select 'span.k', :text => 'def'
72
    end
73
  end
74

  
75
  def test_should_not_allow_invalid_language_for_code_blocks
76
    text = <<-STR
77
~~~foo
78
test
79
~~~
80
STR
81
    assert_equal "<pre>test\n</pre>", @formatter.new(text).to_html
82
  end
83

  
84
  def test_external_links_should_have_external_css_class
85
    text = 'This is a [link](http://example.net/)'
86
    assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', @formatter.new(text).to_html.strip
87
  end
88

  
89
  def test_locals_links_should_not_have_external_css_class
90
    text = 'This is a [link](/issues)'
91
    assert_equal '<p>This is a <a href="/issues">link</a></p>', @formatter.new(text).to_html.strip
92
  end
93

  
94
  def test_markdown_should_not_require_surrounded_empty_line
95
    text = <<-STR
96
This is a list:
97
* One
98
* Two
99
STR
100
    assert_equal "<p>This is a list:</p>\n\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", @formatter.new(text).to_html.strip
101
  end
102

  
103
  def test_footnotes
104
    text = <<-STR
105
This is some text[^1].
106

  
107
[^1]: This is the foot note
108
STR
109

  
110
    # rails html sanitizer replaces entities with their utf8 counterparts
111
    expected = <<-EXPECTED
112
<p>This is some text<sup id="fnref1"><a href="#fn1">1</a></sup>.</p>
113
<div class="footnotes">
114
<hr>
115
<ol>
116

  
117
<li id="fn1">
118
<p>This is the foot note <a href="#fnref1">↩</a></p>
119
</li>
120

  
121
</ol>
122
</div>
123
EXPECTED
124

  
125
    assert_equal expected.gsub(%r{[\r\n\t]}, ''), @formatter.new(text).to_html.gsub(%r{[\r\n\t]}, '')
126
  end
127

  
128
  STR_WITH_PRE = [
129
    # 0
130
    <<~STR.chomp,
131
      # Title
132

  
133
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
134
    STR
135
    # 1
136
    <<~STR.chomp,
137
      ## Heading 2
138

  
139
      ~~~ruby
140
        def foo
141
        end
142
      ~~~
143

  
144
      Morbi facilisis accumsan orci non pharetra.
145

  
146
      ```
147
      Pre Content:
148

  
149
      ## Inside pre
150

  
151
      <tag> inside pre block
152

  
153
      Morbi facilisis accumsan orci non pharetra.
154
      ```
155
    STR
156
    # 2
157
    <<~STR.chomp,
158
      ### Heading 3
159

  
160
      Nulla nunc nisi, egestas in ornare vel, posuere ac libero.
161
    STR
162
  ]
163

  
164
  def test_get_section_should_ignore_pre_content
165
    text = STR_WITH_PRE.join("\n\n")
166

  
167
    assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
168
    assert_section_with_hash STR_WITH_PRE[2], text, 3
169
  end
170

  
171
  def test_update_section_should_not_escape_pre_content_outside_section
172
    text = STR_WITH_PRE.join("\n\n")
173
    replacement = "New text"
174

  
175
    assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
176
      @formatter.new(text).update_section(3, replacement)
177
  end
178

  
179
  def test_should_support_underlined_text
180
    text = 'This _text_ should be underlined'
181
    assert_equal '<p>This <u>text</u> should be underlined</p>', @formatter.new(text).to_html.strip
182
  end
183

  
184
  def test_should_support_html_tables
185
    text = '<table style="background: red"><tr><td>Cell</td></tr></table>'
186
    assert_equal '<table><tr><td>Cell</td></tr></table>', @formatter.new(text).to_html.strip
187
  end
188

  
189
  private
190

  
191
  def assert_section_with_hash(expected, text, index)
192
    result = @formatter.new(text).get_section(index)
193

  
194
    assert_kind_of Array, result
195
    assert_equal 2, result.size
196
    assert_equal expected, result.first, "section content did not match"
197
    assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
198
  end
199
  end
200
end
    (1-1/1)