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