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