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