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