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