##// END OF EJS Templates
Added css classes to the HTML body based on the theme, controller, and action. #819...
Eric Davis -
r3797:a544a1e57cd6
parent child
Show More
@@ -1,805 +1,818
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 # Truncates at line break after 250 characters or options[:length]
267 # Truncates at line break after 250 characters or options[:length]
268 def truncate_lines(string, options={})
268 def truncate_lines(string, options={})
269 length = options[:length] || 250
269 length = options[:length] || 250
270 if string.to_s =~ /\A(.{#{length}}.*?)$/m
270 if string.to_s =~ /\A(.{#{length}}.*?)$/m
271 "#{$1}..."
271 "#{$1}..."
272 else
272 else
273 string
273 string
274 end
274 end
275 end
275 end
276
276
277 def html_hours(text)
277 def html_hours(text)
278 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
278 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
279 end
279 end
280
280
281 def authoring(created, author, options={})
281 def authoring(created, author, options={})
282 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
282 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
283 end
283 end
284
284
285 def time_tag(time)
285 def time_tag(time)
286 text = distance_of_time_in_words(Time.now, time)
286 text = distance_of_time_in_words(Time.now, time)
287 if @project
287 if @project
288 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
288 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
289 else
289 else
290 content_tag('acronym', text, :title => format_time(time))
290 content_tag('acronym', text, :title => format_time(time))
291 end
291 end
292 end
292 end
293
293
294 def syntax_highlight(name, content)
294 def syntax_highlight(name, content)
295 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
295 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
296 end
296 end
297
297
298 def to_path_param(path)
298 def to_path_param(path)
299 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
299 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
300 end
300 end
301
301
302 def pagination_links_full(paginator, count=nil, options={})
302 def pagination_links_full(paginator, count=nil, options={})
303 page_param = options.delete(:page_param) || :page
303 page_param = options.delete(:page_param) || :page
304 per_page_links = options.delete(:per_page_links)
304 per_page_links = options.delete(:per_page_links)
305 url_param = params.dup
305 url_param = params.dup
306 # don't reuse query params if filters are present
306 # don't reuse query params if filters are present
307 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
307 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
308
308
309 html = ''
309 html = ''
310 if paginator.current.previous
310 if paginator.current.previous
311 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
311 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
312 end
312 end
313
313
314 html << (pagination_links_each(paginator, options) do |n|
314 html << (pagination_links_each(paginator, options) do |n|
315 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
315 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
316 end || '')
316 end || '')
317
317
318 if paginator.current.next
318 if paginator.current.next
319 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
319 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
320 end
320 end
321
321
322 unless count.nil?
322 unless count.nil?
323 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
323 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
324 if per_page_links != false && links = per_page_links(paginator.items_per_page)
324 if per_page_links != false && links = per_page_links(paginator.items_per_page)
325 html << " | #{links}"
325 html << " | #{links}"
326 end
326 end
327 end
327 end
328
328
329 html
329 html
330 end
330 end
331
331
332 def per_page_links(selected=nil)
332 def per_page_links(selected=nil)
333 url_param = params.dup
333 url_param = params.dup
334 url_param.clear if url_param.has_key?(:set_filter)
334 url_param.clear if url_param.has_key?(:set_filter)
335
335
336 links = Setting.per_page_options_array.collect do |n|
336 links = Setting.per_page_options_array.collect do |n|
337 n == selected ? n : link_to_remote(n, {:update => "content",
337 n == selected ? n : link_to_remote(n, {:update => "content",
338 :url => params.dup.merge(:per_page => n),
338 :url => params.dup.merge(:per_page => n),
339 :method => :get},
339 :method => :get},
340 {:href => url_for(url_param.merge(:per_page => n))})
340 {:href => url_for(url_param.merge(:per_page => n))})
341 end
341 end
342 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
342 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
343 end
343 end
344
344
345 def reorder_links(name, url)
345 def reorder_links(name, url)
346 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
346 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
347 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
347 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
348 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
348 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
349 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
349 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
350 end
350 end
351
351
352 def breadcrumb(*args)
352 def breadcrumb(*args)
353 elements = args.flatten
353 elements = args.flatten
354 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
354 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
355 end
355 end
356
356
357 def other_formats_links(&block)
357 def other_formats_links(&block)
358 concat('<p class="other-formats">' + l(:label_export_to))
358 concat('<p class="other-formats">' + l(:label_export_to))
359 yield Redmine::Views::OtherFormatsBuilder.new(self)
359 yield Redmine::Views::OtherFormatsBuilder.new(self)
360 concat('</p>')
360 concat('</p>')
361 end
361 end
362
362
363 def page_header_title
363 def page_header_title
364 if @project.nil? || @project.new_record?
364 if @project.nil? || @project.new_record?
365 h(Setting.app_title)
365 h(Setting.app_title)
366 else
366 else
367 b = []
367 b = []
368 ancestors = (@project.root? ? [] : @project.ancestors.visible)
368 ancestors = (@project.root? ? [] : @project.ancestors.visible)
369 if ancestors.any?
369 if ancestors.any?
370 root = ancestors.shift
370 root = ancestors.shift
371 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
371 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
372 if ancestors.size > 2
372 if ancestors.size > 2
373 b << '&#8230;'
373 b << '&#8230;'
374 ancestors = ancestors[-2, 2]
374 ancestors = ancestors[-2, 2]
375 end
375 end
376 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
376 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
377 end
377 end
378 b << h(@project)
378 b << h(@project)
379 b.join(' &#187; ')
379 b.join(' &#187; ')
380 end
380 end
381 end
381 end
382
382
383 def html_title(*args)
383 def html_title(*args)
384 if args.empty?
384 if args.empty?
385 title = []
385 title = []
386 title << @project.name if @project
386 title << @project.name if @project
387 title += @html_title if @html_title
387 title += @html_title if @html_title
388 title << Setting.app_title
388 title << Setting.app_title
389 title.select {|t| !t.blank? }.join(' - ')
389 title.select {|t| !t.blank? }.join(' - ')
390 else
390 else
391 @html_title ||= []
391 @html_title ||= []
392 @html_title += args
392 @html_title += args
393 end
393 end
394 end
394 end
395
395
396 # Returns the theme, controller name, and action as css classes for the
397 # HTML body.
398 def body_css_classes
399 css = []
400 if theme = Redmine::Themes.theme(Setting.ui_theme)
401 css << 'theme-' + theme.name
402 end
403
404 css << 'controller-' + params[:controller]
405 css << 'action-' + params[:action]
406 css.join(' ')
407 end
408
396 def accesskey(s)
409 def accesskey(s)
397 Redmine::AccessKeys.key_for s
410 Redmine::AccessKeys.key_for s
398 end
411 end
399
412
400 # Formats text according to system settings.
413 # Formats text according to system settings.
401 # 2 ways to call this method:
414 # 2 ways to call this method:
402 # * with a String: textilizable(text, options)
415 # * with a String: textilizable(text, options)
403 # * with an object and one of its attribute: textilizable(issue, :description, options)
416 # * with an object and one of its attribute: textilizable(issue, :description, options)
404 def textilizable(*args)
417 def textilizable(*args)
405 options = args.last.is_a?(Hash) ? args.pop : {}
418 options = args.last.is_a?(Hash) ? args.pop : {}
406 case args.size
419 case args.size
407 when 1
420 when 1
408 obj = options[:object]
421 obj = options[:object]
409 text = args.shift
422 text = args.shift
410 when 2
423 when 2
411 obj = args.shift
424 obj = args.shift
412 attr = args.shift
425 attr = args.shift
413 text = obj.send(attr).to_s
426 text = obj.send(attr).to_s
414 else
427 else
415 raise ArgumentError, 'invalid arguments to textilizable'
428 raise ArgumentError, 'invalid arguments to textilizable'
416 end
429 end
417 return '' if text.blank?
430 return '' if text.blank?
418 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
431 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
419 only_path = options.delete(:only_path) == false ? false : true
432 only_path = options.delete(:only_path) == false ? false : true
420
433
421 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
434 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
422
435
423 parse_non_pre_blocks(text) do |text|
436 parse_non_pre_blocks(text) do |text|
424 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
437 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
425 send method_name, text, project, obj, attr, only_path, options
438 send method_name, text, project, obj, attr, only_path, options
426 end
439 end
427 end
440 end
428 end
441 end
429
442
430 def parse_non_pre_blocks(text)
443 def parse_non_pre_blocks(text)
431 s = StringScanner.new(text)
444 s = StringScanner.new(text)
432 tags = []
445 tags = []
433 parsed = ''
446 parsed = ''
434 while !s.eos?
447 while !s.eos?
435 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
448 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
436 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
449 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
437 if tags.empty?
450 if tags.empty?
438 yield text
451 yield text
439 end
452 end
440 parsed << text
453 parsed << text
441 if tag
454 if tag
442 if closing
455 if closing
443 if tags.last == tag.downcase
456 if tags.last == tag.downcase
444 tags.pop
457 tags.pop
445 end
458 end
446 else
459 else
447 tags << tag.downcase
460 tags << tag.downcase
448 end
461 end
449 parsed << full_tag
462 parsed << full_tag
450 end
463 end
451 end
464 end
452 # Close any non closing tags
465 # Close any non closing tags
453 while tag = tags.pop
466 while tag = tags.pop
454 parsed << "</#{tag}>"
467 parsed << "</#{tag}>"
455 end
468 end
456 parsed
469 parsed
457 end
470 end
458
471
459 def parse_inline_attachments(text, project, obj, attr, only_path, options)
472 def parse_inline_attachments(text, project, obj, attr, only_path, options)
460 # when using an image link, try to use an attachment, if possible
473 # when using an image link, try to use an attachment, if possible
461 if options[:attachments] || (obj && obj.respond_to?(:attachments))
474 if options[:attachments] || (obj && obj.respond_to?(:attachments))
462 attachments = nil
475 attachments = nil
463 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
476 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
464 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
477 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
465 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
478 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
466 # search for the picture in attachments
479 # search for the picture in attachments
467 if found = attachments.detect { |att| att.filename.downcase == filename }
480 if found = attachments.detect { |att| att.filename.downcase == filename }
468 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
481 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
469 desc = found.description.to_s.gsub('"', '')
482 desc = found.description.to_s.gsub('"', '')
470 if !desc.blank? && alttext.blank?
483 if !desc.blank? && alttext.blank?
471 alt = " title=\"#{desc}\" alt=\"#{desc}\""
484 alt = " title=\"#{desc}\" alt=\"#{desc}\""
472 end
485 end
473 "src=\"#{image_url}\"#{alt}"
486 "src=\"#{image_url}\"#{alt}"
474 else
487 else
475 m
488 m
476 end
489 end
477 end
490 end
478 end
491 end
479 end
492 end
480
493
481 # Wiki links
494 # Wiki links
482 #
495 #
483 # Examples:
496 # Examples:
484 # [[mypage]]
497 # [[mypage]]
485 # [[mypage|mytext]]
498 # [[mypage|mytext]]
486 # wiki links can refer other project wikis, using project name or identifier:
499 # wiki links can refer other project wikis, using project name or identifier:
487 # [[project:]] -> wiki starting page
500 # [[project:]] -> wiki starting page
488 # [[project:|mytext]]
501 # [[project:|mytext]]
489 # [[project:mypage]]
502 # [[project:mypage]]
490 # [[project:mypage|mytext]]
503 # [[project:mypage|mytext]]
491 def parse_wiki_links(text, project, obj, attr, only_path, options)
504 def parse_wiki_links(text, project, obj, attr, only_path, options)
492 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
505 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
493 link_project = project
506 link_project = project
494 esc, all, page, title = $1, $2, $3, $5
507 esc, all, page, title = $1, $2, $3, $5
495 if esc.nil?
508 if esc.nil?
496 if page =~ /^([^\:]+)\:(.*)$/
509 if page =~ /^([^\:]+)\:(.*)$/
497 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
510 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
498 page = $2
511 page = $2
499 title ||= $1 if page.blank?
512 title ||= $1 if page.blank?
500 end
513 end
501
514
502 if link_project && link_project.wiki
515 if link_project && link_project.wiki
503 # extract anchor
516 # extract anchor
504 anchor = nil
517 anchor = nil
505 if page =~ /^(.+?)\#(.+)$/
518 if page =~ /^(.+?)\#(.+)$/
506 page, anchor = $1, $2
519 page, anchor = $1, $2
507 end
520 end
508 # check if page exists
521 # check if page exists
509 wiki_page = link_project.wiki.find_page(page)
522 wiki_page = link_project.wiki.find_page(page)
510 url = case options[:wiki_links]
523 url = case options[:wiki_links]
511 when :local; "#{title}.html"
524 when :local; "#{title}.html"
512 when :anchor; "##{title}" # used for single-file wiki export
525 when :anchor; "##{title}" # used for single-file wiki export
513 else
526 else
514 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
527 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
515 end
528 end
516 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
529 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
517 else
530 else
518 # project or wiki doesn't exist
531 # project or wiki doesn't exist
519 all
532 all
520 end
533 end
521 else
534 else
522 all
535 all
523 end
536 end
524 end
537 end
525 end
538 end
526
539
527 # Redmine links
540 # Redmine links
528 #
541 #
529 # Examples:
542 # Examples:
530 # Issues:
543 # Issues:
531 # #52 -> Link to issue #52
544 # #52 -> Link to issue #52
532 # Changesets:
545 # Changesets:
533 # r52 -> Link to revision 52
546 # r52 -> Link to revision 52
534 # commit:a85130f -> Link to scmid starting with a85130f
547 # commit:a85130f -> Link to scmid starting with a85130f
535 # Documents:
548 # Documents:
536 # document#17 -> Link to document with id 17
549 # document#17 -> Link to document with id 17
537 # document:Greetings -> Link to the document with title "Greetings"
550 # document:Greetings -> Link to the document with title "Greetings"
538 # document:"Some document" -> Link to the document with title "Some document"
551 # document:"Some document" -> Link to the document with title "Some document"
539 # Versions:
552 # Versions:
540 # version#3 -> Link to version with id 3
553 # version#3 -> Link to version with id 3
541 # version:1.0.0 -> Link to version named "1.0.0"
554 # version:1.0.0 -> Link to version named "1.0.0"
542 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
555 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
543 # Attachments:
556 # Attachments:
544 # attachment:file.zip -> Link to the attachment of the current object named file.zip
557 # attachment:file.zip -> Link to the attachment of the current object named file.zip
545 # Source files:
558 # Source files:
546 # source:some/file -> Link to the file located at /some/file in the project's repository
559 # source:some/file -> Link to the file located at /some/file in the project's repository
547 # source:some/file@52 -> Link to the file's revision 52
560 # source:some/file@52 -> Link to the file's revision 52
548 # source:some/file#L120 -> Link to line 120 of the file
561 # source:some/file#L120 -> Link to line 120 of the file
549 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
562 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
550 # export:some/file -> Force the download of the file
563 # export:some/file -> Force the download of the file
551 # Forum messages:
564 # Forum messages:
552 # message#1218 -> Link to message with id 1218
565 # message#1218 -> Link to message with id 1218
553 def parse_redmine_links(text, project, obj, attr, only_path, options)
566 def parse_redmine_links(text, project, obj, attr, only_path, options)
554 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
567 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
555 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
568 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
556 link = nil
569 link = nil
557 if esc.nil?
570 if esc.nil?
558 if prefix.nil? && sep == 'r'
571 if prefix.nil? && sep == 'r'
559 if project && (changeset = project.changesets.find_by_revision(identifier))
572 if project && (changeset = project.changesets.find_by_revision(identifier))
560 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
573 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
561 :class => 'changeset',
574 :class => 'changeset',
562 :title => truncate_single_line(changeset.comments, :length => 100))
575 :title => truncate_single_line(changeset.comments, :length => 100))
563 end
576 end
564 elsif sep == '#'
577 elsif sep == '#'
565 oid = identifier.to_i
578 oid = identifier.to_i
566 case prefix
579 case prefix
567 when nil
580 when nil
568 if issue = Issue.visible.find_by_id(oid, :include => :status)
581 if issue = Issue.visible.find_by_id(oid, :include => :status)
569 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
582 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
570 :class => issue.css_classes,
583 :class => issue.css_classes,
571 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
584 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
572 end
585 end
573 when 'document'
586 when 'document'
574 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
587 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
575 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
588 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
576 :class => 'document'
589 :class => 'document'
577 end
590 end
578 when 'version'
591 when 'version'
579 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
592 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
580 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
593 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
581 :class => 'version'
594 :class => 'version'
582 end
595 end
583 when 'message'
596 when 'message'
584 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
597 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
585 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
598 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
586 :controller => 'messages',
599 :controller => 'messages',
587 :action => 'show',
600 :action => 'show',
588 :board_id => message.board,
601 :board_id => message.board,
589 :id => message.root,
602 :id => message.root,
590 :anchor => (message.parent ? "message-#{message.id}" : nil)},
603 :anchor => (message.parent ? "message-#{message.id}" : nil)},
591 :class => 'message'
604 :class => 'message'
592 end
605 end
593 when 'project'
606 when 'project'
594 if p = Project.visible.find_by_id(oid)
607 if p = Project.visible.find_by_id(oid)
595 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
608 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
596 :class => 'project'
609 :class => 'project'
597 end
610 end
598 end
611 end
599 elsif sep == ':'
612 elsif sep == ':'
600 # removes the double quotes if any
613 # removes the double quotes if any
601 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
614 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
602 case prefix
615 case prefix
603 when 'document'
616 when 'document'
604 if project && document = project.documents.find_by_title(name)
617 if project && document = project.documents.find_by_title(name)
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
618 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
606 :class => 'document'
619 :class => 'document'
607 end
620 end
608 when 'version'
621 when 'version'
609 if project && version = project.versions.find_by_name(name)
622 if project && version = project.versions.find_by_name(name)
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
623 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
611 :class => 'version'
624 :class => 'version'
612 end
625 end
613 when 'commit'
626 when 'commit'
614 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
627 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
615 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
628 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
616 :class => 'changeset',
629 :class => 'changeset',
617 :title => truncate_single_line(changeset.comments, :length => 100)
630 :title => truncate_single_line(changeset.comments, :length => 100)
618 end
631 end
619 when 'source', 'export'
632 when 'source', 'export'
620 if project && project.repository
633 if project && project.repository
621 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
634 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
622 path, rev, anchor = $1, $3, $5
635 path, rev, anchor = $1, $3, $5
623 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
636 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
624 :path => to_path_param(path),
637 :path => to_path_param(path),
625 :rev => rev,
638 :rev => rev,
626 :anchor => anchor,
639 :anchor => anchor,
627 :format => (prefix == 'export' ? 'raw' : nil)},
640 :format => (prefix == 'export' ? 'raw' : nil)},
628 :class => (prefix == 'export' ? 'source download' : 'source')
641 :class => (prefix == 'export' ? 'source download' : 'source')
629 end
642 end
630 when 'attachment'
643 when 'attachment'
631 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
644 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
632 if attachments && attachment = attachments.detect {|a| a.filename == name }
645 if attachments && attachment = attachments.detect {|a| a.filename == name }
633 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
646 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
634 :class => 'attachment'
647 :class => 'attachment'
635 end
648 end
636 when 'project'
649 when 'project'
637 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
650 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
638 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
651 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
639 :class => 'project'
652 :class => 'project'
640 end
653 end
641 end
654 end
642 end
655 end
643 end
656 end
644 leading + (link || "#{prefix}#{sep}#{identifier}")
657 leading + (link || "#{prefix}#{sep}#{identifier}")
645 end
658 end
646 end
659 end
647
660
648 # Same as Rails' simple_format helper without using paragraphs
661 # Same as Rails' simple_format helper without using paragraphs
649 def simple_format_without_paragraph(text)
662 def simple_format_without_paragraph(text)
650 text.to_s.
663 text.to_s.
651 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
664 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
652 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
665 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
653 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
666 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
654 end
667 end
655
668
656 def lang_options_for_select(blank=true)
669 def lang_options_for_select(blank=true)
657 (blank ? [["(auto)", ""]] : []) +
670 (blank ? [["(auto)", ""]] : []) +
658 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
671 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
659 end
672 end
660
673
661 def label_tag_for(name, option_tags = nil, options = {})
674 def label_tag_for(name, option_tags = nil, options = {})
662 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
675 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
663 content_tag("label", label_text)
676 content_tag("label", label_text)
664 end
677 end
665
678
666 def labelled_tabular_form_for(name, object, options, &proc)
679 def labelled_tabular_form_for(name, object, options, &proc)
667 options[:html] ||= {}
680 options[:html] ||= {}
668 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
681 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
669 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
682 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
670 end
683 end
671
684
672 def back_url_hidden_field_tag
685 def back_url_hidden_field_tag
673 back_url = params[:back_url] || request.env['HTTP_REFERER']
686 back_url = params[:back_url] || request.env['HTTP_REFERER']
674 back_url = CGI.unescape(back_url.to_s)
687 back_url = CGI.unescape(back_url.to_s)
675 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
688 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
676 end
689 end
677
690
678 def check_all_links(form_name)
691 def check_all_links(form_name)
679 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
692 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
680 " | " +
693 " | " +
681 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
694 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
682 end
695 end
683
696
684 def progress_bar(pcts, options={})
697 def progress_bar(pcts, options={})
685 pcts = [pcts, pcts] unless pcts.is_a?(Array)
698 pcts = [pcts, pcts] unless pcts.is_a?(Array)
686 pcts = pcts.collect(&:round)
699 pcts = pcts.collect(&:round)
687 pcts[1] = pcts[1] - pcts[0]
700 pcts[1] = pcts[1] - pcts[0]
688 pcts << (100 - pcts[1] - pcts[0])
701 pcts << (100 - pcts[1] - pcts[0])
689 width = options[:width] || '100px;'
702 width = options[:width] || '100px;'
690 legend = options[:legend] || ''
703 legend = options[:legend] || ''
691 content_tag('table',
704 content_tag('table',
692 content_tag('tr',
705 content_tag('tr',
693 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
706 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
694 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
707 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
695 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
708 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
696 ), :class => 'progress', :style => "width: #{width};") +
709 ), :class => 'progress', :style => "width: #{width};") +
697 content_tag('p', legend, :class => 'pourcent')
710 content_tag('p', legend, :class => 'pourcent')
698 end
711 end
699
712
700 def checked_image(checked=true)
713 def checked_image(checked=true)
701 if checked
714 if checked
702 image_tag 'toggle_check.png'
715 image_tag 'toggle_check.png'
703 end
716 end
704 end
717 end
705
718
706 def context_menu(url)
719 def context_menu(url)
707 unless @context_menu_included
720 unless @context_menu_included
708 content_for :header_tags do
721 content_for :header_tags do
709 javascript_include_tag('context_menu') +
722 javascript_include_tag('context_menu') +
710 stylesheet_link_tag('context_menu')
723 stylesheet_link_tag('context_menu')
711 end
724 end
712 @context_menu_included = true
725 @context_menu_included = true
713 end
726 end
714 javascript_tag "new ContextMenu('#{ url_for(url) }')"
727 javascript_tag "new ContextMenu('#{ url_for(url) }')"
715 end
728 end
716
729
717 def context_menu_link(name, url, options={})
730 def context_menu_link(name, url, options={})
718 options[:class] ||= ''
731 options[:class] ||= ''
719 if options.delete(:selected)
732 if options.delete(:selected)
720 options[:class] << ' icon-checked disabled'
733 options[:class] << ' icon-checked disabled'
721 options[:disabled] = true
734 options[:disabled] = true
722 end
735 end
723 if options.delete(:disabled)
736 if options.delete(:disabled)
724 options.delete(:method)
737 options.delete(:method)
725 options.delete(:confirm)
738 options.delete(:confirm)
726 options.delete(:onclick)
739 options.delete(:onclick)
727 options[:class] << ' disabled'
740 options[:class] << ' disabled'
728 url = '#'
741 url = '#'
729 end
742 end
730 link_to name, url, options
743 link_to name, url, options
731 end
744 end
732
745
733 def calendar_for(field_id)
746 def calendar_for(field_id)
734 include_calendar_headers_tags
747 include_calendar_headers_tags
735 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
748 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
736 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
749 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
737 end
750 end
738
751
739 def include_calendar_headers_tags
752 def include_calendar_headers_tags
740 unless @calendar_headers_tags_included
753 unless @calendar_headers_tags_included
741 @calendar_headers_tags_included = true
754 @calendar_headers_tags_included = true
742 content_for :header_tags do
755 content_for :header_tags do
743 start_of_week = case Setting.start_of_week.to_i
756 start_of_week = case Setting.start_of_week.to_i
744 when 1
757 when 1
745 'Calendar._FD = 1;' # Monday
758 'Calendar._FD = 1;' # Monday
746 when 7
759 when 7
747 'Calendar._FD = 0;' # Sunday
760 'Calendar._FD = 0;' # Sunday
748 else
761 else
749 '' # use language
762 '' # use language
750 end
763 end
751
764
752 javascript_include_tag('calendar/calendar') +
765 javascript_include_tag('calendar/calendar') +
753 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
766 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
754 javascript_tag(start_of_week) +
767 javascript_tag(start_of_week) +
755 javascript_include_tag('calendar/calendar-setup') +
768 javascript_include_tag('calendar/calendar-setup') +
756 stylesheet_link_tag('calendar')
769 stylesheet_link_tag('calendar')
757 end
770 end
758 end
771 end
759 end
772 end
760
773
761 def content_for(name, content = nil, &block)
774 def content_for(name, content = nil, &block)
762 @has_content ||= {}
775 @has_content ||= {}
763 @has_content[name] = true
776 @has_content[name] = true
764 super(name, content, &block)
777 super(name, content, &block)
765 end
778 end
766
779
767 def has_content?(name)
780 def has_content?(name)
768 (@has_content && @has_content[name]) || false
781 (@has_content && @has_content[name]) || false
769 end
782 end
770
783
771 # Returns the avatar image tag for the given +user+ if avatars are enabled
784 # Returns the avatar image tag for the given +user+ if avatars are enabled
772 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
785 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
773 def avatar(user, options = { })
786 def avatar(user, options = { })
774 if Setting.gravatar_enabled?
787 if Setting.gravatar_enabled?
775 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
788 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
776 email = nil
789 email = nil
777 if user.respond_to?(:mail)
790 if user.respond_to?(:mail)
778 email = user.mail
791 email = user.mail
779 elsif user.to_s =~ %r{<(.+?)>}
792 elsif user.to_s =~ %r{<(.+?)>}
780 email = $1
793 email = $1
781 end
794 end
782 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
795 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
783 end
796 end
784 end
797 end
785
798
786 def favicon
799 def favicon
787 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
800 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
788 end
801 end
789
802
790 private
803 private
791
804
792 def wiki_helper
805 def wiki_helper
793 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
806 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
794 extend helper
807 extend helper
795 return self
808 return self
796 end
809 end
797
810
798 def link_to_remote_content_update(text, url_params)
811 def link_to_remote_content_update(text, url_params)
799 link_to_remote(text,
812 link_to_remote(text,
800 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
813 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
801 {:href => url_for(:params => url_params)}
814 {:href => url_for(:params => url_params)}
802 )
815 )
803 end
816 end
804
817
805 end
818 end
@@ -1,77 +1,77
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
3 <head>
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5 <title><%=h html_title %></title>
5 <title><%=h html_title %></title>
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
7 <meta name="keywords" content="issue,bug,tracker" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <%= favicon %>
8 <%= favicon %>
9 <%= stylesheet_link_tag 'application', :media => 'all' %>
9 <%= stylesheet_link_tag 'application', :media => 'all' %>
10 <%= javascript_include_tag :defaults %>
10 <%= javascript_include_tag :defaults %>
11 <%= heads_for_wiki_formatter %>
11 <%= heads_for_wiki_formatter %>
12 <!--[if IE]>
12 <!--[if IE]>
13 <style type="text/css">
13 <style type="text/css">
14 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
14 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
15 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
15 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
16 </style>
16 </style>
17 <![endif]-->
17 <![endif]-->
18 <%= call_hook :view_layouts_base_html_head %>
18 <%= call_hook :view_layouts_base_html_head %>
19 <!-- page specific tags -->
19 <!-- page specific tags -->
20 <%= yield :header_tags -%>
20 <%= yield :header_tags -%>
21 </head>
21 </head>
22 <body>
22 <body class="<%= body_css_classes %>">
23 <div id="wrapper">
23 <div id="wrapper">
24 <div id="wrapper2">
24 <div id="wrapper2">
25 <div id="top-menu">
25 <div id="top-menu">
26 <div id="account">
26 <div id="account">
27 <%= render_menu :account_menu -%>
27 <%= render_menu :account_menu -%>
28 </div>
28 </div>
29 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
29 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
30 <%= render_menu :top_menu -%>
30 <%= render_menu :top_menu -%>
31 </div>
31 </div>
32
32
33 <div id="header">
33 <div id="header">
34 <div id="quick-search">
34 <div id="quick-search">
35 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
35 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
36 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
36 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
37 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
37 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
38 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
38 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
39 <% end %>
39 <% end %>
40 <%= render_project_jump_box %>
40 <%= render_project_jump_box %>
41 </div>
41 </div>
42
42
43 <h1><%= page_header_title %></h1>
43 <h1><%= page_header_title %></h1>
44
44
45 <% if display_main_menu?(@project) %>
45 <% if display_main_menu?(@project) %>
46 <div id="main-menu">
46 <div id="main-menu">
47 <%= render_main_menu(@project) %>
47 <%= render_main_menu(@project) %>
48 </div>
48 </div>
49 <% end %>
49 <% end %>
50 </div>
50 </div>
51
51
52 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
52 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
53 <div id="sidebar">
53 <div id="sidebar">
54 <%= yield :sidebar %>
54 <%= yield :sidebar %>
55 <%= call_hook :view_layouts_base_sidebar %>
55 <%= call_hook :view_layouts_base_sidebar %>
56 </div>
56 </div>
57
57
58 <div id="content">
58 <div id="content">
59 <%= render_flash_messages %>
59 <%= render_flash_messages %>
60 <%= yield %>
60 <%= yield %>
61 <%= call_hook :view_layouts_base_content %>
61 <%= call_hook :view_layouts_base_content %>
62 <div style="clear:both;"></div>
62 <div style="clear:both;"></div>
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
66 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
67
67
68 <div id="footer">
68 <div id="footer">
69 <div class="bgl"><div class="bgr">
69 <div class="bgl"><div class="bgr">
70 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
70 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
71 </div></div>
71 </div></div>
72 </div>
72 </div>
73 </div>
73 </div>
74 </div>
74 </div>
75 <%= call_hook :view_layouts_base_body_bottom %>
75 <%= call_hook :view_layouts_base_body_bottom %>
76 </body>
76 </body>
77 </html>
77 </html>
General Comments 0
You need to be logged in to leave comments. Login now