##// END OF EJS Templates
Show line breaks in activity events summary....
Jean-Philippe Lang -
r2345:4c312f3d6b67
parent child
Show More
@@ -1,702 +1,702
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 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include GravatarHelper::PublicMethods
25 include GravatarHelper::PublicMethods
26
26
27 extend Forwardable
27 extend Forwardable
28 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29
29
30 def current_role
30 def current_role
31 @current_role ||= User.current.role_for_project(@project)
31 @current_role ||= User.current.role_for_project(@project)
32 end
32 end
33
33
34 # Return true if user is authorized for controller/action, otherwise false
34 # Return true if user is authorized for controller/action, otherwise false
35 def authorize_for(controller, action)
35 def authorize_for(controller, action)
36 User.current.allowed_to?({:controller => controller, :action => action}, @project)
36 User.current.allowed_to?({:controller => controller, :action => action}, @project)
37 end
37 end
38
38
39 # Display a link if user is authorized
39 # Display a link if user is authorized
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 end
42 end
43
43
44 # Display a link to remote if user is authorized
44 # Display a link to remote if user is authorized
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 url = options[:url] || {}
46 url = options[:url] || {}
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 end
48 end
49
49
50 # Display a link to user's account page
50 # Display a link to user's account page
51 def link_to_user(user, options={})
51 def link_to_user(user, options={})
52 (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
52 (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
53 end
53 end
54
54
55 def link_to_issue(issue, options={})
55 def link_to_issue(issue, options={})
56 options[:class] ||= ''
56 options[:class] ||= ''
57 options[:class] << ' issue'
57 options[:class] << ' issue'
58 options[:class] << ' closed' if issue.closed?
58 options[:class] << ' closed' if issue.closed?
59 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
59 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
60 end
60 end
61
61
62 # Generates a link to an attachment.
62 # Generates a link to an attachment.
63 # Options:
63 # Options:
64 # * :text - Link text (default to attachment filename)
64 # * :text - Link text (default to attachment filename)
65 # * :download - Force download (default: false)
65 # * :download - Force download (default: false)
66 def link_to_attachment(attachment, options={})
66 def link_to_attachment(attachment, options={})
67 text = options.delete(:text) || attachment.filename
67 text = options.delete(:text) || attachment.filename
68 action = options.delete(:download) ? 'download' : 'show'
68 action = options.delete(:download) ? 'download' : 'show'
69
69
70 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
70 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
71 end
71 end
72
72
73 def toggle_link(name, id, options={})
73 def toggle_link(name, id, options={})
74 onclick = "Element.toggle('#{id}'); "
74 onclick = "Element.toggle('#{id}'); "
75 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
75 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
76 onclick << "return false;"
76 onclick << "return false;"
77 link_to(name, "#", :onclick => onclick)
77 link_to(name, "#", :onclick => onclick)
78 end
78 end
79
79
80 def image_to_function(name, function, html_options = {})
80 def image_to_function(name, function, html_options = {})
81 html_options.symbolize_keys!
81 html_options.symbolize_keys!
82 tag(:input, html_options.merge({
82 tag(:input, html_options.merge({
83 :type => "image", :src => image_path(name),
83 :type => "image", :src => image_path(name),
84 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
84 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
85 }))
85 }))
86 end
86 end
87
87
88 def prompt_to_remote(name, text, param, url, html_options = {})
88 def prompt_to_remote(name, text, param, url, html_options = {})
89 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
89 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
90 link_to name, {}, html_options
90 link_to name, {}, html_options
91 end
91 end
92
92
93 def format_date(date)
93 def format_date(date)
94 return nil unless date
94 return nil unless date
95 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
95 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 date.strftime(@date_format)
97 date.strftime(@date_format)
98 end
98 end
99
99
100 def format_time(time, include_date = true)
100 def format_time(time, include_date = true)
101 return nil unless time
101 return nil unless time
102 time = time.to_time if time.is_a?(String)
102 time = time.to_time if time.is_a?(String)
103 zone = User.current.time_zone
103 zone = User.current.time_zone
104 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
104 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
105 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
105 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
106 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
106 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
107 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
107 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
108 end
108 end
109
109
110 def format_activity_title(text)
110 def format_activity_title(text)
111 h(truncate_single_line(text, 100))
111 h(truncate_single_line(text, 100))
112 end
112 end
113
113
114 def format_activity_day(date)
114 def format_activity_day(date)
115 date == Date.today ? l(:label_today).titleize : format_date(date)
115 date == Date.today ? l(:label_today).titleize : format_date(date)
116 end
116 end
117
117
118 def format_activity_description(text)
118 def format_activity_description(text)
119 h(truncate(text.to_s, 250).gsub(%r{<(pre|code)>.*$}m, '...'))
119 h(truncate(text.to_s, 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
120 end
120 end
121
121
122 def distance_of_date_in_words(from_date, to_date = 0)
122 def distance_of_date_in_words(from_date, to_date = 0)
123 from_date = from_date.to_date if from_date.respond_to?(:to_date)
123 from_date = from_date.to_date if from_date.respond_to?(:to_date)
124 to_date = to_date.to_date if to_date.respond_to?(:to_date)
124 to_date = to_date.to_date if to_date.respond_to?(:to_date)
125 distance_in_days = (to_date - from_date).abs
125 distance_in_days = (to_date - from_date).abs
126 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
126 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
127 end
127 end
128
128
129 def due_date_distance_in_words(date)
129 def due_date_distance_in_words(date)
130 if date
130 if date
131 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
131 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
132 end
132 end
133 end
133 end
134
134
135 def render_page_hierarchy(pages, node=nil)
135 def render_page_hierarchy(pages, node=nil)
136 content = ''
136 content = ''
137 if pages[node]
137 if pages[node]
138 content << "<ul class=\"pages-hierarchy\">\n"
138 content << "<ul class=\"pages-hierarchy\">\n"
139 pages[node].each do |page|
139 pages[node].each do |page|
140 content << "<li>"
140 content << "<li>"
141 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
141 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
142 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
142 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
143 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
143 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
144 content << "</li>\n"
144 content << "</li>\n"
145 end
145 end
146 content << "</ul>\n"
146 content << "</ul>\n"
147 end
147 end
148 content
148 content
149 end
149 end
150
150
151 # Renders flash messages
151 # Renders flash messages
152 def render_flash_messages
152 def render_flash_messages
153 s = ''
153 s = ''
154 flash.each do |k,v|
154 flash.each do |k,v|
155 s << content_tag('div', v, :class => "flash #{k}")
155 s << content_tag('div', v, :class => "flash #{k}")
156 end
156 end
157 s
157 s
158 end
158 end
159
159
160 # Renders the project quick-jump box
160 # Renders the project quick-jump box
161 def render_project_jump_box
161 def render_project_jump_box
162 # Retrieve them now to avoid a COUNT query
162 # Retrieve them now to avoid a COUNT query
163 projects = User.current.projects.all
163 projects = User.current.projects.all
164 if projects.any?
164 if projects.any?
165 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
165 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
166 "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
166 "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
167 '<option disabled="disabled">---</option>'
167 '<option disabled="disabled">---</option>'
168 s << project_tree_options_for_select(projects) do |p|
168 s << project_tree_options_for_select(projects) do |p|
169 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
169 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
170 end
170 end
171 s << '</select>'
171 s << '</select>'
172 s
172 s
173 end
173 end
174 end
174 end
175
175
176 def project_tree_options_for_select(projects, options = {})
176 def project_tree_options_for_select(projects, options = {})
177 s = ''
177 s = ''
178 project_tree(projects) do |project, level|
178 project_tree(projects) do |project, level|
179 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
179 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
180 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
180 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
181 tag_options.merge!(yield(project)) if block_given?
181 tag_options.merge!(yield(project)) if block_given?
182 s << content_tag('option', name_prefix + h(project), tag_options)
182 s << content_tag('option', name_prefix + h(project), tag_options)
183 end
183 end
184 s
184 s
185 end
185 end
186
186
187 # Yields the given block for each project with its level in the tree
187 # Yields the given block for each project with its level in the tree
188 def project_tree(projects, &block)
188 def project_tree(projects, &block)
189 ancestors = []
189 ancestors = []
190 projects.sort_by(&:lft).each do |project|
190 projects.sort_by(&:lft).each do |project|
191 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
191 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
192 ancestors.pop
192 ancestors.pop
193 end
193 end
194 yield project, ancestors.size
194 yield project, ancestors.size
195 ancestors << project
195 ancestors << project
196 end
196 end
197 end
197 end
198
198
199 def project_nested_ul(projects, &block)
199 def project_nested_ul(projects, &block)
200 s = ''
200 s = ''
201 if projects.any?
201 if projects.any?
202 ancestors = []
202 ancestors = []
203 projects.sort_by(&:lft).each do |project|
203 projects.sort_by(&:lft).each do |project|
204 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
204 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
205 s << "<ul>\n"
205 s << "<ul>\n"
206 else
206 else
207 ancestors.pop
207 ancestors.pop
208 s << "</li>"
208 s << "</li>"
209 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
209 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
210 ancestors.pop
210 ancestors.pop
211 s << "</ul></li>\n"
211 s << "</ul></li>\n"
212 end
212 end
213 end
213 end
214 s << "<li>"
214 s << "<li>"
215 s << yield(project).to_s
215 s << yield(project).to_s
216 ancestors << project
216 ancestors << project
217 end
217 end
218 s << ("</li></ul>\n" * ancestors.size)
218 s << ("</li></ul>\n" * ancestors.size)
219 end
219 end
220 s
220 s
221 end
221 end
222
222
223 # Truncates and returns the string as a single line
223 # Truncates and returns the string as a single line
224 def truncate_single_line(string, *args)
224 def truncate_single_line(string, *args)
225 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
225 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
226 end
226 end
227
227
228 def html_hours(text)
228 def html_hours(text)
229 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
229 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
230 end
230 end
231
231
232 def authoring(created, author, options={})
232 def authoring(created, author, options={})
233 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
233 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
234 link_to(distance_of_time_in_words(Time.now, created),
234 link_to(distance_of_time_in_words(Time.now, created),
235 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
235 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
236 :title => format_time(created))
236 :title => format_time(created))
237 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
237 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
238 l(options[:label] || :label_added_time_by, author_tag, time_tag)
238 l(options[:label] || :label_added_time_by, author_tag, time_tag)
239 end
239 end
240
240
241 def l_or_humanize(s, options={})
241 def l_or_humanize(s, options={})
242 k = "#{options[:prefix]}#{s}".to_sym
242 k = "#{options[:prefix]}#{s}".to_sym
243 l_has_string?(k) ? l(k) : s.to_s.humanize
243 l_has_string?(k) ? l(k) : s.to_s.humanize
244 end
244 end
245
245
246 def day_name(day)
246 def day_name(day)
247 l(:general_day_names).split(',')[day-1]
247 l(:general_day_names).split(',')[day-1]
248 end
248 end
249
249
250 def month_name(month)
250 def month_name(month)
251 l(:actionview_datehelper_select_month_names).split(',')[month-1]
251 l(:actionview_datehelper_select_month_names).split(',')[month-1]
252 end
252 end
253
253
254 def syntax_highlight(name, content)
254 def syntax_highlight(name, content)
255 type = CodeRay::FileType[name]
255 type = CodeRay::FileType[name]
256 type ? CodeRay.scan(content, type).html : h(content)
256 type ? CodeRay.scan(content, type).html : h(content)
257 end
257 end
258
258
259 def to_path_param(path)
259 def to_path_param(path)
260 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
260 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
261 end
261 end
262
262
263 def pagination_links_full(paginator, count=nil, options={})
263 def pagination_links_full(paginator, count=nil, options={})
264 page_param = options.delete(:page_param) || :page
264 page_param = options.delete(:page_param) || :page
265 url_param = params.dup
265 url_param = params.dup
266 # don't reuse params if filters are present
266 # don't reuse params if filters are present
267 url_param.clear if url_param.has_key?(:set_filter)
267 url_param.clear if url_param.has_key?(:set_filter)
268
268
269 html = ''
269 html = ''
270 if paginator.current.previous
270 if paginator.current.previous
271 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
271 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
272 end
272 end
273
273
274 html << (pagination_links_each(paginator, options) do |n|
274 html << (pagination_links_each(paginator, options) do |n|
275 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
275 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
276 end || '')
276 end || '')
277
277
278 if paginator.current.next
278 if paginator.current.next
279 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
279 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
280 end
280 end
281
281
282 unless count.nil?
282 unless count.nil?
283 html << [
283 html << [
284 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
284 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
285 per_page_links(paginator.items_per_page)
285 per_page_links(paginator.items_per_page)
286 ].compact.join(' | ')
286 ].compact.join(' | ')
287 end
287 end
288
288
289 html
289 html
290 end
290 end
291
291
292 def per_page_links(selected=nil)
292 def per_page_links(selected=nil)
293 url_param = params.dup
293 url_param = params.dup
294 url_param.clear if url_param.has_key?(:set_filter)
294 url_param.clear if url_param.has_key?(:set_filter)
295
295
296 links = Setting.per_page_options_array.collect do |n|
296 links = Setting.per_page_options_array.collect do |n|
297 n == selected ? n : link_to_remote(n, {:update => "content",
297 n == selected ? n : link_to_remote(n, {:update => "content",
298 :url => params.dup.merge(:per_page => n),
298 :url => params.dup.merge(:per_page => n),
299 :method => :get},
299 :method => :get},
300 {:href => url_for(url_param.merge(:per_page => n))})
300 {:href => url_for(url_param.merge(:per_page => n))})
301 end
301 end
302 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
302 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
303 end
303 end
304
304
305 def breadcrumb(*args)
305 def breadcrumb(*args)
306 elements = args.flatten
306 elements = args.flatten
307 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
307 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
308 end
308 end
309
309
310 def other_formats_links(&block)
310 def other_formats_links(&block)
311 concat('<p class="other-formats">' + l(:label_export_to), block.binding)
311 concat('<p class="other-formats">' + l(:label_export_to), block.binding)
312 yield Redmine::Views::OtherFormatsBuilder.new(self)
312 yield Redmine::Views::OtherFormatsBuilder.new(self)
313 concat('</p>', block.binding)
313 concat('</p>', block.binding)
314 end
314 end
315
315
316 def html_title(*args)
316 def html_title(*args)
317 if args.empty?
317 if args.empty?
318 title = []
318 title = []
319 title << @project.name if @project
319 title << @project.name if @project
320 title += @html_title if @html_title
320 title += @html_title if @html_title
321 title << Setting.app_title
321 title << Setting.app_title
322 title.compact.join(' - ')
322 title.compact.join(' - ')
323 else
323 else
324 @html_title ||= []
324 @html_title ||= []
325 @html_title += args
325 @html_title += args
326 end
326 end
327 end
327 end
328
328
329 def accesskey(s)
329 def accesskey(s)
330 Redmine::AccessKeys.key_for s
330 Redmine::AccessKeys.key_for s
331 end
331 end
332
332
333 # Formats text according to system settings.
333 # Formats text according to system settings.
334 # 2 ways to call this method:
334 # 2 ways to call this method:
335 # * with a String: textilizable(text, options)
335 # * with a String: textilizable(text, options)
336 # * with an object and one of its attribute: textilizable(issue, :description, options)
336 # * with an object and one of its attribute: textilizable(issue, :description, options)
337 def textilizable(*args)
337 def textilizable(*args)
338 options = args.last.is_a?(Hash) ? args.pop : {}
338 options = args.last.is_a?(Hash) ? args.pop : {}
339 case args.size
339 case args.size
340 when 1
340 when 1
341 obj = options[:object]
341 obj = options[:object]
342 text = args.shift
342 text = args.shift
343 when 2
343 when 2
344 obj = args.shift
344 obj = args.shift
345 text = obj.send(args.shift).to_s
345 text = obj.send(args.shift).to_s
346 else
346 else
347 raise ArgumentError, 'invalid arguments to textilizable'
347 raise ArgumentError, 'invalid arguments to textilizable'
348 end
348 end
349 return '' if text.blank?
349 return '' if text.blank?
350
350
351 only_path = options.delete(:only_path) == false ? false : true
351 only_path = options.delete(:only_path) == false ? false : true
352
352
353 # when using an image link, try to use an attachment, if possible
353 # when using an image link, try to use an attachment, if possible
354 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
354 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
355
355
356 if attachments
356 if attachments
357 attachments = attachments.sort_by(&:created_on).reverse
357 attachments = attachments.sort_by(&:created_on).reverse
358 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
358 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
359 style = $1
359 style = $1
360 filename = $6
360 filename = $6
361 rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
361 rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
362 # search for the picture in attachments
362 # search for the picture in attachments
363 if found = attachments.detect { |att| att.filename =~ rf }
363 if found = attachments.detect { |att| att.filename =~ rf }
364 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
364 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
365 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
365 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
366 alt = desc.blank? ? nil : "(#{desc})"
366 alt = desc.blank? ? nil : "(#{desc})"
367 "!#{style}#{image_url}#{alt}!"
367 "!#{style}#{image_url}#{alt}!"
368 else
368 else
369 "!#{style}#{filename}!"
369 "!#{style}#{filename}!"
370 end
370 end
371 end
371 end
372 end
372 end
373
373
374 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
374 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
375
375
376 # different methods for formatting wiki links
376 # different methods for formatting wiki links
377 case options[:wiki_links]
377 case options[:wiki_links]
378 when :local
378 when :local
379 # used for local links to html files
379 # used for local links to html files
380 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
380 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
381 when :anchor
381 when :anchor
382 # used for single-file wiki export
382 # used for single-file wiki export
383 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
383 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
384 else
384 else
385 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
385 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
386 end
386 end
387
387
388 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
388 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
389
389
390 # Wiki links
390 # Wiki links
391 #
391 #
392 # Examples:
392 # Examples:
393 # [[mypage]]
393 # [[mypage]]
394 # [[mypage|mytext]]
394 # [[mypage|mytext]]
395 # wiki links can refer other project wikis, using project name or identifier:
395 # wiki links can refer other project wikis, using project name or identifier:
396 # [[project:]] -> wiki starting page
396 # [[project:]] -> wiki starting page
397 # [[project:|mytext]]
397 # [[project:|mytext]]
398 # [[project:mypage]]
398 # [[project:mypage]]
399 # [[project:mypage|mytext]]
399 # [[project:mypage|mytext]]
400 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
400 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
401 link_project = project
401 link_project = project
402 esc, all, page, title = $1, $2, $3, $5
402 esc, all, page, title = $1, $2, $3, $5
403 if esc.nil?
403 if esc.nil?
404 if page =~ /^([^\:]+)\:(.*)$/
404 if page =~ /^([^\:]+)\:(.*)$/
405 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
405 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
406 page = $2
406 page = $2
407 title ||= $1 if page.blank?
407 title ||= $1 if page.blank?
408 end
408 end
409
409
410 if link_project && link_project.wiki
410 if link_project && link_project.wiki
411 # extract anchor
411 # extract anchor
412 anchor = nil
412 anchor = nil
413 if page =~ /^(.+?)\#(.+)$/
413 if page =~ /^(.+?)\#(.+)$/
414 page, anchor = $1, $2
414 page, anchor = $1, $2
415 end
415 end
416 # check if page exists
416 # check if page exists
417 wiki_page = link_project.wiki.find_page(page)
417 wiki_page = link_project.wiki.find_page(page)
418 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
418 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
419 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
419 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
420 else
420 else
421 # project or wiki doesn't exist
421 # project or wiki doesn't exist
422 title || page
422 title || page
423 end
423 end
424 else
424 else
425 all
425 all
426 end
426 end
427 end
427 end
428
428
429 # Redmine links
429 # Redmine links
430 #
430 #
431 # Examples:
431 # Examples:
432 # Issues:
432 # Issues:
433 # #52 -> Link to issue #52
433 # #52 -> Link to issue #52
434 # Changesets:
434 # Changesets:
435 # r52 -> Link to revision 52
435 # r52 -> Link to revision 52
436 # commit:a85130f -> Link to scmid starting with a85130f
436 # commit:a85130f -> Link to scmid starting with a85130f
437 # Documents:
437 # Documents:
438 # document#17 -> Link to document with id 17
438 # document#17 -> Link to document with id 17
439 # document:Greetings -> Link to the document with title "Greetings"
439 # document:Greetings -> Link to the document with title "Greetings"
440 # document:"Some document" -> Link to the document with title "Some document"
440 # document:"Some document" -> Link to the document with title "Some document"
441 # Versions:
441 # Versions:
442 # version#3 -> Link to version with id 3
442 # version#3 -> Link to version with id 3
443 # version:1.0.0 -> Link to version named "1.0.0"
443 # version:1.0.0 -> Link to version named "1.0.0"
444 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
444 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
445 # Attachments:
445 # Attachments:
446 # attachment:file.zip -> Link to the attachment of the current object named file.zip
446 # attachment:file.zip -> Link to the attachment of the current object named file.zip
447 # Source files:
447 # Source files:
448 # source:some/file -> Link to the file located at /some/file in the project's repository
448 # source:some/file -> Link to the file located at /some/file in the project's repository
449 # source:some/file@52 -> Link to the file's revision 52
449 # source:some/file@52 -> Link to the file's revision 52
450 # source:some/file#L120 -> Link to line 120 of the file
450 # source:some/file#L120 -> Link to line 120 of the file
451 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
451 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
452 # export:some/file -> Force the download of the file
452 # export:some/file -> Force the download of the file
453 # Forum messages:
453 # Forum messages:
454 # message#1218 -> Link to message with id 1218
454 # message#1218 -> Link to message with id 1218
455 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
455 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
456 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
456 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
457 link = nil
457 link = nil
458 if esc.nil?
458 if esc.nil?
459 if prefix.nil? && sep == 'r'
459 if prefix.nil? && sep == 'r'
460 if project && (changeset = project.changesets.find_by_revision(oid))
460 if project && (changeset = project.changesets.find_by_revision(oid))
461 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
461 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
462 :class => 'changeset',
462 :class => 'changeset',
463 :title => truncate_single_line(changeset.comments, 100))
463 :title => truncate_single_line(changeset.comments, 100))
464 end
464 end
465 elsif sep == '#'
465 elsif sep == '#'
466 oid = oid.to_i
466 oid = oid.to_i
467 case prefix
467 case prefix
468 when nil
468 when nil
469 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
469 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
470 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
470 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
471 :class => (issue.closed? ? 'issue closed' : 'issue'),
471 :class => (issue.closed? ? 'issue closed' : 'issue'),
472 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
472 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
473 link = content_tag('del', link) if issue.closed?
473 link = content_tag('del', link) if issue.closed?
474 end
474 end
475 when 'document'
475 when 'document'
476 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
476 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
477 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
477 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
478 :class => 'document'
478 :class => 'document'
479 end
479 end
480 when 'version'
480 when 'version'
481 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
481 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
482 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
482 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
483 :class => 'version'
483 :class => 'version'
484 end
484 end
485 when 'message'
485 when 'message'
486 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
486 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
487 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
487 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
488 :controller => 'messages',
488 :controller => 'messages',
489 :action => 'show',
489 :action => 'show',
490 :board_id => message.board,
490 :board_id => message.board,
491 :id => message.root,
491 :id => message.root,
492 :anchor => (message.parent ? "message-#{message.id}" : nil)},
492 :anchor => (message.parent ? "message-#{message.id}" : nil)},
493 :class => 'message'
493 :class => 'message'
494 end
494 end
495 end
495 end
496 elsif sep == ':'
496 elsif sep == ':'
497 # removes the double quotes if any
497 # removes the double quotes if any
498 name = oid.gsub(%r{^"(.*)"$}, "\\1")
498 name = oid.gsub(%r{^"(.*)"$}, "\\1")
499 case prefix
499 case prefix
500 when 'document'
500 when 'document'
501 if project && document = project.documents.find_by_title(name)
501 if project && document = project.documents.find_by_title(name)
502 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
502 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
503 :class => 'document'
503 :class => 'document'
504 end
504 end
505 when 'version'
505 when 'version'
506 if project && version = project.versions.find_by_name(name)
506 if project && version = project.versions.find_by_name(name)
507 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
507 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
508 :class => 'version'
508 :class => 'version'
509 end
509 end
510 when 'commit'
510 when 'commit'
511 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
511 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
512 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
512 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
513 :class => 'changeset',
513 :class => 'changeset',
514 :title => truncate_single_line(changeset.comments, 100)
514 :title => truncate_single_line(changeset.comments, 100)
515 end
515 end
516 when 'source', 'export'
516 when 'source', 'export'
517 if project && project.repository
517 if project && project.repository
518 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
518 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
519 path, rev, anchor = $1, $3, $5
519 path, rev, anchor = $1, $3, $5
520 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
520 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
521 :path => to_path_param(path),
521 :path => to_path_param(path),
522 :rev => rev,
522 :rev => rev,
523 :anchor => anchor,
523 :anchor => anchor,
524 :format => (prefix == 'export' ? 'raw' : nil)},
524 :format => (prefix == 'export' ? 'raw' : nil)},
525 :class => (prefix == 'export' ? 'source download' : 'source')
525 :class => (prefix == 'export' ? 'source download' : 'source')
526 end
526 end
527 when 'attachment'
527 when 'attachment'
528 if attachments && attachment = attachments.detect {|a| a.filename == name }
528 if attachments && attachment = attachments.detect {|a| a.filename == name }
529 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
529 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
530 :class => 'attachment'
530 :class => 'attachment'
531 end
531 end
532 end
532 end
533 end
533 end
534 end
534 end
535 leading + (link || "#{prefix}#{sep}#{oid}")
535 leading + (link || "#{prefix}#{sep}#{oid}")
536 end
536 end
537
537
538 text
538 text
539 end
539 end
540
540
541 # Same as Rails' simple_format helper without using paragraphs
541 # Same as Rails' simple_format helper without using paragraphs
542 def simple_format_without_paragraph(text)
542 def simple_format_without_paragraph(text)
543 text.to_s.
543 text.to_s.
544 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
544 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
545 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
545 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
546 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
546 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
547 end
547 end
548
548
549 def error_messages_for(object_name, options = {})
549 def error_messages_for(object_name, options = {})
550 options = options.symbolize_keys
550 options = options.symbolize_keys
551 object = instance_variable_get("@#{object_name}")
551 object = instance_variable_get("@#{object_name}")
552 if object && !object.errors.empty?
552 if object && !object.errors.empty?
553 # build full_messages here with controller current language
553 # build full_messages here with controller current language
554 full_messages = []
554 full_messages = []
555 object.errors.each do |attr, msg|
555 object.errors.each do |attr, msg|
556 next if msg.nil?
556 next if msg.nil?
557 msg = msg.first if msg.is_a? Array
557 msg = msg.first if msg.is_a? Array
558 if attr == "base"
558 if attr == "base"
559 full_messages << l(msg)
559 full_messages << l(msg)
560 else
560 else
561 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
561 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
562 end
562 end
563 end
563 end
564 # retrieve custom values error messages
564 # retrieve custom values error messages
565 if object.errors[:custom_values]
565 if object.errors[:custom_values]
566 object.custom_values.each do |v|
566 object.custom_values.each do |v|
567 v.errors.each do |attr, msg|
567 v.errors.each do |attr, msg|
568 next if msg.nil?
568 next if msg.nil?
569 msg = msg.first if msg.is_a? Array
569 msg = msg.first if msg.is_a? Array
570 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
570 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
571 end
571 end
572 end
572 end
573 end
573 end
574 content_tag("div",
574 content_tag("div",
575 content_tag(
575 content_tag(
576 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
576 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
577 ) +
577 ) +
578 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
578 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
579 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
579 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
580 )
580 )
581 else
581 else
582 ""
582 ""
583 end
583 end
584 end
584 end
585
585
586 def lang_options_for_select(blank=true)
586 def lang_options_for_select(blank=true)
587 (blank ? [["(auto)", ""]] : []) +
587 (blank ? [["(auto)", ""]] : []) +
588 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
588 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
589 end
589 end
590
590
591 def label_tag_for(name, option_tags = nil, options = {})
591 def label_tag_for(name, option_tags = nil, options = {})
592 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
592 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
593 content_tag("label", label_text)
593 content_tag("label", label_text)
594 end
594 end
595
595
596 def labelled_tabular_form_for(name, object, options, &proc)
596 def labelled_tabular_form_for(name, object, options, &proc)
597 options[:html] ||= {}
597 options[:html] ||= {}
598 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
598 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
599 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
599 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
600 end
600 end
601
601
602 def back_url_hidden_field_tag
602 def back_url_hidden_field_tag
603 back_url = params[:back_url] || request.env['HTTP_REFERER']
603 back_url = params[:back_url] || request.env['HTTP_REFERER']
604 back_url = CGI.unescape(back_url.to_s)
604 back_url = CGI.unescape(back_url.to_s)
605 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
605 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
606 end
606 end
607
607
608 def check_all_links(form_name)
608 def check_all_links(form_name)
609 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
609 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
610 " | " +
610 " | " +
611 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
611 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
612 end
612 end
613
613
614 def progress_bar(pcts, options={})
614 def progress_bar(pcts, options={})
615 pcts = [pcts, pcts] unless pcts.is_a?(Array)
615 pcts = [pcts, pcts] unless pcts.is_a?(Array)
616 pcts[1] = pcts[1] - pcts[0]
616 pcts[1] = pcts[1] - pcts[0]
617 pcts << (100 - pcts[1] - pcts[0])
617 pcts << (100 - pcts[1] - pcts[0])
618 width = options[:width] || '100px;'
618 width = options[:width] || '100px;'
619 legend = options[:legend] || ''
619 legend = options[:legend] || ''
620 content_tag('table',
620 content_tag('table',
621 content_tag('tr',
621 content_tag('tr',
622 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
622 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
623 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
623 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
624 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
624 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
625 ), :class => 'progress', :style => "width: #{width};") +
625 ), :class => 'progress', :style => "width: #{width};") +
626 content_tag('p', legend, :class => 'pourcent')
626 content_tag('p', legend, :class => 'pourcent')
627 end
627 end
628
628
629 def context_menu_link(name, url, options={})
629 def context_menu_link(name, url, options={})
630 options[:class] ||= ''
630 options[:class] ||= ''
631 if options.delete(:selected)
631 if options.delete(:selected)
632 options[:class] << ' icon-checked disabled'
632 options[:class] << ' icon-checked disabled'
633 options[:disabled] = true
633 options[:disabled] = true
634 end
634 end
635 if options.delete(:disabled)
635 if options.delete(:disabled)
636 options.delete(:method)
636 options.delete(:method)
637 options.delete(:confirm)
637 options.delete(:confirm)
638 options.delete(:onclick)
638 options.delete(:onclick)
639 options[:class] << ' disabled'
639 options[:class] << ' disabled'
640 url = '#'
640 url = '#'
641 end
641 end
642 link_to name, url, options
642 link_to name, url, options
643 end
643 end
644
644
645 def calendar_for(field_id)
645 def calendar_for(field_id)
646 include_calendar_headers_tags
646 include_calendar_headers_tags
647 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
647 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
648 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
648 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
649 end
649 end
650
650
651 def include_calendar_headers_tags
651 def include_calendar_headers_tags
652 unless @calendar_headers_tags_included
652 unless @calendar_headers_tags_included
653 @calendar_headers_tags_included = true
653 @calendar_headers_tags_included = true
654 content_for :header_tags do
654 content_for :header_tags do
655 javascript_include_tag('calendar/calendar') +
655 javascript_include_tag('calendar/calendar') +
656 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
656 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
657 javascript_include_tag('calendar/calendar-setup') +
657 javascript_include_tag('calendar/calendar-setup') +
658 stylesheet_link_tag('calendar')
658 stylesheet_link_tag('calendar')
659 end
659 end
660 end
660 end
661 end
661 end
662
662
663 def content_for(name, content = nil, &block)
663 def content_for(name, content = nil, &block)
664 @has_content ||= {}
664 @has_content ||= {}
665 @has_content[name] = true
665 @has_content[name] = true
666 super(name, content, &block)
666 super(name, content, &block)
667 end
667 end
668
668
669 def has_content?(name)
669 def has_content?(name)
670 (@has_content && @has_content[name]) || false
670 (@has_content && @has_content[name]) || false
671 end
671 end
672
672
673 # Returns the avatar image tag for the given +user+ if avatars are enabled
673 # Returns the avatar image tag for the given +user+ if avatars are enabled
674 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
674 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
675 def avatar(user, options = { })
675 def avatar(user, options = { })
676 if Setting.gravatar_enabled?
676 if Setting.gravatar_enabled?
677 email = nil
677 email = nil
678 if user.respond_to?(:mail)
678 if user.respond_to?(:mail)
679 email = user.mail
679 email = user.mail
680 elsif user.to_s =~ %r{<(.+?)>}
680 elsif user.to_s =~ %r{<(.+?)>}
681 email = $1
681 email = $1
682 end
682 end
683 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
683 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
684 end
684 end
685 end
685 end
686
686
687 private
687 private
688
688
689 def wiki_helper
689 def wiki_helper
690 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
690 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
691 extend helper
691 extend helper
692 return self
692 return self
693 end
693 end
694
694
695 def link_to_remote_content_update(text, url_params)
695 def link_to_remote_content_update(text, url_params)
696 link_to_remote(text,
696 link_to_remote(text,
697 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
697 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
698 {:href => url_for(:params => url_params)}
698 {:href => url_for(:params => url_params)}
699 )
699 )
700 end
700 end
701
701
702 end
702 end
General Comments 0
You need to be logged in to leave comments. Login now