##// END OF EJS Templates
Merged r13105 (#16669)....
Jean-Philippe Lang -
r12883:d548d9338e78
parent child
Show More
@@ -1,136 +1,137
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'cgi'
18 require 'cgi'
19
19
20 module Redmine
20 module Redmine
21 module WikiFormatting
21 module WikiFormatting
22 module Markdown
22 module Markdown
23 class HTML < Redcarpet::Render::HTML
23 class HTML < Redcarpet::Render::HTML
24 include ActionView::Helpers::TagHelper
24 include ActionView::Helpers::TagHelper
25
25
26 def link(link, title, content)
26 def link(link, title, content)
27 css = nil
27 css = nil
28 unless link && link.starts_with?('/')
28 unless link && link.starts_with?('/')
29 css = 'external'
29 css = 'external'
30 end
30 end
31 content_tag('a', content.html_safe, :href => link, :title => title, :class => css)
31 content_tag('a', content.html_safe, :href => link, :title => title, :class => css)
32 end
32 end
33
33
34 def block_code(code, language)
34 def block_code(code, language)
35 if language.present?
35 if language.present?
36 "<pre><code class=\"#{CGI.escapeHTML language} syntaxhl\">" +
36 "<pre><code class=\"#{CGI.escapeHTML language} syntaxhl\">" +
37 Redmine::SyntaxHighlighting.highlight_by_language(code, language) +
37 Redmine::SyntaxHighlighting.highlight_by_language(code, language) +
38 "</code></pre>"
38 "</code></pre>"
39 else
39 else
40 "<pre>" + CGI.escapeHTML(code) + "</pre>"
40 "<pre>" + CGI.escapeHTML(code) + "</pre>"
41 end
41 end
42 end
42 end
43 end
43 end
44
44
45 class Formatter
45 class Formatter
46 def initialize(text)
46 def initialize(text)
47 @text = text
47 @text = text
48 end
48 end
49
49
50 def to_html(*args)
50 def to_html(*args)
51 html = formatter.render(@text)
51 html = formatter.render(@text)
52 # restore wiki links eg. [[Foo]]
52 # restore wiki links eg. [[Foo]]
53 html.gsub!(%r{\[<a href="(.*?)">(.*?)</a>\]}) do
53 html.gsub!(%r{\[<a href="(.*?)">(.*?)</a>\]}) do
54 "[[#{$2}]]"
54 "[[#{$2}]]"
55 end
55 end
56 # restore Redmine links with double-quotes, eg. version:"1.0"
56 # restore Redmine links with double-quotes, eg. version:"1.0"
57 html.gsub!(/(\w):&quot;(.+?)&quot;/) do
57 html.gsub!(/(\w):&quot;(.+?)&quot;/) do
58 "#{$1}:\"#{$2}\""
58 "#{$1}:\"#{$2}\""
59 end
59 end
60 html
60 html
61 end
61 end
62
62
63 def get_section(index)
63 def get_section(index)
64 section = extract_sections(index)[1]
64 section = extract_sections(index)[1]
65 hash = Digest::MD5.hexdigest(section)
65 hash = Digest::MD5.hexdigest(section)
66 return section, hash
66 return section, hash
67 end
67 end
68
68
69 def update_section(index, update, hash=nil)
69 def update_section(index, update, hash=nil)
70 t = extract_sections(index)
70 t = extract_sections(index)
71 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
71 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
72 raise Redmine::WikiFormatting::StaleSectionError
72 raise Redmine::WikiFormatting::StaleSectionError
73 end
73 end
74 t[1] = update unless t[1].blank?
74 t[1] = update unless t[1].blank?
75 t.reject(&:blank?).join "\n\n"
75 t.reject(&:blank?).join "\n\n"
76 end
76 end
77
77
78 def extract_sections(index)
78 def extract_sections(index)
79 sections = ['', '', '']
79 sections = ['', '', '']
80 offset = 0
80 offset = 0
81 i = 0
81 i = 0
82 l = 1
82 l = 1
83 inside_pre = false
83 inside_pre = false
84 @text.split(/(^(?:.+\r?\n\r?(?:\=+|\-+)|#+.+|~~~.*)\s*$)/).each do |part|
84 @text.split(/(^(?:.+\r?\n\r?(?:\=+|\-+)|#+.+|~~~.*)\s*$)/).each do |part|
85 level = nil
85 level = nil
86 if part =~ /\A~{3,}(\S+)?\s*$/
86 if part =~ /\A~{3,}(\S+)?\s*$/
87 if $1
87 if $1
88 if !inside_pre
88 if !inside_pre
89 inside_pre = true
89 inside_pre = true
90 end
90 end
91 else
91 else
92 inside_pre = !inside_pre
92 inside_pre = !inside_pre
93 end
93 end
94 elsif inside_pre
94 elsif inside_pre
95 # nop
95 # nop
96 elsif part =~ /\A(#+).+/
96 elsif part =~ /\A(#+).+/
97 level = $1.size
97 level = $1.size
98 elsif part =~ /\A.+\r?\n\r?(\=+|\-+)\s*$/
98 elsif part =~ /\A.+\r?\n\r?(\=+|\-+)\s*$/
99 level = $1.include?('=') ? 1 : 2
99 level = $1.include?('=') ? 1 : 2
100 end
100 end
101 if level
101 if level
102 i += 1
102 i += 1
103 if offset == 0 && i == index
103 if offset == 0 && i == index
104 # entering the requested section
104 # entering the requested section
105 offset = 1
105 offset = 1
106 l = level
106 l = level
107 elsif offset == 1 && i > index && level <= l
107 elsif offset == 1 && i > index && level <= l
108 # leaving the requested section
108 # leaving the requested section
109 offset = 2
109 offset = 2
110 end
110 end
111 end
111 end
112 sections[offset] << part
112 sections[offset] << part
113 end
113 end
114 sections.map(&:strip)
114 sections.map(&:strip)
115 end
115 end
116
116
117 private
117 private
118
118
119 def formatter
119 def formatter
120 @@formatter ||= Redcarpet::Markdown.new(
120 @@formatter ||= Redcarpet::Markdown.new(
121 Redmine::WikiFormatting::Markdown::HTML.new(
121 Redmine::WikiFormatting::Markdown::HTML.new(
122 :filter_html => true,
122 :filter_html => true,
123 :hard_wrap => true
123 :hard_wrap => true
124 ),
124 ),
125 :autolink => true,
125 :autolink => true,
126 :fenced_code_blocks => true,
126 :fenced_code_blocks => true,
127 :space_after_headers => true,
127 :space_after_headers => true,
128 :tables => true,
128 :tables => true,
129 :strikethrough => true,
129 :strikethrough => true,
130 :superscript => true
130 :superscript => true,
131 :no_intra_emphasis => true
131 )
132 )
132 end
133 end
133 end
134 end
134 end
135 end
135 end
136 end
136 end
137 end
@@ -1,64 +1,68
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
19
20 class Redmine::WikiFormatting::MarkdownFormatterTest < ActionView::TestCase
20 class Redmine::WikiFormatting::MarkdownFormatterTest < ActionView::TestCase
21 if Object.const_defined?(:Redcarpet)
21 if Object.const_defined?(:Redcarpet)
22
22
23 def setup
23 def setup
24 @formatter = Redmine::WikiFormatting::Markdown::Formatter
24 @formatter = Redmine::WikiFormatting::Markdown::Formatter
25 end
25 end
26
26
27 def test_inline_style
27 def test_inline_style
28 assert_equal "<p><strong>foo</strong></p>", @formatter.new("**foo**").to_html.strip
28 assert_equal "<p><strong>foo</strong></p>", @formatter.new("**foo**").to_html.strip
29 end
29 end
30
30
31 def test_not_set_intra_emphasis
32 assert_equal "<p>foo_bar_baz</p>", @formatter.new("foo_bar_baz").to_html.strip
33 end
34
31 def test_wiki_links_should_be_preserved
35 def test_wiki_links_should_be_preserved
32 text = 'This is a wiki link: [[Foo]]'
36 text = 'This is a wiki link: [[Foo]]'
33 assert_include '[[Foo]]', @formatter.new(text).to_html
37 assert_include '[[Foo]]', @formatter.new(text).to_html
34 end
38 end
35
39
36 def test_redmine_links_with_double_quotes_should_be_preserved
40 def test_redmine_links_with_double_quotes_should_be_preserved
37 text = 'This is a redmine link: version:"1.0"'
41 text = 'This is a redmine link: version:"1.0"'
38 assert_include 'version:"1.0"', @formatter.new(text).to_html
42 assert_include 'version:"1.0"', @formatter.new(text).to_html
39 end
43 end
40
44
41 def test_should_support_syntax_highligth
45 def test_should_support_syntax_highligth
42 text = <<-STR
46 text = <<-STR
43 ~~~ruby
47 ~~~ruby
44 def foo
48 def foo
45 end
49 end
46 ~~~
50 ~~~
47 STR
51 STR
48 assert_select_in @formatter.new(text).to_html, 'pre code.ruby.syntaxhl' do
52 assert_select_in @formatter.new(text).to_html, 'pre code.ruby.syntaxhl' do
49 assert_select 'span.keyword', :text => 'def'
53 assert_select 'span.keyword', :text => 'def'
50 end
54 end
51 end
55 end
52
56
53 def test_external_links_should_have_external_css_class
57 def test_external_links_should_have_external_css_class
54 text = 'This is a [link](http://example.net/)'
58 text = 'This is a [link](http://example.net/)'
55 assert_equal '<p>This is a <a class="external" href="http://example.net/">link</a></p>', @formatter.new(text).to_html.strip
59 assert_equal '<p>This is a <a class="external" href="http://example.net/">link</a></p>', @formatter.new(text).to_html.strip
56 end
60 end
57
61
58 def test_locals_links_should_not_have_external_css_class
62 def test_locals_links_should_not_have_external_css_class
59 text = 'This is a [link](/issues)'
63 text = 'This is a [link](/issues)'
60 assert_equal '<p>This is a <a href="/issues">link</a></p>', @formatter.new(text).to_html.strip
64 assert_equal '<p>This is a <a href="/issues">link</a></p>', @formatter.new(text).to_html.strip
61 end
65 end
62
66
63 end
67 end
64 end
68 end
General Comments 0
You need to be logged in to leave comments. Login now