##// END OF EJS Templates
Extends child_pages macro to display child pages based on page parameter (#1975)....
Jean-Philippe Lang -
r2051:06266c8fecd7
parent child
Show More
@@ -0,0 +1,98
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../../../../test_helper'
19
20 class Redmine::WikiFormatting::MacrosTest < HelperTestCase
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules, :users,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues,
26 :versions, :documents,
27 :wikis, :wiki_pages, :wiki_contents,
28 :boards, :messages,
29 :attachments
30
31 def setup
32 super
33 @project = nil
34 end
35
36 def teardown
37 end
38
39 def test_macro_hello_world
40 text = "{{hello_world}}"
41 assert textilizable(text).match(/Hello world!/)
42 # escaping
43 text = "!{{hello_world}}"
44 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
45 end
46
47 def test_macro_include
48 @project = Project.find(1)
49 # include a page of the current project wiki
50 text = "{{include(Another page)}}"
51 assert textilizable(text).match(/This is a link to a ticket/)
52
53 @project = nil
54 # include a page of a specific project wiki
55 text = "{{include(ecookbook:Another page)}}"
56 assert textilizable(text).match(/This is a link to a ticket/)
57
58 text = "{{include(ecookbook:)}}"
59 assert textilizable(text).match(/CookBook documentation/)
60
61 text = "{{include(unknowidentifier:somepage)}}"
62 assert textilizable(text).match(/Page not found/)
63 end
64
65 def test_macro_child_pages
66 expected = "<p><ul class=\"pages-hierarchy\">\n" +
67 "<li><a href=\"/wiki/ecookbook/Child_1\">Child 1</a></li>\n" +
68 "<li><a href=\"/wiki/ecookbook/Child_2\">Child 2</a></li>\n" +
69 "</ul>\n</p>"
70
71 @project = Project.find(1)
72 # child pages of the current wiki page
73 assert_equal expected, textilizable("{{child_pages}}", :object => WikiPage.find(2).content)
74 # child pages of another page
75 assert_equal expected, textilizable("{{child_pages(Another_page)}}", :object => WikiPage.find(1).content)
76
77 @project = Project.find(2)
78 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page)}}", :object => WikiPage.find(1).content)
79 end
80
81 def test_macro_child_pages_with_option
82 expected = "<p><ul class=\"pages-hierarchy\">\n" +
83 "<li><a href=\"/wiki/ecookbook/Another_page\">Another page</a>\n" +
84 "<ul class=\"pages-hierarchy\">\n" +
85 "<li><a href=\"/wiki/ecookbook/Child_1\">Child 1</a></li>\n" +
86 "<li><a href=\"/wiki/ecookbook/Child_2\">Child 2</a></li>\n" +
87 "</ul>\n</li>\n</ul>\n</p>"
88
89 @project = Project.find(1)
90 # child pages of the current wiki page
91 assert_equal expected, textilizable("{{child_pages(parent=1)}}", :object => WikiPage.find(2).content)
92 # child pages of another page
93 assert_equal expected, textilizable("{{child_pages(Another_page, parent=1)}}", :object => WikiPage.find(1).content)
94
95 @project = Project.find(2)
96 assert_equal expected, textilizable("{{child_pages(ecookbook:Another_page, parent=1)}}", :object => WikiPage.find(1).content)
97 end
98 end
@@ -1,589 +1,605
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20 require 'forwardable'
20 require 'forwardable'
21
21
22 module ApplicationHelper
22 module ApplicationHelper
23 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::WikiFormatting::Macros::Definitions
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 def current_role
29 def current_role
30 @current_role ||= User.current.role_for_project(@project)
30 @current_role ||= User.current.role_for_project(@project)
31 end
31 end
32
32
33 # Return true if user is authorized for controller/action, otherwise false
33 # Return true if user is authorized for controller/action, otherwise false
34 def authorize_for(controller, action)
34 def authorize_for(controller, action)
35 User.current.allowed_to?({:controller => controller, :action => action}, @project)
35 User.current.allowed_to?({:controller => controller, :action => action}, @project)
36 end
36 end
37
37
38 # Display a link if user is authorized
38 # Display a link if user is authorized
39 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
39 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
40 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
40 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
41 end
41 end
42
42
43 # Display a link to remote if user is authorized
43 # Display a link to remote if user is authorized
44 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
44 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
45 url = options[:url] || {}
45 url = options[:url] || {}
46 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
46 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
47 end
47 end
48
48
49 # Display a link to user's account page
49 # Display a link to user's account page
50 def link_to_user(user)
50 def link_to_user(user)
51 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
51 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
52 end
52 end
53
53
54 def link_to_issue(issue, options={})
54 def link_to_issue(issue, options={})
55 options[:class] ||= ''
55 options[:class] ||= ''
56 options[:class] << ' issue'
56 options[:class] << ' issue'
57 options[:class] << ' closed' if issue.closed?
57 options[:class] << ' closed' if issue.closed?
58 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
58 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
59 end
59 end
60
60
61 # Generates a link to an attachment.
61 # Generates a link to an attachment.
62 # Options:
62 # Options:
63 # * :text - Link text (default to attachment filename)
63 # * :text - Link text (default to attachment filename)
64 # * :download - Force download (default: false)
64 # * :download - Force download (default: false)
65 def link_to_attachment(attachment, options={})
65 def link_to_attachment(attachment, options={})
66 text = options.delete(:text) || attachment.filename
66 text = options.delete(:text) || attachment.filename
67 action = options.delete(:download) ? 'download' : 'show'
67 action = options.delete(:download) ? 'download' : 'show'
68
68
69 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
69 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
70 end
70 end
71
71
72 def toggle_link(name, id, options={})
72 def toggle_link(name, id, options={})
73 onclick = "Element.toggle('#{id}'); "
73 onclick = "Element.toggle('#{id}'); "
74 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
74 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
75 onclick << "return false;"
75 onclick << "return false;"
76 link_to(name, "#", :onclick => onclick)
76 link_to(name, "#", :onclick => onclick)
77 end
77 end
78
78
79 def image_to_function(name, function, html_options = {})
79 def image_to_function(name, function, html_options = {})
80 html_options.symbolize_keys!
80 html_options.symbolize_keys!
81 tag(:input, html_options.merge({
81 tag(:input, html_options.merge({
82 :type => "image", :src => image_path(name),
82 :type => "image", :src => image_path(name),
83 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
83 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
84 }))
84 }))
85 end
85 end
86
86
87 def prompt_to_remote(name, text, param, url, html_options = {})
87 def prompt_to_remote(name, text, param, url, html_options = {})
88 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
88 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
89 link_to name, {}, html_options
89 link_to name, {}, html_options
90 end
90 end
91
91
92 def format_date(date)
92 def format_date(date)
93 return nil unless date
93 return nil unless date
94 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
94 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
95 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
95 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
96 date.strftime(@date_format)
96 date.strftime(@date_format)
97 end
97 end
98
98
99 def format_time(time, include_date = true)
99 def format_time(time, include_date = true)
100 return nil unless time
100 return nil unless time
101 time = time.to_time if time.is_a?(String)
101 time = time.to_time if time.is_a?(String)
102 zone = User.current.time_zone
102 zone = User.current.time_zone
103 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
103 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
104 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
104 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
105 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
105 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
106 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
106 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
107 end
107 end
108
108
109 def distance_of_date_in_words(from_date, to_date = 0)
109 def distance_of_date_in_words(from_date, to_date = 0)
110 from_date = from_date.to_date if from_date.respond_to?(:to_date)
110 from_date = from_date.to_date if from_date.respond_to?(:to_date)
111 to_date = to_date.to_date if to_date.respond_to?(:to_date)
111 to_date = to_date.to_date if to_date.respond_to?(:to_date)
112 distance_in_days = (to_date - from_date).abs
112 distance_in_days = (to_date - from_date).abs
113 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
113 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
114 end
114 end
115
115
116 def due_date_distance_in_words(date)
116 def due_date_distance_in_words(date)
117 if date
117 if date
118 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
118 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
119 end
119 end
120 end
120 end
121
121
122 def render_page_hierarchy(pages, node=nil)
123 content = ''
124 if pages[node]
125 content << "<ul class=\"pages-hierarchy\">\n"
126 pages[node].each do |page|
127 content << "<li>"
128 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
129 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
130 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
131 content << "</li>\n"
132 end
133 content << "</ul>\n"
134 end
135 content
136 end
137
122 # Truncates and returns the string as a single line
138 # Truncates and returns the string as a single line
123 def truncate_single_line(string, *args)
139 def truncate_single_line(string, *args)
124 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
140 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
125 end
141 end
126
142
127 def html_hours(text)
143 def html_hours(text)
128 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
144 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
129 end
145 end
130
146
131 def authoring(created, author)
147 def authoring(created, author)
132 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
148 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
133 link_to(distance_of_time_in_words(Time.now, created),
149 link_to(distance_of_time_in_words(Time.now, created),
134 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
150 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
135 :title => format_time(created))
151 :title => format_time(created))
136 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
152 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
137 l(:label_added_time_by, author_tag, time_tag)
153 l(:label_added_time_by, author_tag, time_tag)
138 end
154 end
139
155
140 def l_or_humanize(s, options={})
156 def l_or_humanize(s, options={})
141 k = "#{options[:prefix]}#{s}".to_sym
157 k = "#{options[:prefix]}#{s}".to_sym
142 l_has_string?(k) ? l(k) : s.to_s.humanize
158 l_has_string?(k) ? l(k) : s.to_s.humanize
143 end
159 end
144
160
145 def day_name(day)
161 def day_name(day)
146 l(:general_day_names).split(',')[day-1]
162 l(:general_day_names).split(',')[day-1]
147 end
163 end
148
164
149 def month_name(month)
165 def month_name(month)
150 l(:actionview_datehelper_select_month_names).split(',')[month-1]
166 l(:actionview_datehelper_select_month_names).split(',')[month-1]
151 end
167 end
152
168
153 def syntax_highlight(name, content)
169 def syntax_highlight(name, content)
154 type = CodeRay::FileType[name]
170 type = CodeRay::FileType[name]
155 type ? CodeRay.scan(content, type).html : h(content)
171 type ? CodeRay.scan(content, type).html : h(content)
156 end
172 end
157
173
158 def to_path_param(path)
174 def to_path_param(path)
159 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
175 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
160 end
176 end
161
177
162 def pagination_links_full(paginator, count=nil, options={})
178 def pagination_links_full(paginator, count=nil, options={})
163 page_param = options.delete(:page_param) || :page
179 page_param = options.delete(:page_param) || :page
164 url_param = params.dup
180 url_param = params.dup
165 # don't reuse params if filters are present
181 # don't reuse params if filters are present
166 url_param.clear if url_param.has_key?(:set_filter)
182 url_param.clear if url_param.has_key?(:set_filter)
167
183
168 html = ''
184 html = ''
169 html << link_to_remote(('&#171; ' + l(:label_previous)),
185 html << link_to_remote(('&#171; ' + l(:label_previous)),
170 {:update => 'content',
186 {:update => 'content',
171 :url => url_param.merge(page_param => paginator.current.previous),
187 :url => url_param.merge(page_param => paginator.current.previous),
172 :complete => 'window.scrollTo(0,0)'},
188 :complete => 'window.scrollTo(0,0)'},
173 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
189 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
174
190
175 html << (pagination_links_each(paginator, options) do |n|
191 html << (pagination_links_each(paginator, options) do |n|
176 link_to_remote(n.to_s,
192 link_to_remote(n.to_s,
177 {:url => {:params => url_param.merge(page_param => n)},
193 {:url => {:params => url_param.merge(page_param => n)},
178 :update => 'content',
194 :update => 'content',
179 :complete => 'window.scrollTo(0,0)'},
195 :complete => 'window.scrollTo(0,0)'},
180 {:href => url_for(:params => url_param.merge(page_param => n))})
196 {:href => url_for(:params => url_param.merge(page_param => n))})
181 end || '')
197 end || '')
182
198
183 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
199 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
184 {:update => 'content',
200 {:update => 'content',
185 :url => url_param.merge(page_param => paginator.current.next),
201 :url => url_param.merge(page_param => paginator.current.next),
186 :complete => 'window.scrollTo(0,0)'},
202 :complete => 'window.scrollTo(0,0)'},
187 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
203 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
188
204
189 unless count.nil?
205 unless count.nil?
190 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
206 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
191 end
207 end
192
208
193 html
209 html
194 end
210 end
195
211
196 def per_page_links(selected=nil)
212 def per_page_links(selected=nil)
197 url_param = params.dup
213 url_param = params.dup
198 url_param.clear if url_param.has_key?(:set_filter)
214 url_param.clear if url_param.has_key?(:set_filter)
199
215
200 links = Setting.per_page_options_array.collect do |n|
216 links = Setting.per_page_options_array.collect do |n|
201 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
217 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
202 {:href => url_for(url_param.merge(:per_page => n))})
218 {:href => url_for(url_param.merge(:per_page => n))})
203 end
219 end
204 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
220 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
205 end
221 end
206
222
207 def breadcrumb(*args)
223 def breadcrumb(*args)
208 elements = args.flatten
224 elements = args.flatten
209 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
225 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
210 end
226 end
211
227
212 def html_title(*args)
228 def html_title(*args)
213 if args.empty?
229 if args.empty?
214 title = []
230 title = []
215 title << @project.name if @project
231 title << @project.name if @project
216 title += @html_title if @html_title
232 title += @html_title if @html_title
217 title << Setting.app_title
233 title << Setting.app_title
218 title.compact.join(' - ')
234 title.compact.join(' - ')
219 else
235 else
220 @html_title ||= []
236 @html_title ||= []
221 @html_title += args
237 @html_title += args
222 end
238 end
223 end
239 end
224
240
225 def accesskey(s)
241 def accesskey(s)
226 Redmine::AccessKeys.key_for s
242 Redmine::AccessKeys.key_for s
227 end
243 end
228
244
229 # Formats text according to system settings.
245 # Formats text according to system settings.
230 # 2 ways to call this method:
246 # 2 ways to call this method:
231 # * with a String: textilizable(text, options)
247 # * with a String: textilizable(text, options)
232 # * with an object and one of its attribute: textilizable(issue, :description, options)
248 # * with an object and one of its attribute: textilizable(issue, :description, options)
233 def textilizable(*args)
249 def textilizable(*args)
234 options = args.last.is_a?(Hash) ? args.pop : {}
250 options = args.last.is_a?(Hash) ? args.pop : {}
235 case args.size
251 case args.size
236 when 1
252 when 1
237 obj = options[:object]
253 obj = options[:object]
238 text = args.shift
254 text = args.shift
239 when 2
255 when 2
240 obj = args.shift
256 obj = args.shift
241 text = obj.send(args.shift).to_s
257 text = obj.send(args.shift).to_s
242 else
258 else
243 raise ArgumentError, 'invalid arguments to textilizable'
259 raise ArgumentError, 'invalid arguments to textilizable'
244 end
260 end
245 return '' if text.blank?
261 return '' if text.blank?
246
262
247 only_path = options.delete(:only_path) == false ? false : true
263 only_path = options.delete(:only_path) == false ? false : true
248
264
249 # when using an image link, try to use an attachment, if possible
265 # when using an image link, try to use an attachment, if possible
250 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
266 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
251
267
252 if attachments
268 if attachments
253 attachments = attachments.sort_by(&:created_on).reverse
269 attachments = attachments.sort_by(&:created_on).reverse
254 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
270 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
255 style = $1
271 style = $1
256 filename = $6
272 filename = $6
257 rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
273 rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
258 # search for the picture in attachments
274 # search for the picture in attachments
259 if found = attachments.detect { |att| att.filename =~ rf }
275 if found = attachments.detect { |att| att.filename =~ rf }
260 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
276 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
261 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
277 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
262 alt = desc.blank? ? nil : "(#{desc})"
278 alt = desc.blank? ? nil : "(#{desc})"
263 "!#{style}#{image_url}#{alt}!"
279 "!#{style}#{image_url}#{alt}!"
264 else
280 else
265 "!#{style}#{filename}!"
281 "!#{style}#{filename}!"
266 end
282 end
267 end
283 end
268 end
284 end
269
285
270 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
286 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
271
287
272 # different methods for formatting wiki links
288 # different methods for formatting wiki links
273 case options[:wiki_links]
289 case options[:wiki_links]
274 when :local
290 when :local
275 # used for local links to html files
291 # used for local links to html files
276 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
292 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
277 when :anchor
293 when :anchor
278 # used for single-file wiki export
294 # used for single-file wiki export
279 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
295 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
280 else
296 else
281 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
297 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
282 end
298 end
283
299
284 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
300 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
285
301
286 # Wiki links
302 # Wiki links
287 #
303 #
288 # Examples:
304 # Examples:
289 # [[mypage]]
305 # [[mypage]]
290 # [[mypage|mytext]]
306 # [[mypage|mytext]]
291 # wiki links can refer other project wikis, using project name or identifier:
307 # wiki links can refer other project wikis, using project name or identifier:
292 # [[project:]] -> wiki starting page
308 # [[project:]] -> wiki starting page
293 # [[project:|mytext]]
309 # [[project:|mytext]]
294 # [[project:mypage]]
310 # [[project:mypage]]
295 # [[project:mypage|mytext]]
311 # [[project:mypage|mytext]]
296 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
312 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
297 link_project = project
313 link_project = project
298 esc, all, page, title = $1, $2, $3, $5
314 esc, all, page, title = $1, $2, $3, $5
299 if esc.nil?
315 if esc.nil?
300 if page =~ /^([^\:]+)\:(.*)$/
316 if page =~ /^([^\:]+)\:(.*)$/
301 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
317 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
302 page = $2
318 page = $2
303 title ||= $1 if page.blank?
319 title ||= $1 if page.blank?
304 end
320 end
305
321
306 if link_project && link_project.wiki
322 if link_project && link_project.wiki
307 # extract anchor
323 # extract anchor
308 anchor = nil
324 anchor = nil
309 if page =~ /^(.+?)\#(.+)$/
325 if page =~ /^(.+?)\#(.+)$/
310 page, anchor = $1, $2
326 page, anchor = $1, $2
311 end
327 end
312 # check if page exists
328 # check if page exists
313 wiki_page = link_project.wiki.find_page(page)
329 wiki_page = link_project.wiki.find_page(page)
314 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
330 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
315 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
331 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
316 else
332 else
317 # project or wiki doesn't exist
333 # project or wiki doesn't exist
318 title || page
334 title || page
319 end
335 end
320 else
336 else
321 all
337 all
322 end
338 end
323 end
339 end
324
340
325 # Redmine links
341 # Redmine links
326 #
342 #
327 # Examples:
343 # Examples:
328 # Issues:
344 # Issues:
329 # #52 -> Link to issue #52
345 # #52 -> Link to issue #52
330 # Changesets:
346 # Changesets:
331 # r52 -> Link to revision 52
347 # r52 -> Link to revision 52
332 # commit:a85130f -> Link to scmid starting with a85130f
348 # commit:a85130f -> Link to scmid starting with a85130f
333 # Documents:
349 # Documents:
334 # document#17 -> Link to document with id 17
350 # document#17 -> Link to document with id 17
335 # document:Greetings -> Link to the document with title "Greetings"
351 # document:Greetings -> Link to the document with title "Greetings"
336 # document:"Some document" -> Link to the document with title "Some document"
352 # document:"Some document" -> Link to the document with title "Some document"
337 # Versions:
353 # Versions:
338 # version#3 -> Link to version with id 3
354 # version#3 -> Link to version with id 3
339 # version:1.0.0 -> Link to version named "1.0.0"
355 # version:1.0.0 -> Link to version named "1.0.0"
340 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
356 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
341 # Attachments:
357 # Attachments:
342 # attachment:file.zip -> Link to the attachment of the current object named file.zip
358 # attachment:file.zip -> Link to the attachment of the current object named file.zip
343 # Source files:
359 # Source files:
344 # source:some/file -> Link to the file located at /some/file in the project's repository
360 # source:some/file -> Link to the file located at /some/file in the project's repository
345 # source:some/file@52 -> Link to the file's revision 52
361 # source:some/file@52 -> Link to the file's revision 52
346 # source:some/file#L120 -> Link to line 120 of the file
362 # source:some/file#L120 -> Link to line 120 of the file
347 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
363 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
348 # export:some/file -> Force the download of the file
364 # export:some/file -> Force the download of the file
349 # Forum messages:
365 # Forum messages:
350 # message#1218 -> Link to message with id 1218
366 # message#1218 -> Link to message with id 1218
351 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
367 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
352 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
368 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
353 link = nil
369 link = nil
354 if esc.nil?
370 if esc.nil?
355 if prefix.nil? && sep == 'r'
371 if prefix.nil? && sep == 'r'
356 if project && (changeset = project.changesets.find_by_revision(oid))
372 if project && (changeset = project.changesets.find_by_revision(oid))
357 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
373 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
358 :class => 'changeset',
374 :class => 'changeset',
359 :title => truncate_single_line(changeset.comments, 100))
375 :title => truncate_single_line(changeset.comments, 100))
360 end
376 end
361 elsif sep == '#'
377 elsif sep == '#'
362 oid = oid.to_i
378 oid = oid.to_i
363 case prefix
379 case prefix
364 when nil
380 when nil
365 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
381 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
366 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
382 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
367 :class => (issue.closed? ? 'issue closed' : 'issue'),
383 :class => (issue.closed? ? 'issue closed' : 'issue'),
368 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
384 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
369 link = content_tag('del', link) if issue.closed?
385 link = content_tag('del', link) if issue.closed?
370 end
386 end
371 when 'document'
387 when 'document'
372 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
388 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
373 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
389 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
374 :class => 'document'
390 :class => 'document'
375 end
391 end
376 when 'version'
392 when 'version'
377 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
393 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
378 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
394 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
379 :class => 'version'
395 :class => 'version'
380 end
396 end
381 when 'message'
397 when 'message'
382 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
398 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
383 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
399 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
384 :controller => 'messages',
400 :controller => 'messages',
385 :action => 'show',
401 :action => 'show',
386 :board_id => message.board,
402 :board_id => message.board,
387 :id => message.root,
403 :id => message.root,
388 :anchor => (message.parent ? "message-#{message.id}" : nil)},
404 :anchor => (message.parent ? "message-#{message.id}" : nil)},
389 :class => 'message'
405 :class => 'message'
390 end
406 end
391 end
407 end
392 elsif sep == ':'
408 elsif sep == ':'
393 # removes the double quotes if any
409 # removes the double quotes if any
394 name = oid.gsub(%r{^"(.*)"$}, "\\1")
410 name = oid.gsub(%r{^"(.*)"$}, "\\1")
395 case prefix
411 case prefix
396 when 'document'
412 when 'document'
397 if project && document = project.documents.find_by_title(name)
413 if project && document = project.documents.find_by_title(name)
398 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
414 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
399 :class => 'document'
415 :class => 'document'
400 end
416 end
401 when 'version'
417 when 'version'
402 if project && version = project.versions.find_by_name(name)
418 if project && version = project.versions.find_by_name(name)
403 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
419 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
404 :class => 'version'
420 :class => 'version'
405 end
421 end
406 when 'commit'
422 when 'commit'
407 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
423 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
408 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
424 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
409 :class => 'changeset',
425 :class => 'changeset',
410 :title => truncate_single_line(changeset.comments, 100)
426 :title => truncate_single_line(changeset.comments, 100)
411 end
427 end
412 when 'source', 'export'
428 when 'source', 'export'
413 if project && project.repository
429 if project && project.repository
414 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
430 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
415 path, rev, anchor = $1, $3, $5
431 path, rev, anchor = $1, $3, $5
416 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
432 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
417 :path => to_path_param(path),
433 :path => to_path_param(path),
418 :rev => rev,
434 :rev => rev,
419 :anchor => anchor,
435 :anchor => anchor,
420 :format => (prefix == 'export' ? 'raw' : nil)},
436 :format => (prefix == 'export' ? 'raw' : nil)},
421 :class => (prefix == 'export' ? 'source download' : 'source')
437 :class => (prefix == 'export' ? 'source download' : 'source')
422 end
438 end
423 when 'attachment'
439 when 'attachment'
424 if attachments && attachment = attachments.detect {|a| a.filename == name }
440 if attachments && attachment = attachments.detect {|a| a.filename == name }
425 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
441 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
426 :class => 'attachment'
442 :class => 'attachment'
427 end
443 end
428 end
444 end
429 end
445 end
430 end
446 end
431 leading + (link || "#{prefix}#{sep}#{oid}")
447 leading + (link || "#{prefix}#{sep}#{oid}")
432 end
448 end
433
449
434 text
450 text
435 end
451 end
436
452
437 # Same as Rails' simple_format helper without using paragraphs
453 # Same as Rails' simple_format helper without using paragraphs
438 def simple_format_without_paragraph(text)
454 def simple_format_without_paragraph(text)
439 text.to_s.
455 text.to_s.
440 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
456 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
441 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
457 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
442 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
458 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
443 end
459 end
444
460
445 def error_messages_for(object_name, options = {})
461 def error_messages_for(object_name, options = {})
446 options = options.symbolize_keys
462 options = options.symbolize_keys
447 object = instance_variable_get("@#{object_name}")
463 object = instance_variable_get("@#{object_name}")
448 if object && !object.errors.empty?
464 if object && !object.errors.empty?
449 # build full_messages here with controller current language
465 # build full_messages here with controller current language
450 full_messages = []
466 full_messages = []
451 object.errors.each do |attr, msg|
467 object.errors.each do |attr, msg|
452 next if msg.nil?
468 next if msg.nil?
453 msg = msg.first if msg.is_a? Array
469 msg = msg.first if msg.is_a? Array
454 if attr == "base"
470 if attr == "base"
455 full_messages << l(msg)
471 full_messages << l(msg)
456 else
472 else
457 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
473 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
458 end
474 end
459 end
475 end
460 # retrieve custom values error messages
476 # retrieve custom values error messages
461 if object.errors[:custom_values]
477 if object.errors[:custom_values]
462 object.custom_values.each do |v|
478 object.custom_values.each do |v|
463 v.errors.each do |attr, msg|
479 v.errors.each do |attr, msg|
464 next if msg.nil?
480 next if msg.nil?
465 msg = msg.first if msg.is_a? Array
481 msg = msg.first if msg.is_a? Array
466 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
482 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
467 end
483 end
468 end
484 end
469 end
485 end
470 content_tag("div",
486 content_tag("div",
471 content_tag(
487 content_tag(
472 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
488 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
473 ) +
489 ) +
474 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
490 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
475 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
491 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
476 )
492 )
477 else
493 else
478 ""
494 ""
479 end
495 end
480 end
496 end
481
497
482 def lang_options_for_select(blank=true)
498 def lang_options_for_select(blank=true)
483 (blank ? [["(auto)", ""]] : []) +
499 (blank ? [["(auto)", ""]] : []) +
484 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
500 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
485 end
501 end
486
502
487 def label_tag_for(name, option_tags = nil, options = {})
503 def label_tag_for(name, option_tags = nil, options = {})
488 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
504 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
489 content_tag("label", label_text)
505 content_tag("label", label_text)
490 end
506 end
491
507
492 def labelled_tabular_form_for(name, object, options, &proc)
508 def labelled_tabular_form_for(name, object, options, &proc)
493 options[:html] ||= {}
509 options[:html] ||= {}
494 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
510 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
495 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
511 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
496 end
512 end
497
513
498 def back_url_hidden_field_tag
514 def back_url_hidden_field_tag
499 back_url = params[:back_url] || request.env['HTTP_REFERER']
515 back_url = params[:back_url] || request.env['HTTP_REFERER']
500 hidden_field_tag('back_url', back_url) unless back_url.blank?
516 hidden_field_tag('back_url', back_url) unless back_url.blank?
501 end
517 end
502
518
503 def check_all_links(form_name)
519 def check_all_links(form_name)
504 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
520 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
505 " | " +
521 " | " +
506 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
522 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
507 end
523 end
508
524
509 def progress_bar(pcts, options={})
525 def progress_bar(pcts, options={})
510 pcts = [pcts, pcts] unless pcts.is_a?(Array)
526 pcts = [pcts, pcts] unless pcts.is_a?(Array)
511 pcts[1] = pcts[1] - pcts[0]
527 pcts[1] = pcts[1] - pcts[0]
512 pcts << (100 - pcts[1] - pcts[0])
528 pcts << (100 - pcts[1] - pcts[0])
513 width = options[:width] || '100px;'
529 width = options[:width] || '100px;'
514 legend = options[:legend] || ''
530 legend = options[:legend] || ''
515 content_tag('table',
531 content_tag('table',
516 content_tag('tr',
532 content_tag('tr',
517 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
533 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
518 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
534 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
519 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
535 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
520 ), :class => 'progress', :style => "width: #{width};") +
536 ), :class => 'progress', :style => "width: #{width};") +
521 content_tag('p', legend, :class => 'pourcent')
537 content_tag('p', legend, :class => 'pourcent')
522 end
538 end
523
539
524 def context_menu_link(name, url, options={})
540 def context_menu_link(name, url, options={})
525 options[:class] ||= ''
541 options[:class] ||= ''
526 if options.delete(:selected)
542 if options.delete(:selected)
527 options[:class] << ' icon-checked disabled'
543 options[:class] << ' icon-checked disabled'
528 options[:disabled] = true
544 options[:disabled] = true
529 end
545 end
530 if options.delete(:disabled)
546 if options.delete(:disabled)
531 options.delete(:method)
547 options.delete(:method)
532 options.delete(:confirm)
548 options.delete(:confirm)
533 options.delete(:onclick)
549 options.delete(:onclick)
534 options[:class] << ' disabled'
550 options[:class] << ' disabled'
535 url = '#'
551 url = '#'
536 end
552 end
537 link_to name, url, options
553 link_to name, url, options
538 end
554 end
539
555
540 def calendar_for(field_id)
556 def calendar_for(field_id)
541 include_calendar_headers_tags
557 include_calendar_headers_tags
542 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
558 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
543 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
559 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
544 end
560 end
545
561
546 def include_calendar_headers_tags
562 def include_calendar_headers_tags
547 unless @calendar_headers_tags_included
563 unless @calendar_headers_tags_included
548 @calendar_headers_tags_included = true
564 @calendar_headers_tags_included = true
549 content_for :header_tags do
565 content_for :header_tags do
550 javascript_include_tag('calendar/calendar') +
566 javascript_include_tag('calendar/calendar') +
551 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
567 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
552 javascript_include_tag('calendar/calendar-setup') +
568 javascript_include_tag('calendar/calendar-setup') +
553 stylesheet_link_tag('calendar')
569 stylesheet_link_tag('calendar')
554 end
570 end
555 end
571 end
556 end
572 end
557
573
558 def content_for(name, content = nil, &block)
574 def content_for(name, content = nil, &block)
559 @has_content ||= {}
575 @has_content ||= {}
560 @has_content[name] = true
576 @has_content[name] = true
561 super(name, content, &block)
577 super(name, content, &block)
562 end
578 end
563
579
564 def has_content?(name)
580 def has_content?(name)
565 (@has_content && @has_content[name]) || false
581 (@has_content && @has_content[name]) || false
566 end
582 end
567
583
568 # Returns the avatar image tag for the given +user+ if avatars are enabled
584 # Returns the avatar image tag for the given +user+ if avatars are enabled
569 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
585 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
570 def avatar(user, options = { })
586 def avatar(user, options = { })
571 if Setting.gravatar_enabled?
587 if Setting.gravatar_enabled?
572 email = nil
588 email = nil
573 if user.respond_to?(:mail)
589 if user.respond_to?(:mail)
574 email = user.mail
590 email = user.mail
575 elsif user.to_s =~ %r{<(.+?)>}
591 elsif user.to_s =~ %r{<(.+?)>}
576 email = $1
592 email = $1
577 end
593 end
578 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
594 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
579 end
595 end
580 end
596 end
581
597
582 private
598 private
583
599
584 def wiki_helper
600 def wiki_helper
585 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
601 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
586 extend helper
602 extend helper
587 return self
603 return self
588 end
604 end
589 end
605 end
@@ -1,72 +1,56
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 module WikiHelper
18 module WikiHelper
19
20 def render_page_hierarchy(pages, node=nil)
21 content = ''
22 if pages[node]
23 content << "<ul class=\"pages-hierarchy\">\n"
24 pages[node].each do |page|
25 content << "<li>"
26 content << link_to(h(page.pretty_title), {:action => 'index', :page => page.title},
27 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
28 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
29 content << "</li>\n"
30 end
31 content << "</ul>\n"
32 end
33 content
34 end
35
19
36 def html_diff(wdiff)
20 def html_diff(wdiff)
37 words = wdiff.words.collect{|word| h(word)}
21 words = wdiff.words.collect{|word| h(word)}
38 words_add = 0
22 words_add = 0
39 words_del = 0
23 words_del = 0
40 dels = 0
24 dels = 0
41 del_off = 0
25 del_off = 0
42 wdiff.diff.diffs.each do |diff|
26 wdiff.diff.diffs.each do |diff|
43 add_at = nil
27 add_at = nil
44 add_to = nil
28 add_to = nil
45 del_at = nil
29 del_at = nil
46 deleted = ""
30 deleted = ""
47 diff.each do |change|
31 diff.each do |change|
48 pos = change[1]
32 pos = change[1]
49 if change[0] == "+"
33 if change[0] == "+"
50 add_at = pos + dels unless add_at
34 add_at = pos + dels unless add_at
51 add_to = pos + dels
35 add_to = pos + dels
52 words_add += 1
36 words_add += 1
53 else
37 else
54 del_at = pos unless del_at
38 del_at = pos unless del_at
55 deleted << ' ' + change[2]
39 deleted << ' ' + change[2]
56 words_del += 1
40 words_del += 1
57 end
41 end
58 end
42 end
59 if add_at
43 if add_at
60 words[add_at] = '<span class="diff_in">' + words[add_at]
44 words[add_at] = '<span class="diff_in">' + words[add_at]
61 words[add_to] = words[add_to] + '</span>'
45 words[add_to] = words[add_to] + '</span>'
62 end
46 end
63 if del_at
47 if del_at
64 words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
48 words.insert del_at - del_off + dels + words_add, '<span class="diff_out">' + deleted + '</span>'
65 dels += 1
49 dels += 1
66 del_off += words_del
50 del_off += words_del
67 words_del = 0
51 words_del = 0
68 end
52 end
69 end
53 end
70 simple_format_without_paragraph(words.join(' '))
54 simple_format_without_paragraph(words.join(' '))
71 end
55 end
72 end
56 end
@@ -1,54 +1,73
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 class Wiki < ActiveRecord::Base
18 class Wiki < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
20 has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title'
21 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
21 has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all
22
22
23 validates_presence_of :start_page
23 validates_presence_of :start_page
24 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
24 validates_format_of :start_page, :with => /^[^,\.\/\?\;\|\:]*$/
25
25
26 # find the page with the given title
26 # find the page with the given title
27 # if page doesn't exist, return a new page
27 # if page doesn't exist, return a new page
28 def find_or_new_page(title)
28 def find_or_new_page(title)
29 title = start_page if title.blank?
29 title = start_page if title.blank?
30 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
30 find_page(title) || WikiPage.new(:wiki => self, :title => Wiki.titleize(title))
31 end
31 end
32
32
33 # find the page with the given title
33 # find the page with the given title
34 def find_page(title, options = {})
34 def find_page(title, options = {})
35 title = start_page if title.blank?
35 title = start_page if title.blank?
36 title = Wiki.titleize(title)
36 title = Wiki.titleize(title)
37 page = pages.find_by_title(title)
37 page = pages.find_by_title(title)
38 if !page && !(options[:with_redirect] == false)
38 if !page && !(options[:with_redirect] == false)
39 # search for a redirect
39 # search for a redirect
40 redirect = redirects.find_by_title(title)
40 redirect = redirects.find_by_title(title)
41 page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
41 page = find_page(redirect.redirects_to, :with_redirect => false) if redirect
42 end
42 end
43 page
43 page
44 end
44 end
45
45
46 # Finds a page by title
47 # The given string can be of one of the forms: "title" or "project:title"
48 # Examples:
49 # Wiki.find_page("bar", project => foo)
50 # Wiki.find_page("foo:bar")
51 def self.find_page(title, options = {})
52 project = options[:project]
53 if title.to_s =~ %r{^([^\:]+)\:(.*)$}
54 project_identifier, title = $1, $2
55 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
56 end
57 if project && project.wiki
58 page = project.wiki.find_page(title)
59 if page && page.content
60 page
61 end
62 end
63 end
64
46 # turn a string into a valid page title
65 # turn a string into a valid page title
47 def self.titleize(title)
66 def self.titleize(title)
48 # replace spaces with _ and remove unwanted caracters
67 # replace spaces with _ and remove unwanted caracters
49 title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
68 title = title.gsub(/\s+/, '_').delete(',./?;|:') if title
50 # upcase the first letter
69 # upcase the first letter
51 title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
70 title = (title.slice(0..0).upcase + (title.slice(1..-1) || '')) if title
52 title
71 title
53 end
72 end
54 end
73 end
@@ -1,107 +1,121
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 module Redmine
18 module Redmine
19 module WikiFormatting
19 module WikiFormatting
20 module Macros
20 module Macros
21 module Definitions
21 module Definitions
22 def exec_macro(name, obj, args)
22 def exec_macro(name, obj, args)
23 method_name = "macro_#{name}"
23 method_name = "macro_#{name}"
24 send(method_name, obj, args) if respond_to?(method_name)
24 send(method_name, obj, args) if respond_to?(method_name)
25 end
25 end
26
27 def extract_macro_options(args, *keys)
28 options = {}
29 while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym)
30 options[$1.downcase.to_sym] = $2
31 args.pop
32 end
33 return [args, options]
34 end
26 end
35 end
27
36
28 @@available_macros = {}
37 @@available_macros = {}
29
38
30 class << self
39 class << self
31 # Called with a block to define additional macros.
40 # Called with a block to define additional macros.
32 # Macro blocks accept 2 arguments:
41 # Macro blocks accept 2 arguments:
33 # * obj: the object that is rendered
42 # * obj: the object that is rendered
34 # * args: macro arguments
43 # * args: macro arguments
35 #
44 #
36 # Plugins can use this method to define new macros:
45 # Plugins can use this method to define new macros:
37 #
46 #
38 # Redmine::WikiFormatting::Macros.register do
47 # Redmine::WikiFormatting::Macros.register do
39 # desc "This is my macro"
48 # desc "This is my macro"
40 # macro :my_macro do |obj, args|
49 # macro :my_macro do |obj, args|
41 # "My macro output"
50 # "My macro output"
42 # end
51 # end
43 # end
52 # end
44 def register(&block)
53 def register(&block)
45 class_eval(&block) if block_given?
54 class_eval(&block) if block_given?
46 end
55 end
47
56
48 private
57 private
49 # Defines a new macro with the given name and block.
58 # Defines a new macro with the given name and block.
50 def macro(name, &block)
59 def macro(name, &block)
51 name = name.to_sym if name.is_a?(String)
60 name = name.to_sym if name.is_a?(String)
52 @@available_macros[name] = @@desc || ''
61 @@available_macros[name] = @@desc || ''
53 @@desc = nil
62 @@desc = nil
54 raise "Can not create a macro without a block!" unless block_given?
63 raise "Can not create a macro without a block!" unless block_given?
55 Definitions.send :define_method, "macro_#{name}".downcase, &block
64 Definitions.send :define_method, "macro_#{name}".downcase, &block
56 end
65 end
57
66
58 # Sets description for the next macro to be defined
67 # Sets description for the next macro to be defined
59 def desc(txt)
68 def desc(txt)
60 @@desc = txt
69 @@desc = txt
61 end
70 end
62 end
71 end
63
72
64 # Builtin macros
73 # Builtin macros
65 desc "Sample macro."
74 desc "Sample macro."
66 macro :hello_world do |obj, args|
75 macro :hello_world do |obj, args|
67 "Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}")
76 "Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}")
68 end
77 end
69
78
70 desc "Displays a list of all available macros, including description if available."
79 desc "Displays a list of all available macros, including description if available."
71 macro :macro_list do
80 macro :macro_list do
72 out = ''
81 out = ''
73 @@available_macros.keys.collect(&:to_s).sort.each do |macro|
82 @@available_macros.keys.collect(&:to_s).sort.each do |macro|
74 out << content_tag('dt', content_tag('code', macro))
83 out << content_tag('dt', content_tag('code', macro))
75 out << content_tag('dd', textilizable(@@available_macros[macro.to_sym]))
84 out << content_tag('dd', textilizable(@@available_macros[macro.to_sym]))
76 end
85 end
77 content_tag('dl', out)
86 content_tag('dl', out)
78 end
87 end
79
88
80 desc "Displays a list of child pages."
89 desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
90 " !{{child_pages}} -- can be used from a wiki page only\n" +
91 " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
92 " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
81 macro :child_pages do |obj, args|
93 macro :child_pages do |obj, args|
82 raise 'This macro applies to wiki pages only.' unless obj.is_a?(WikiContent)
94 args, options = extract_macro_options(args, :parent)
83 render_page_hierarchy(obj.page.descendants.group_by(&:parent_id), obj.page.id)
95 page = nil
96 if args.size > 0
97 page = Wiki.find_page(args.first.to_s, :project => @project)
98 elsif obj.is_a?(WikiContent)
99 page = obj.page
100 else
101 raise 'With no argument, this macro can be called from wiki pages only.'
102 end
103 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
104 pages = ([page] + page.descendants).group_by(&:parent_id)
105 render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
84 end
106 end
85
107
86 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
108 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
87 macro :include do |obj, args|
109 macro :include do |obj, args|
88 project = @project
110 page = Wiki.find_page(args.first.to_s, :project => @project)
89 title = args.first.to_s
111 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
90 if title =~ %r{^([^\:]+)\:(.*)$}
91 project_identifier, title = $1, $2
92 project = Project.find_by_identifier(project_identifier) || Project.find_by_name(project_identifier)
93 end
94 raise 'Unknow project' unless project && User.current.allowed_to?(:view_wiki_pages, project)
95 raise 'No wiki for this project' unless !project.wiki.nil?
96 page = project.wiki.find_page(title)
97 raise "Page #{args.first} doesn't exist" unless page && page.content
98 @included_wiki_pages ||= []
112 @included_wiki_pages ||= []
99 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
113 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
100 @included_wiki_pages << page.title
114 @included_wiki_pages << page.title
101 out = textilizable(page.content, :text, :attachments => page.attachments)
115 out = textilizable(page.content, :text, :attachments => page.attachments)
102 @included_wiki_pages.pop
116 @included_wiki_pages.pop
103 out
117 out
104 end
118 end
105 end
119 end
106 end
120 end
107 end
121 end
@@ -1,50 +1,72
1 ---
1 ---
2 wiki_contents_001:
2 wiki_contents_001:
3 text: |-
3 text: |-
4 h1. CookBook documentation
4 h1. CookBook documentation
5
5
6 {{child_pages}}
6 {{child_pages}}
7
7
8 Some updated [[documentation]] here with gzipped history
8 Some updated [[documentation]] here with gzipped history
9 updated_on: 2007-03-07 00:10:51 +01:00
9 updated_on: 2007-03-07 00:10:51 +01:00
10 page_id: 1
10 page_id: 1
11 id: 1
11 id: 1
12 version: 3
12 version: 3
13 author_id: 1
13 author_id: 1
14 comments: Gzip compression activated
14 comments: Gzip compression activated
15 wiki_contents_002:
15 wiki_contents_002:
16 text: |-
16 text: |-
17 h1. Another page
17 h1. Another page
18
18
19 This is a link to a ticket: #2
19 This is a link to a ticket: #2
20 And this is an included page:
20 And this is an included page:
21 {{include(Page with an inline image)}}
21 {{include(Page with an inline image)}}
22 updated_on: 2007-03-08 00:18:07 +01:00
22 updated_on: 2007-03-08 00:18:07 +01:00
23 page_id: 2
23 page_id: 2
24 id: 2
24 id: 2
25 version: 1
25 version: 1
26 author_id: 1
26 author_id: 1
27 comments:
27 comments:
28 wiki_contents_003:
28 wiki_contents_003:
29 text: |-
29 text: |-
30 h1. Start page
30 h1. Start page
31
31
32 E-commerce web site start page
32 E-commerce web site start page
33 updated_on: 2007-03-08 00:18:07 +01:00
33 updated_on: 2007-03-08 00:18:07 +01:00
34 page_id: 3
34 page_id: 3
35 id: 3
35 id: 3
36 version: 1
36 version: 1
37 author_id: 1
37 author_id: 1
38 comments:
38 comments:
39 wiki_contents_004:
39 wiki_contents_004:
40 text: |-
40 text: |-
41 h1. Page with an inline image
41 h1. Page with an inline image
42
42
43 This is an inline image:
43 This is an inline image:
44
44
45 !logo.gif!
45 !logo.gif!
46 updated_on: 2007-03-08 00:18:07 +01:00
46 updated_on: 2007-03-08 00:18:07 +01:00
47 page_id: 4
47 page_id: 4
48 id: 4
48 id: 4
49 version: 1
49 version: 1
50 author_id: 1
51 comments:
52 wiki_contents_005:
53 text: |-
54 h1. Child page 1
55
56 This is a child page
57 updated_on: 2007-03-08 00:18:07 +01:00
58 page_id: 5
59 id: 5
60 version: 1
61 author_id: 1
62 comments:
63 wiki_contents_006:
64 text: |-
65 h1. Child page 2
66
67 This is a child page
68 updated_on: 2007-03-08 00:18:07 +01:00
69 page_id: 6
70 id: 6
71 version: 1
50 author_id: 1 No newline at end of file
72 author_id: 1
@@ -1,30 +1,44
1 ---
1 ---
2 wiki_pages_001:
2 wiki_pages_001:
3 created_on: 2007-03-07 00:08:07 +01:00
3 created_on: 2007-03-07 00:08:07 +01:00
4 title: CookBook_documentation
4 title: CookBook_documentation
5 id: 1
5 id: 1
6 wiki_id: 1
6 wiki_id: 1
7 protected: true
7 protected: true
8 parent_id:
8 parent_id:
9 wiki_pages_002:
9 wiki_pages_002:
10 created_on: 2007-03-08 00:18:07 +01:00
10 created_on: 2007-03-08 00:18:07 +01:00
11 title: Another_page
11 title: Another_page
12 id: 2
12 id: 2
13 wiki_id: 1
13 wiki_id: 1
14 protected: false
14 protected: false
15 parent_id:
15 parent_id:
16 wiki_pages_003:
16 wiki_pages_003:
17 created_on: 2007-03-08 00:18:07 +01:00
17 created_on: 2007-03-08 00:18:07 +01:00
18 title: Start_page
18 title: Start_page
19 id: 3
19 id: 3
20 wiki_id: 2
20 wiki_id: 2
21 protected: false
21 protected: false
22 parent_id:
22 parent_id:
23 wiki_pages_004:
23 wiki_pages_004:
24 created_on: 2007-03-08 00:18:07 +01:00
24 created_on: 2007-03-08 00:18:07 +01:00
25 title: Page_with_an_inline_image
25 title: Page_with_an_inline_image
26 id: 4
26 id: 4
27 wiki_id: 1
27 wiki_id: 1
28 protected: false
28 protected: false
29 parent_id: 1
29 parent_id: 1
30 wiki_pages_005:
31 created_on: 2007-03-08 00:18:07 +01:00
32 title: Child_1
33 id: 5
34 wiki_id: 1
35 protected: false
36 parent_id: 2
37 wiki_pages_006:
38 created_on: 2007-03-08 00:18:07 +01:00
39 title: Child_2
40 id: 6
41 wiki_id: 1
42 protected: false
43 parent_id: 2
30 No newline at end of file
44
@@ -1,457 +1,431
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules, :users,
23 fixtures :projects, :roles, :enabled_modules, :users,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages,
27 :boards, :messages,
28 :attachments
28 :attachments
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 def test_auto_links
34 def test_auto_links
35 to_test = {
35 to_test = {
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
58 }
58 }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
60 end
60 end
61
61
62 def test_auto_mailto
62 def test_auto_mailto
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
64 textilizable('test@foo.bar')
64 textilizable('test@foo.bar')
65 end
65 end
66
66
67 def test_inline_images
67 def test_inline_images
68 to_test = {
68 to_test = {
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
72 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
72 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
73 }
73 }
74 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
74 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
75 end
75 end
76
76
77 def test_attached_images
77 def test_attached_images
78 to_test = {
78 to_test = {
79 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
79 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
80 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
80 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
81 }
81 }
82 attachments = Attachment.find(:all)
82 attachments = Attachment.find(:all)
83 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
83 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
84 end
84 end
85
85
86 def test_textile_external_links
86 def test_textile_external_links
87 to_test = {
87 to_test = {
88 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
88 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
89 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
89 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
90 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
90 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
91 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
91 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
92 # no multiline link text
92 # no multiline link text
93 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
93 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
94 }
94 }
95 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
95 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
96 end
96 end
97
97
98 def test_redmine_links
98 def test_redmine_links
99 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
99 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
100 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
100 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
101
101
102 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
102 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
103 :class => 'changeset', :title => 'My very first commit')
103 :class => 'changeset', :title => 'My very first commit')
104
104
105 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
105 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
106 :class => 'document')
106 :class => 'document')
107
107
108 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
108 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
109 :class => 'version')
109 :class => 'version')
110
110
111 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
111 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
112
112
113 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
113 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
114 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
114 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
115
115
116 to_test = {
116 to_test = {
117 # tickets
117 # tickets
118 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
118 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
119 # changesets
119 # changesets
120 'r1' => changeset_link,
120 'r1' => changeset_link,
121 # documents
121 # documents
122 'document#1' => document_link,
122 'document#1' => document_link,
123 'document:"Test document"' => document_link,
123 'document:"Test document"' => document_link,
124 # versions
124 # versions
125 'version#2' => version_link,
125 'version#2' => version_link,
126 'version:1.0' => version_link,
126 'version:1.0' => version_link,
127 'version:"1.0"' => version_link,
127 'version:"1.0"' => version_link,
128 # source
128 # source
129 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
129 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
130 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
130 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
131 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
131 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
132 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
132 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
133 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
133 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
134 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
134 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
135 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
135 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
136 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
136 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
137 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
137 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
138 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
138 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
139 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
139 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
140 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
140 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
141 # message
141 # message
142 'message#4' => link_to('Post 2', message_url, :class => 'message'),
142 'message#4' => link_to('Post 2', message_url, :class => 'message'),
143 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
143 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
144 # escaping
144 # escaping
145 '!#3.' => '#3.',
145 '!#3.' => '#3.',
146 '!r1' => 'r1',
146 '!r1' => 'r1',
147 '!document#1' => 'document#1',
147 '!document#1' => 'document#1',
148 '!document:"Test document"' => 'document:"Test document"',
148 '!document:"Test document"' => 'document:"Test document"',
149 '!version#2' => 'version#2',
149 '!version#2' => 'version#2',
150 '!version:1.0' => 'version:1.0',
150 '!version:1.0' => 'version:1.0',
151 '!version:"1.0"' => 'version:"1.0"',
151 '!version:"1.0"' => 'version:"1.0"',
152 '!source:/some/file' => 'source:/some/file',
152 '!source:/some/file' => 'source:/some/file',
153 # invalid expressions
153 # invalid expressions
154 'source:' => 'source:',
154 'source:' => 'source:',
155 # url hash
155 # url hash
156 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
156 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
157 }
157 }
158 @project = Project.find(1)
158 @project = Project.find(1)
159 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
159 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
160 end
160 end
161
161
162 def test_wiki_links
162 def test_wiki_links
163 to_test = {
163 to_test = {
164 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
164 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
165 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
165 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
166 # link with anchor
166 # link with anchor
167 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
167 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
168 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
168 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
169 # page that doesn't exist
169 # page that doesn't exist
170 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
170 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
171 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
171 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
172 # link to another project wiki
172 # link to another project wiki
173 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
173 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
174 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
174 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
175 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
175 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
176 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
176 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
177 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
177 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
178 # striked through link
178 # striked through link
179 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
179 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
180 '-[[Another page|Page]] link-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a> link</del>',
180 '-[[Another page|Page]] link-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a> link</del>',
181 # escaping
181 # escaping
182 '![[Another page|Page]]' => '[[Another page|Page]]',
182 '![[Another page|Page]]' => '[[Another page|Page]]',
183 }
183 }
184 @project = Project.find(1)
184 @project = Project.find(1)
185 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
185 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
186 end
186 end
187
187
188 def test_html_tags
188 def test_html_tags
189 to_test = {
189 to_test = {
190 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
190 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
191 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
191 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
192 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
192 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
193 # do not escape pre/code tags
193 # do not escape pre/code tags
194 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
194 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
195 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
195 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
196 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
196 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
197 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
197 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
198 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
198 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
199 # remove attributes except class
199 # remove attributes except class
200 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
200 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
201 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
201 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
202 }
202 }
203 to_test.each { |text, result| assert_equal result, textilizable(text) }
203 to_test.each { |text, result| assert_equal result, textilizable(text) }
204 end
204 end
205
205
206 def test_allowed_html_tags
206 def test_allowed_html_tags
207 to_test = {
207 to_test = {
208 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
208 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
209 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
209 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
210 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
210 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
211 }
211 }
212 to_test.each { |text, result| assert_equal result, textilizable(text) }
212 to_test.each { |text, result| assert_equal result, textilizable(text) }
213 end
213 end
214
214
215 def syntax_highlight
215 def syntax_highlight
216 raw = <<-RAW
216 raw = <<-RAW
217 <pre><code class="ruby">
217 <pre><code class="ruby">
218 # Some ruby code here
218 # Some ruby code here
219 </pre></code>
219 </pre></code>
220 RAW
220 RAW
221
221
222 expected = <<-EXPECTED
222 expected = <<-EXPECTED
223 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
223 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
224 </pre></code>
224 </pre></code>
225 EXPECTED
225 EXPECTED
226
226
227 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
227 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
228 end
228 end
229
229
230 def test_wiki_links_in_tables
230 def test_wiki_links_in_tables
231 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
231 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
232 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
232 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
233 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
233 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
234 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
234 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
235 }
235 }
236 @project = Project.find(1)
236 @project = Project.find(1)
237 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
237 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
238 end
238 end
239
239
240 def test_text_formatting
240 def test_text_formatting
241 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
241 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
242 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
242 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
243 }
243 }
244 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
244 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
245 end
245 end
246
246
247 def test_wiki_horizontal_rule
247 def test_wiki_horizontal_rule
248 assert_equal '<hr />', textilizable('---')
248 assert_equal '<hr />', textilizable('---')
249 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
249 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
250 end
250 end
251
251
252 def test_acronym
252 def test_acronym
253 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
253 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
254 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
254 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
255 end
255 end
256
256
257 def test_footnotes
257 def test_footnotes
258 raw = <<-RAW
258 raw = <<-RAW
259 This is some text[1].
259 This is some text[1].
260
260
261 fn1. This is the foot note
261 fn1. This is the foot note
262 RAW
262 RAW
263
263
264 expected = <<-EXPECTED
264 expected = <<-EXPECTED
265 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
265 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
266 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
266 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
267 EXPECTED
267 EXPECTED
268
268
269 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
269 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
270 end
270 end
271
271
272 def test_table_of_content
272 def test_table_of_content
273 raw = <<-RAW
273 raw = <<-RAW
274 {{toc}}
274 {{toc}}
275
275
276 h1. Title
276 h1. Title
277
277
278 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
278 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
279
279
280 h2. Subtitle
280 h2. Subtitle
281
281
282 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
282 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
283
283
284 h2. Subtitle with %{color:red}red text%
284 h2. Subtitle with %{color:red}red text%
285
285
286 h1. Another title
286 h1. Another title
287
287
288 RAW
288 RAW
289
289
290 expected = '<ul class="toc">' +
290 expected = '<ul class="toc">' +
291 '<li class="heading1"><a href="#Title">Title</a></li>' +
291 '<li class="heading1"><a href="#Title">Title</a></li>' +
292 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
292 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
293 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
293 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
294 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
294 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
295 '</ul>'
295 '</ul>'
296
296
297 assert textilizable(raw).gsub("\n", "").include?(expected)
297 assert textilizable(raw).gsub("\n", "").include?(expected)
298 end
298 end
299
299
300 def test_blockquote
300 def test_blockquote
301 # orig raw text
301 # orig raw text
302 raw = <<-RAW
302 raw = <<-RAW
303 John said:
303 John said:
304 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
304 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
305 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
305 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
306 > * Donec odio lorem,
306 > * Donec odio lorem,
307 > * sagittis ac,
307 > * sagittis ac,
308 > * malesuada in,
308 > * malesuada in,
309 > * adipiscing eu, dolor.
309 > * adipiscing eu, dolor.
310 >
310 >
311 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
311 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
312 > Proin a tellus. Nam vel neque.
312 > Proin a tellus. Nam vel neque.
313
313
314 He's right.
314 He's right.
315 RAW
315 RAW
316
316
317 # expected html
317 # expected html
318 expected = <<-EXPECTED
318 expected = <<-EXPECTED
319 <p>John said:</p>
319 <p>John said:</p>
320 <blockquote>
320 <blockquote>
321 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
321 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
322 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
322 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
323 <ul>
323 <ul>
324 <li>Donec odio lorem,</li>
324 <li>Donec odio lorem,</li>
325 <li>sagittis ac,</li>
325 <li>sagittis ac,</li>
326 <li>malesuada in,</li>
326 <li>malesuada in,</li>
327 <li>adipiscing eu, dolor.</li>
327 <li>adipiscing eu, dolor.</li>
328 </ul>
328 </ul>
329 <blockquote>
329 <blockquote>
330 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
330 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
331 </blockquote>
331 </blockquote>
332 <p>Proin a tellus. Nam vel neque.</p>
332 <p>Proin a tellus. Nam vel neque.</p>
333 </blockquote>
333 </blockquote>
334 <p>He's right.</p>
334 <p>He's right.</p>
335 EXPECTED
335 EXPECTED
336
336
337 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
337 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
338 end
338 end
339
339
340 def test_table
340 def test_table
341 raw = <<-RAW
341 raw = <<-RAW
342 This is a table with empty cells:
342 This is a table with empty cells:
343
343
344 |cell11|cell12||
344 |cell11|cell12||
345 |cell21||cell23|
345 |cell21||cell23|
346 |cell31|cell32|cell33|
346 |cell31|cell32|cell33|
347 RAW
347 RAW
348
348
349 expected = <<-EXPECTED
349 expected = <<-EXPECTED
350 <p>This is a table with empty cells:</p>
350 <p>This is a table with empty cells:</p>
351
351
352 <table>
352 <table>
353 <tr><td>cell11</td><td>cell12</td><td></td></tr>
353 <tr><td>cell11</td><td>cell12</td><td></td></tr>
354 <tr><td>cell21</td><td></td><td>cell23</td></tr>
354 <tr><td>cell21</td><td></td><td>cell23</td></tr>
355 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
355 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
356 </table>
356 </table>
357 EXPECTED
357 EXPECTED
358
358
359 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
359 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
360 end
360 end
361
361
362 def test_macro_hello_world
363 text = "{{hello_world}}"
364 assert textilizable(text).match(/Hello world!/)
365 # escaping
366 text = "!{{hello_world}}"
367 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
368 end
369
370 def test_macro_include
371 @project = Project.find(1)
372 # include a page of the current project wiki
373 text = "{{include(Another page)}}"
374 assert textilizable(text).match(/This is a link to a ticket/)
375
376 @project = nil
377 # include a page of a specific project wiki
378 text = "{{include(ecookbook:Another page)}}"
379 assert textilizable(text).match(/This is a link to a ticket/)
380
381 text = "{{include(ecookbook:)}}"
382 assert textilizable(text).match(/CookBook documentation/)
383
384 text = "{{include(unknowidentifier:somepage)}}"
385 assert textilizable(text).match(/Unknow project/)
386 end
387
388 def test_default_formatter
362 def test_default_formatter
389 Setting.text_formatting = 'unknown'
363 Setting.text_formatting = 'unknown'
390 text = 'a *link*: http://www.example.net/'
364 text = 'a *link*: http://www.example.net/'
391 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
365 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
392 Setting.text_formatting = 'textile'
366 Setting.text_formatting = 'textile'
393 end
367 end
394
368
395 def test_date_format_default
369 def test_date_format_default
396 today = Date.today
370 today = Date.today
397 Setting.date_format = ''
371 Setting.date_format = ''
398 assert_equal l_date(today), format_date(today)
372 assert_equal l_date(today), format_date(today)
399 end
373 end
400
374
401 def test_date_format
375 def test_date_format
402 today = Date.today
376 today = Date.today
403 Setting.date_format = '%d %m %Y'
377 Setting.date_format = '%d %m %Y'
404 assert_equal today.strftime('%d %m %Y'), format_date(today)
378 assert_equal today.strftime('%d %m %Y'), format_date(today)
405 end
379 end
406
380
407 def test_time_format_default
381 def test_time_format_default
408 now = Time.now
382 now = Time.now
409 Setting.date_format = ''
383 Setting.date_format = ''
410 Setting.time_format = ''
384 Setting.time_format = ''
411 assert_equal l_datetime(now), format_time(now)
385 assert_equal l_datetime(now), format_time(now)
412 assert_equal l_time(now), format_time(now, false)
386 assert_equal l_time(now), format_time(now, false)
413 end
387 end
414
388
415 def test_time_format
389 def test_time_format
416 now = Time.now
390 now = Time.now
417 Setting.date_format = '%d %m %Y'
391 Setting.date_format = '%d %m %Y'
418 Setting.time_format = '%H %M'
392 Setting.time_format = '%H %M'
419 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
393 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
420 assert_equal now.strftime('%H %M'), format_time(now, false)
394 assert_equal now.strftime('%H %M'), format_time(now, false)
421 end
395 end
422
396
423 def test_utc_time_format
397 def test_utc_time_format
424 now = Time.now.utc
398 now = Time.now.utc
425 Setting.date_format = '%d %m %Y'
399 Setting.date_format = '%d %m %Y'
426 Setting.time_format = '%H %M'
400 Setting.time_format = '%H %M'
427 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
401 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
428 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
402 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
429 end
403 end
430
404
431 def test_due_date_distance_in_words
405 def test_due_date_distance_in_words
432 to_test = { Date.today => 'Due in 0 days',
406 to_test = { Date.today => 'Due in 0 days',
433 Date.today + 1 => 'Due in 1 day',
407 Date.today + 1 => 'Due in 1 day',
434 Date.today + 100 => 'Due in 100 days',
408 Date.today + 100 => 'Due in 100 days',
435 Date.today + 20000 => 'Due in 20000 days',
409 Date.today + 20000 => 'Due in 20000 days',
436 Date.today - 1 => '1 day late',
410 Date.today - 1 => '1 day late',
437 Date.today - 100 => '100 days late',
411 Date.today - 100 => '100 days late',
438 Date.today - 20000 => '20000 days late',
412 Date.today - 20000 => '20000 days late',
439 }
413 }
440 to_test.each do |date, expected|
414 to_test.each do |date, expected|
441 assert_equal expected, due_date_distance_in_words(date)
415 assert_equal expected, due_date_distance_in_words(date)
442 end
416 end
443 end
417 end
444
418
445 def test_avatar
419 def test_avatar
446 # turn on avatars
420 # turn on avatars
447 Setting.gravatar_enabled = '1'
421 Setting.gravatar_enabled = '1'
448 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
422 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
449 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
423 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
450 assert_nil avatar('jsmith')
424 assert_nil avatar('jsmith')
451 assert_nil avatar(nil)
425 assert_nil avatar(nil)
452
426
453 # turn off avatars
427 # turn off avatars
454 Setting.gravatar_enabled = '0'
428 Setting.gravatar_enabled = '0'
455 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
429 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
456 end
430 end
457 end
431 end
General Comments 0
You need to be logged in to leave comments. Login now