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