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