Mercurial > hg > soundsoftware-site
comparison app/helpers/application_helper.rb @ 1115:433d4f72a19b redmine-2.2
Update to Redmine SVN revision 11137 on 2.2-stable branch
author | Chris Cannam |
---|---|
date | Mon, 07 Jan 2013 12:01:42 +0000 |
parents | 5f33065ddc4b |
children | bb32da3bea34 3e4c3460b6ca |
comparison
equal
deleted
inserted
replaced
929:5f33065ddc4b | 1115:433d4f72a19b |
---|---|
1 # encoding: utf-8 | 1 # encoding: utf-8 |
2 # | 2 # |
3 # Redmine - project management software | 3 # Redmine - project management software |
4 # Copyright (C) 2006-2011 Jean-Philippe Lang | 4 # Copyright (C) 2006-2012 Jean-Philippe Lang |
5 # | 5 # |
6 # This program is free software; you can redistribute it and/or | 6 # This program is free software; you can redistribute it and/or |
7 # modify it under the terms of the GNU General Public License | 7 # modify it under the terms of the GNU General Public License |
8 # as published by the Free Software Foundation; either version 2 | 8 # as published by the Free Software Foundation; either version 2 |
9 # of the License, or (at your option) any later version. | 9 # of the License, or (at your option) any later version. |
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to | 41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to |
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) | 42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) |
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) | 43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) |
44 end | 44 end |
45 | 45 |
46 # Display a link to remote if user is authorized | |
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil) | |
48 url = options[:url] || {} | |
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) | |
50 end | |
51 | |
52 # Displays a link to user's account page if active | 46 # Displays a link to user's account page if active |
53 def link_to_user(user, options={}) | 47 def link_to_user(user, options={}) |
54 if user.is_a?(User) | 48 if user.is_a?(User) |
55 name = h(user.name(options[:format])) | 49 name = h(user.name(options[:format])) |
56 if user.active? | 50 if user.active? || (User.current.admin? && user.logged?) |
57 link_to name, :controller => 'users', :action => 'show', :id => user | 51 link_to name, user_path(user), :class => user.css_classes |
58 else | 52 else |
59 name | 53 name |
60 end | 54 end |
61 else | 55 else |
62 h(user.to_s) | 56 h(user.to_s) |
68 # | 62 # |
69 # link_to_issue(issue) # => Defect #6: This is the subject | 63 # link_to_issue(issue) # => Defect #6: This is the subject |
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... | 64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... |
71 # link_to_issue(issue, :subject => false) # => Defect #6 | 65 # link_to_issue(issue, :subject => false) # => Defect #6 |
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6 | 66 # link_to_issue(issue, :project => true) # => Foo - Defect #6 |
67 # link_to_issue(issue, :subject => false, :tracker => false) # => #6 | |
73 # | 68 # |
74 def link_to_issue(issue, options={}) | 69 def link_to_issue(issue, options={}) |
75 title = nil | 70 title = nil |
76 subject = nil | 71 subject = nil |
72 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}" | |
77 if options[:subject] == false | 73 if options[:subject] == false |
78 title = truncate(issue.subject, :length => 60) | 74 title = truncate(issue.subject, :length => 60) |
79 else | 75 else |
80 subject = issue.subject | 76 subject = issue.subject |
81 if options[:truncate] | 77 if options[:truncate] |
82 subject = truncate(subject, :length => options[:truncate]) | 78 subject = truncate(subject, :length => options[:truncate]) |
83 end | 79 end |
84 end | 80 end |
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, | 81 s = link_to text, issue_path(issue), :class => issue.css_classes, :title => title |
86 :class => issue.css_classes, | 82 s << h(": #{subject}") if subject |
87 :title => title | 83 s = h("#{issue.project} - ") + s if options[:project] |
88 s << ": #{h subject}" if subject | |
89 s = "#{h issue.project} - " + s if options[:project] | |
90 s | 84 s |
91 end | 85 end |
92 | 86 |
93 # Generates a link to an attachment. | 87 # Generates a link to an attachment. |
94 # Options: | 88 # Options: |
95 # * :text - Link text (default to attachment filename) | 89 # * :text - Link text (default to attachment filename) |
96 # * :download - Force download (default: false) | 90 # * :download - Force download (default: false) |
97 def link_to_attachment(attachment, options={}) | 91 def link_to_attachment(attachment, options={}) |
98 text = options.delete(:text) || attachment.filename | 92 text = options.delete(:text) || attachment.filename |
99 action = options.delete(:download) ? 'download' : 'show' | 93 action = options.delete(:download) ? 'download' : 'show' |
94 opt_only_path = {} | |
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true) | |
96 options.delete(:only_path) | |
100 link_to(h(text), | 97 link_to(h(text), |
101 {:controller => 'attachments', :action => action, | 98 {:controller => 'attachments', :action => action, |
102 :id => attachment, :filename => attachment.filename }, | 99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path), |
103 options) | 100 options) |
104 end | 101 end |
105 | 102 |
106 # Generates a link to a SCM revision | 103 # Generates a link to a SCM revision |
107 # Options: | 104 # Options: |
108 # * :text - Link text (default to the formatted revision) | 105 # * :text - Link text (default to the formatted revision) |
109 def link_to_revision(revision, project, options={}) | 106 def link_to_revision(revision, repository, options={}) |
107 if repository.is_a?(Project) | |
108 repository = repository.repository | |
109 end | |
110 text = options.delete(:text) || format_revision(revision) | 110 text = options.delete(:text) || format_revision(revision) |
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision | 111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision |
112 | 112 link_to( |
113 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, | 113 h(text), |
114 :title => l(:label_revision_id, format_revision(revision))) | 114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, |
115 :title => l(:label_revision_id, format_revision(revision)) | |
116 ) | |
115 end | 117 end |
116 | 118 |
117 # Generates a link to a message | 119 # Generates a link to a message |
118 def link_to_message(message, options={}, html_options = nil) | 120 def link_to_message(message, options={}, html_options = nil) |
119 link_to( | 121 link_to( |
120 h(truncate(message.subject, :length => 60)), | 122 h(truncate(message.subject, :length => 60)), |
121 { :controller => 'messages', :action => 'show', | 123 { :controller => 'messages', :action => 'show', |
122 :board_id => message.board_id, | 124 :board_id => message.board_id, |
123 :id => message.root, | 125 :id => (message.parent_id || message.id), |
124 :r => (message.parent_id && message.id), | 126 :r => (message.parent_id && message.id), |
125 :anchor => (message.parent_id ? "message-#{message.id}" : nil) | 127 :anchor => (message.parent_id ? "message-#{message.id}" : nil) |
126 }.merge(options), | 128 }.merge(options), |
127 html_options | 129 html_options |
128 ) | 130 ) |
135 # link_to_project(project, :action=>'settings') # => link to project settings | 137 # link_to_project(project, :action=>'settings') # => link to project settings |
136 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options | 138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options |
137 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) | 139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) |
138 # | 140 # |
139 def link_to_project(project, options={}, html_options = nil) | 141 def link_to_project(project, options={}, html_options = nil) |
140 if project.active? | 142 if project.archived? |
143 h(project) | |
144 else | |
141 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) | 145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) |
142 link_to(h(project), url, html_options) | 146 link_to(h(project), url, html_options) |
143 else | 147 end |
144 h(project) | 148 end |
145 end | 149 |
150 def wiki_page_path(page, options={}) | |
151 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options)) | |
152 end | |
153 | |
154 def thumbnail_tag(attachment) | |
155 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), | |
156 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, | |
157 :title => attachment.filename | |
146 end | 158 end |
147 | 159 |
148 def toggle_link(name, id, options={}) | 160 def toggle_link(name, id, options={}) |
149 onclick = "Element.toggle('#{id}'); " | 161 onclick = "$('##{id}').toggle(); " |
150 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") | 162 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") |
151 onclick << "return false;" | 163 onclick << "return false;" |
152 link_to(name, "#", :onclick => onclick) | 164 link_to(name, "#", :onclick => onclick) |
153 end | 165 end |
154 | 166 |
155 def image_to_function(name, function, html_options = {}) | 167 def image_to_function(name, function, html_options = {}) |
158 :type => "image", :src => image_path(name), | 170 :type => "image", :src => image_path(name), |
159 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" | 171 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" |
160 })) | 172 })) |
161 end | 173 end |
162 | 174 |
163 def prompt_to_remote(name, text, param, url, html_options = {}) | |
164 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" | |
165 link_to name, {}, html_options | |
166 end | |
167 | |
168 def format_activity_title(text) | 175 def format_activity_title(text) |
169 h(truncate_single_line(text, :length => 100)) | 176 h(truncate_single_line(text, :length => 100)) |
170 end | 177 end |
171 | 178 |
172 def format_activity_day(date) | 179 def format_activity_day(date) |
173 date == Date.today ? l(:label_today).titleize : format_date(date) | 180 date == User.current.today ? l(:label_today).titleize : format_date(date) |
174 end | 181 end |
175 | 182 |
176 def format_activity_description(text) | 183 def format_activity_description(text) |
177 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />") | 184 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') |
185 ).gsub(/[\r\n]+/, "<br />").html_safe | |
178 end | 186 end |
179 | 187 |
180 def format_version_name(version) | 188 def format_version_name(version) |
181 if version.project == @project | 189 if version.project == @project |
182 h(version) | 190 h(version) |
189 if date | 197 if date |
190 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) | 198 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) |
191 end | 199 end |
192 end | 200 end |
193 | 201 |
194 def render_page_hierarchy(pages, node=nil, options={}) | 202 # Renders a tree of projects as a nested set of unordered lists |
195 content = '' | 203 # The given collection may be a subset of the whole project tree |
196 if pages[node] | 204 # (eg. some intermediate nodes are private and can not be seen) |
197 content << "<ul class=\"pages-hierarchy\">\n" | 205 def render_project_nested_lists(projects) |
198 pages[node].each do |page| | |
199 content << "<li>" | |
200 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, | |
201 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) | |
202 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | |
203 content << "</li>\n" | |
204 end | |
205 content << "</ul>\n" | |
206 end | |
207 content.html_safe | |
208 end | |
209 | |
210 # Renders flash messages | |
211 def render_flash_messages | |
212 s = '' | |
213 flash.each do |k,v| | |
214 s << content_tag('div', v, :class => "flash #{k}") | |
215 end | |
216 s.html_safe | |
217 end | |
218 | |
219 # Renders tabs and their content | |
220 def render_tabs(tabs) | |
221 if tabs.any? | |
222 render :partial => 'common/tabs', :locals => {:tabs => tabs} | |
223 else | |
224 content_tag 'p', l(:label_no_data), :class => "nodata" | |
225 end | |
226 end | |
227 | |
228 # Renders the project quick-jump box | |
229 def render_project_jump_box | |
230 return unless User.current.logged? | |
231 projects = User.current.memberships.collect(&:project).compact.uniq | |
232 if projects.any? | |
233 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' + | |
234 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | |
235 '<option value="" disabled="disabled">---</option>' | |
236 s << project_tree_options_for_select(projects, :selected => @project) do |p| | |
237 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) } | |
238 end | |
239 s << '</select>' | |
240 s.html_safe | |
241 end | |
242 end | |
243 | |
244 def project_tree_options_for_select(projects, options = {}) | |
245 s = '' | |
246 project_tree(projects) do |project, level| | |
247 name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') | |
248 tag_options = {:value => project.id} | |
249 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) | |
250 tag_options[:selected] = 'selected' | |
251 else | |
252 tag_options[:selected] = nil | |
253 end | |
254 tag_options.merge!(yield(project)) if block_given? | |
255 s << content_tag('option', name_prefix + h(project), tag_options) | |
256 end | |
257 s.html_safe | |
258 end | |
259 | |
260 # Yields the given block for each project with its level in the tree | |
261 # | |
262 # Wrapper for Project#project_tree | |
263 def project_tree(projects, &block) | |
264 Project.project_tree(projects, &block) | |
265 end | |
266 | |
267 def project_nested_ul(projects, &block) | |
268 s = '' | 206 s = '' |
269 if projects.any? | 207 if projects.any? |
270 ancestors = [] | 208 ancestors = [] |
209 original_project = @project | |
271 projects.sort_by(&:lft).each do |project| | 210 projects.sort_by(&:lft).each do |project| |
211 # set the project environment to please macros. | |
212 @project = project | |
272 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) | 213 if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) |
273 s << "<ul>\n" | 214 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n" |
274 else | 215 else |
275 ancestors.pop | 216 ancestors.pop |
276 s << "</li>" | 217 s << "</li>" |
277 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) | 218 while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) |
278 ancestors.pop | 219 ancestors.pop |
279 s << "</ul></li>\n" | 220 s << "</ul></li>\n" |
280 end | 221 end |
281 end | 222 end |
282 s << "<li>" | 223 classes = (ancestors.empty? ? 'root' : 'child') |
283 s << yield(project).to_s | 224 s << "<li class='#{classes}'><div class='#{classes}'>" |
225 s << h(block_given? ? yield(project) : project.name) | |
226 s << "</div>\n" | |
284 ancestors << project | 227 ancestors << project |
285 end | 228 end |
286 s << ("</li></ul>\n" * ancestors.size) | 229 s << ("</li></ul>\n" * ancestors.size) |
230 @project = original_project | |
287 end | 231 end |
288 s.html_safe | 232 s.html_safe |
233 end | |
234 | |
235 def render_page_hierarchy(pages, node=nil, options={}) | |
236 content = '' | |
237 if pages[node] | |
238 content << "<ul class=\"pages-hierarchy\">\n" | |
239 pages[node].each do |page| | |
240 content << "<li>" | |
241 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil}, | |
242 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) | |
243 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | |
244 content << "</li>\n" | |
245 end | |
246 content << "</ul>\n" | |
247 end | |
248 content.html_safe | |
249 end | |
250 | |
251 # Renders flash messages | |
252 def render_flash_messages | |
253 s = '' | |
254 flash.each do |k,v| | |
255 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") | |
256 end | |
257 s.html_safe | |
258 end | |
259 | |
260 # Renders tabs and their content | |
261 def render_tabs(tabs) | |
262 if tabs.any? | |
263 render :partial => 'common/tabs', :locals => {:tabs => tabs} | |
264 else | |
265 content_tag 'p', l(:label_no_data), :class => "nodata" | |
266 end | |
267 end | |
268 | |
269 # Renders the project quick-jump box | |
270 def render_project_jump_box | |
271 return unless User.current.logged? | |
272 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq | |
273 if projects.any? | |
274 options = | |
275 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | |
276 '<option value="" disabled="disabled">---</option>').html_safe | |
277 | |
278 options << project_tree_options_for_select(projects, :selected => @project) do |p| | |
279 { :value => project_path(:id => p, :jump => current_menu_item) } | |
280 end | |
281 | |
282 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') | |
283 end | |
284 end | |
285 | |
286 def project_tree_options_for_select(projects, options = {}) | |
287 s = '' | |
288 project_tree(projects) do |project, level| | |
289 name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe | |
290 tag_options = {:value => project.id} | |
291 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) | |
292 tag_options[:selected] = 'selected' | |
293 else | |
294 tag_options[:selected] = nil | |
295 end | |
296 tag_options.merge!(yield(project)) if block_given? | |
297 s << content_tag('option', name_prefix + h(project), tag_options) | |
298 end | |
299 s.html_safe | |
300 end | |
301 | |
302 # Yields the given block for each project with its level in the tree | |
303 # | |
304 # Wrapper for Project#project_tree | |
305 def project_tree(projects, &block) | |
306 Project.project_tree(projects, &block) | |
289 end | 307 end |
290 | 308 |
291 def principals_check_box_tags(name, principals) | 309 def principals_check_box_tags(name, principals) |
292 s = '' | 310 s = '' |
293 principals.sort.each do |principal| | 311 principals.sort.each do |principal| |
297 end | 315 end |
298 | 316 |
299 # Returns a string for users/groups option tags | 317 # Returns a string for users/groups option tags |
300 def principals_options_for_select(collection, selected=nil) | 318 def principals_options_for_select(collection, selected=nil) |
301 s = '' | 319 s = '' |
320 if collection.include?(User.current) | |
321 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) | |
322 end | |
302 groups = '' | 323 groups = '' |
303 collection.sort.each do |element| | 324 collection.sort.each do |element| |
304 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) | 325 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) |
305 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) | 326 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) |
306 end | 327 end |
307 unless groups.empty? | 328 unless groups.empty? |
308 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) | 329 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) |
309 end | 330 end |
310 s | 331 s.html_safe |
332 end | |
333 | |
334 # Options for the new membership projects combo-box | |
335 def options_for_membership_project_select(principal, projects) | |
336 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---") | |
337 options << project_tree_options_for_select(projects) do |p| | |
338 {:disabled => principal.projects.include?(p)} | |
339 end | |
340 options | |
311 end | 341 end |
312 | 342 |
313 # Truncates and returns the string as a single line | 343 # Truncates and returns the string as a single line |
314 def truncate_single_line(string, *args) | 344 def truncate_single_line(string, *args) |
315 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') | 345 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
323 else | 353 else |
324 string | 354 string |
325 end | 355 end |
326 end | 356 end |
327 | 357 |
358 def anchor(text) | |
359 text.to_s.gsub(' ', '_') | |
360 end | |
361 | |
328 def html_hours(text) | 362 def html_hours(text) |
329 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe | 363 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe |
330 end | 364 end |
331 | 365 |
332 def authoring(created, author, options={}) | 366 def authoring(created, author, options={}) |
334 end | 368 end |
335 | 369 |
336 def time_tag(time) | 370 def time_tag(time) |
337 text = distance_of_time_in_words(Time.now, time) | 371 text = distance_of_time_in_words(Time.now, time) |
338 if @project | 372 if @project |
339 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) | 373 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) |
340 else | 374 else |
341 content_tag('acronym', text, :title => format_time(time)) | 375 content_tag('acronym', text, :title => format_time(time)) |
342 end | 376 end |
343 end | 377 end |
344 | 378 |
379 def syntax_highlight_lines(name, content) | |
380 lines = [] | |
381 syntax_highlight(name, content).each_line { |line| lines << line } | |
382 lines | |
383 end | |
384 | |
345 def syntax_highlight(name, content) | 385 def syntax_highlight(name, content) |
346 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) | 386 Redmine::SyntaxHighlighting.highlight_by_filename(content, name) |
347 end | 387 end |
348 | 388 |
349 def to_path_param(path) | 389 def to_path_param(path) |
350 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?} | 390 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") |
391 str.blank? ? nil : str | |
351 end | 392 end |
352 | 393 |
353 def pagination_links_full(paginator, count=nil, options={}) | 394 def pagination_links_full(paginator, count=nil, options={}) |
354 page_param = options.delete(:page_param) || :page | 395 page_param = options.delete(:page_param) || :page |
355 per_page_links = options.delete(:per_page_links) | 396 per_page_links = options.delete(:per_page_links) |
374 url_param.merge(page_param => paginator.current.next)) | 415 url_param.merge(page_param => paginator.current.next)) |
375 end | 416 end |
376 | 417 |
377 unless count.nil? | 418 unless count.nil? |
378 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" | 419 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" |
379 if per_page_links != false && links = per_page_links(paginator.items_per_page) | 420 if per_page_links != false && links = per_page_links(paginator.items_per_page, count) |
380 html << " | #{links}" | 421 html << " | #{links}" |
381 end | 422 end |
382 end | 423 end |
383 | 424 |
384 html.html_safe | 425 html.html_safe |
385 end | 426 end |
386 | 427 |
387 def per_page_links(selected=nil) | 428 def per_page_links(selected=nil, item_count=nil) |
388 links = Setting.per_page_options_array.collect do |n| | 429 values = Setting.per_page_options_array |
430 if item_count && values.any? | |
431 if item_count > values.first | |
432 max = values.detect {|value| value >= item_count} || item_count | |
433 else | |
434 max = item_count | |
435 end | |
436 values = values.select {|value| value <= max || value == selected} | |
437 end | |
438 if values.empty? || (values.size == 1 && values.first == selected) | |
439 return nil | |
440 end | |
441 links = values.collect do |n| | |
389 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) | 442 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) |
390 end | 443 end |
391 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil | 444 l(:label_display_per_page, links.join(', ')) |
392 end | 445 end |
393 | 446 |
394 def reorder_links(name, url, method = :post) | 447 def reorder_links(name, url, method = :post) |
395 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), | 448 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), |
396 url.merge({"#{name}[move_to]" => 'highest'}), | 449 url.merge({"#{name}[move_to]" => 'highest'}), |
455 css = [] | 508 css = [] |
456 if theme = Redmine::Themes.theme(Setting.ui_theme) | 509 if theme = Redmine::Themes.theme(Setting.ui_theme) |
457 css << 'theme-' + theme.name | 510 css << 'theme-' + theme.name |
458 end | 511 end |
459 | 512 |
460 css << 'controller-' + params[:controller] | 513 css << 'controller-' + controller_name |
461 css << 'action-' + params[:action] | 514 css << 'action-' + action_name |
462 css.join(' ') | 515 css.join(' ') |
463 end | 516 end |
464 | 517 |
465 def accesskey(s) | 518 def accesskey(s) |
466 Redmine::AccessKeys.key_for s | 519 Redmine::AccessKeys.key_for s |
485 end | 538 end |
486 return '' if text.blank? | 539 return '' if text.blank? |
487 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) | 540 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
488 only_path = options.delete(:only_path) == false ? false : true | 541 only_path = options.delete(:only_path) == false ? false : true |
489 | 542 |
543 text = text.dup | |
544 macros = catch_macros(text) | |
490 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) | 545 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) |
491 | 546 |
492 @parsed_headings = [] | 547 @parsed_headings = [] |
493 @heading_anchors = {} | 548 @heading_anchors = {} |
494 @current_section = 0 if options[:edit_section_links] | 549 @current_section = 0 if options[:edit_section_links] |
495 | 550 |
496 parse_sections(text, project, obj, attr, only_path, options) | 551 parse_sections(text, project, obj, attr, only_path, options) |
497 text = parse_non_pre_blocks(text) do |text| | 552 text = parse_non_pre_blocks(text, obj, macros) do |text| |
498 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name| | 553 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| |
499 send method_name, text, project, obj, attr, only_path, options | 554 send method_name, text, project, obj, attr, only_path, options |
500 end | 555 end |
501 end | 556 end |
502 parse_headings(text, project, obj, attr, only_path, options) | 557 parse_headings(text, project, obj, attr, only_path, options) |
503 | 558 |
504 if @parsed_headings.any? | 559 if @parsed_headings.any? |
505 replace_toc(text, @parsed_headings) | 560 replace_toc(text, @parsed_headings) |
506 end | 561 end |
507 | 562 |
508 text | 563 text.html_safe |
509 end | 564 end |
510 | 565 |
511 def parse_non_pre_blocks(text) | 566 def parse_non_pre_blocks(text, obj, macros) |
512 s = StringScanner.new(text) | 567 s = StringScanner.new(text) |
513 tags = [] | 568 tags = [] |
514 parsed = '' | 569 parsed = '' |
515 while !s.eos? | 570 while !s.eos? |
516 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) | 571 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) |
517 text, full_tag, closing, tag = s[1], s[2], s[3], s[4] | 572 text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
518 if tags.empty? | 573 if tags.empty? |
519 yield text | 574 yield text |
575 inject_macros(text, obj, macros) if macros.any? | |
576 else | |
577 inject_macros(text, obj, macros, false) if macros.any? | |
520 end | 578 end |
521 parsed << text | 579 parsed << text |
522 if tag | 580 if tag |
523 if closing | 581 if closing |
524 if tags.last == tag.downcase | 582 if tags.last == tag.downcase |
532 end | 590 end |
533 # Close any non closing tags | 591 # Close any non closing tags |
534 while tag = tags.pop | 592 while tag = tags.pop |
535 parsed << "</#{tag}>" | 593 parsed << "</#{tag}>" |
536 end | 594 end |
537 parsed.html_safe | 595 parsed |
538 end | 596 end |
539 | 597 |
540 def parse_inline_attachments(text, project, obj, attr, only_path, options) | 598 def parse_inline_attachments(text, project, obj, attr, only_path, options) |
541 # when using an image link, try to use an attachment, if possible | 599 # when using an image link, try to use an attachment, if possible |
542 if options[:attachments] || (obj && obj.respond_to?(:attachments)) | 600 if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
549 :action => 'download', :id => found | 607 :action => 'download', :id => found |
550 desc = found.description.to_s.gsub('"', '') | 608 desc = found.description.to_s.gsub('"', '') |
551 if !desc.blank? && alttext.blank? | 609 if !desc.blank? && alttext.blank? |
552 alt = " title=\"#{desc}\" alt=\"#{desc}\"" | 610 alt = " title=\"#{desc}\" alt=\"#{desc}\"" |
553 end | 611 end |
554 "src=\"#{image_url}\"#{alt}".html_safe | 612 "src=\"#{image_url}\"#{alt}" |
555 else | 613 else |
556 m.html_safe | 614 m |
557 end | 615 end |
558 end | 616 end |
559 end | 617 end |
560 end | 618 end |
561 | 619 |
595 case options[:wiki_links] | 653 case options[:wiki_links] |
596 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') | 654 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') |
597 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export | 655 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export |
598 else | 656 else |
599 wiki_page_id = page.present? ? Wiki.titleize(page) : nil | 657 wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
600 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) | 658 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil |
659 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, | |
660 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent) | |
601 end | 661 end |
602 end | 662 end |
603 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) | 663 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) |
604 else | 664 else |
605 # project or wiki doesn't exist | 665 # project or wiki doesn't exist |
606 all.html_safe | 666 all |
607 end | 667 end |
608 else | 668 else |
609 all.html_safe | 669 all |
610 end | 670 end |
611 end | 671 end |
612 end | 672 end |
613 | 673 |
614 # Redmine links | 674 # Redmine links |
642 # identifier:r52 | 702 # identifier:r52 |
643 # identifier:document:"Some document" | 703 # identifier:document:"Some document" |
644 # identifier:version:1.0.0 | 704 # identifier:version:1.0.0 |
645 # identifier:source:some/file | 705 # identifier:source:some/file |
646 def parse_redmine_links(text, project, obj, attr, only_path, options) | 706 def parse_redmine_links(text, project, obj, attr, only_path, options) |
647 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| | 707 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| |
648 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 | 708 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 |
649 link = nil | 709 link = nil |
650 if project_identifier | 710 if project_identifier |
651 project = Project.visible.find_by_identifier(project_identifier) | 711 project = Project.visible.find_by_identifier(project_identifier) |
652 end | 712 end |
653 if esc.nil? | 713 if esc.nil? |
654 if prefix.nil? && sep == 'r' | 714 if prefix.nil? && sep == 'r' |
655 # project.changesets.visible raises an SQL error because of a double join on repositories | 715 if project |
656 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) | 716 repository = nil |
657 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, | 717 if repo_identifier |
658 :class => 'changeset', | 718 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
659 :title => truncate_single_line(changeset.comments, :length => 100)) | 719 else |
720 repository = project.repository | |
721 end | |
722 # project.changesets.visible raises an SQL error because of a double join on repositories | |
723 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) | |
724 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, | |
725 :class => 'changeset', | |
726 :title => truncate_single_line(changeset.comments, :length => 100)) | |
727 end | |
660 end | 728 end |
661 elsif sep == '#' | 729 elsif sep == '#' |
662 oid = identifier.to_i | 730 oid = identifier.to_i |
663 case prefix | 731 case prefix |
664 when nil | 732 when nil |
665 if issue = Issue.visible.find_by_id(oid, :include => :status) | 733 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) |
666 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, | 734 anchor = comment_id ? "note-#{comment_id}" : nil |
735 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, | |
667 :class => issue.css_classes, | 736 :class => issue.css_classes, |
668 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") | 737 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
669 end | 738 end |
670 when 'document' | 739 when 'document' |
671 if document = Document.visible.find_by_id(oid) | 740 if document = Document.visible.find_by_id(oid) |
718 when 'news' | 787 when 'news' |
719 if project && news = project.news.visible.find_by_title(name) | 788 if project && news = project.news.visible.find_by_title(name) |
720 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | 789 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, |
721 :class => 'news' | 790 :class => 'news' |
722 end | 791 end |
723 when 'commit' | 792 when 'commit', 'source', 'export' |
724 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) | 793 if project |
725 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, | 794 repository = nil |
726 :class => 'changeset', | 795 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$} |
727 :title => truncate_single_line(h(changeset.comments), :length => 100) | 796 repo_prefix, repo_identifier, name = $1, $2, $3 |
728 end | 797 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
729 when 'source', 'export' | 798 else |
730 if project && project.repository && User.current.allowed_to?(:browse_repository, project) | 799 repository = project.repository |
731 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | 800 end |
732 path, rev, anchor = $1, $3, $5 | 801 if prefix == 'commit' |
733 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, | 802 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) |
734 :path => to_path_param(path), | 803 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, |
735 :rev => rev, | 804 :class => 'changeset', |
736 :anchor => anchor, | 805 :title => truncate_single_line(h(changeset.comments), :length => 100) |
737 :format => (prefix == 'export' ? 'raw' : nil)}, | 806 end |
738 :class => (prefix == 'export' ? 'source download' : 'source') | 807 else |
808 if repository && User.current.allowed_to?(:browse_repository, project) | |
809 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | |
810 path, rev, anchor = $1, $3, $5 | |
811 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param, | |
812 :path => to_path_param(path), | |
813 :rev => rev, | |
814 :anchor => anchor}, | |
815 :class => (prefix == 'export' ? 'source download' : 'source') | |
816 end | |
817 end | |
818 repo_prefix = nil | |
739 end | 819 end |
740 when 'attachment' | 820 when 'attachment' |
741 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) | 821 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
742 if attachments && attachment = attachments.detect {|a| a.filename == name } | 822 if attachments && attachment = attachments.detect {|a| a.filename == name } |
743 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, | 823 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, |
748 link = link_to_project(p, {:only_path => only_path}, :class => 'project') | 828 link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
749 end | 829 end |
750 end | 830 end |
751 end | 831 end |
752 end | 832 end |
753 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe | 833 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) |
754 end | 834 end |
755 end | 835 end |
756 | 836 |
757 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE) | 837 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) |
758 | 838 |
759 def parse_sections(text, project, obj, attr, only_path, options) | 839 def parse_sections(text, project, obj, attr, only_path, options) |
760 return unless options[:edit_section_links] | 840 return unless options[:edit_section_links] |
761 text.gsub!(HEADING_RE) do | 841 text.gsub!(HEADING_RE) do |
842 heading = $1 | |
762 @current_section += 1 | 843 @current_section += 1 |
763 if @current_section > 1 | 844 if @current_section > 1 |
764 content_tag('div', | 845 content_tag('div', |
765 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), | 846 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), |
766 :class => 'contextual', | 847 :class => 'contextual', |
767 :title => l(:button_edit_section)) + $1 | 848 :title => l(:button_edit_section)) + heading.html_safe |
768 else | 849 else |
769 $1 | 850 heading |
770 end | 851 end |
771 end | 852 end |
772 end | 853 end |
773 | 854 |
774 # Headings and TOC | 855 # Headings and TOC |
790 @parsed_headings << [level, anchor, item] | 871 @parsed_headings << [level, anchor, item] |
791 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" | 872 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
792 end | 873 end |
793 end | 874 end |
794 | 875 |
795 MACROS_RE = / | 876 MACROS_RE = /( |
796 (!)? # escaping | 877 (!)? # escaping |
797 ( | 878 ( |
798 \{\{ # opening tag | 879 \{\{ # opening tag |
799 ([\w]+) # macro name | 880 ([\w]+) # macro name |
800 (\(([^\}]*)\))? # optional arguments | 881 (\(([^\n\r]*?)\))? # optional arguments |
882 ([\n\r].*?[\n\r])? # optional block of text | |
801 \}\} # closing tag | 883 \}\} # closing tag |
802 ) | 884 ) |
803 /x unless const_defined?(:MACROS_RE) | 885 )/mx unless const_defined?(:MACROS_RE) |
804 | 886 |
805 # Macros substitution | 887 MACRO_SUB_RE = /( |
806 def parse_macros(text, project, obj, attr, only_path, options) | 888 \{\{ |
889 macro\((\d+)\) | |
890 \}\} | |
891 )/x unless const_defined?(:MACRO_SUB_RE) | |
892 | |
893 # Extracts macros from text | |
894 def catch_macros(text) | |
895 macros = {} | |
807 text.gsub!(MACROS_RE) do | 896 text.gsub!(MACROS_RE) do |
808 esc, all, macro = $1, $2, $3.downcase | 897 all, macro = $1, $4.downcase |
809 args = ($5 || '').split(',').each(&:strip) | 898 if macro_exists?(macro) || all =~ MACRO_SUB_RE |
810 if esc.nil? | 899 index = macros.size |
811 begin | 900 macros[index] = all |
812 exec_macro(macro, obj, args) | 901 "{{macro(#{index})}}" |
813 rescue => e | |
814 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>" | |
815 end || all | |
816 else | 902 else |
817 all | 903 all |
904 end | |
905 end | |
906 macros | |
907 end | |
908 | |
909 # Executes and replaces macros in text | |
910 def inject_macros(text, obj, macros, execute=true) | |
911 text.gsub!(MACRO_SUB_RE) do | |
912 all, index = $1, $2.to_i | |
913 orig = macros.delete(index) | |
914 if execute && orig && orig =~ MACROS_RE | |
915 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) | |
916 if esc.nil? | |
917 h(exec_macro(macro, obj, args, block) || all) | |
918 else | |
919 h(all) | |
920 end | |
921 elsif orig | |
922 h(orig) | |
923 else | |
924 h(all) | |
818 end | 925 end |
819 end | 926 end |
820 end | 927 end |
821 | 928 |
822 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | 929 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
823 | 930 |
824 # Renders the TOC with given headings | 931 # Renders the TOC with given headings |
825 def replace_toc(text, headings) | 932 def replace_toc(text, headings) |
826 text.gsub!(TOC_RE) do | 933 text.gsub!(TOC_RE) do |
934 # Keep only the 4 first levels | |
935 headings = headings.select{|level, anchor, item| level <= 4} | |
827 if headings.empty? | 936 if headings.empty? |
828 '' | 937 '' |
829 else | 938 else |
830 div_class = 'toc' | 939 div_class = 'toc' |
831 div_class << ' right' if $1 == '>' | 940 div_class << ' right' if $1 == '>' |
860 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br | 969 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br |
861 html_safe | 970 html_safe |
862 end | 971 end |
863 | 972 |
864 def lang_options_for_select(blank=true) | 973 def lang_options_for_select(blank=true) |
865 (blank ? [["(auto)", ""]] : []) + | 974 (blank ? [["(auto)", ""]] : []) + languages_options |
866 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } | |
867 end | 975 end |
868 | 976 |
869 def label_tag_for(name, option_tags = nil, options = {}) | 977 def label_tag_for(name, option_tags = nil, options = {}) |
870 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") | 978 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
871 content_tag("label", label_text) | 979 content_tag("label", label_text) |
872 end | 980 end |
873 | 981 |
874 def labelled_tabular_form_for(*args, &proc) | |
875 args << {} unless args.last.is_a?(Hash) | |
876 options = args.last | |
877 options[:html] ||= {} | |
878 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) | |
879 options.merge!({:builder => TabularFormBuilder}) | |
880 form_for(*args, &proc) | |
881 end | |
882 | |
883 def labelled_form_for(*args, &proc) | 982 def labelled_form_for(*args, &proc) |
884 args << {} unless args.last.is_a?(Hash) | 983 args << {} unless args.last.is_a?(Hash) |
885 options = args.last | 984 options = args.last |
886 options.merge!({:builder => TabularFormBuilder}) | 985 if args.first.is_a?(Symbol) |
986 options.merge!(:as => args.shift) | |
987 end | |
988 options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
887 form_for(*args, &proc) | 989 form_for(*args, &proc) |
888 end | 990 end |
889 | 991 |
992 def labelled_fields_for(*args, &proc) | |
993 args << {} unless args.last.is_a?(Hash) | |
994 options = args.last | |
995 options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
996 fields_for(*args, &proc) | |
997 end | |
998 | |
999 def labelled_remote_form_for(*args, &proc) | |
1000 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." | |
1001 args << {} unless args.last.is_a?(Hash) | |
1002 options = args.last | |
1003 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) | |
1004 form_for(*args, &proc) | |
1005 end | |
1006 | |
1007 def error_messages_for(*objects) | |
1008 html = "" | |
1009 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact | |
1010 errors = objects.map {|o| o.errors.full_messages}.flatten | |
1011 if errors.any? | |
1012 html << "<div id='errorExplanation'><ul>\n" | |
1013 errors.each do |error| | |
1014 html << "<li>#{h error}</li>\n" | |
1015 end | |
1016 html << "</ul></div>\n" | |
1017 end | |
1018 html.html_safe | |
1019 end | |
1020 | |
1021 def delete_link(url, options={}) | |
1022 options = { | |
1023 :method => :delete, | |
1024 :data => {:confirm => l(:text_are_you_sure)}, | |
1025 :class => 'icon icon-del' | |
1026 }.merge(options) | |
1027 | |
1028 link_to l(:button_delete), url, options | |
1029 end | |
1030 | |
1031 def preview_link(url, form, target='preview', options={}) | |
1032 content_tag 'a', l(:label_preview), { | |
1033 :href => "#", | |
1034 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, | |
1035 :accesskey => accesskey(:preview) | |
1036 }.merge(options) | |
1037 end | |
1038 | |
1039 def link_to_function(name, function, html_options={}) | |
1040 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) | |
1041 end | |
1042 | |
1043 # Helper to render JSON in views | |
1044 def raw_json(arg) | |
1045 arg.to_json.to_s.gsub('/', '\/').html_safe | |
1046 end | |
1047 | |
1048 def back_url | |
1049 url = params[:back_url] | |
1050 if url.nil? && referer = request.env['HTTP_REFERER'] | |
1051 url = CGI.unescape(referer.to_s) | |
1052 end | |
1053 url | |
1054 end | |
1055 | |
890 def back_url_hidden_field_tag | 1056 def back_url_hidden_field_tag |
891 back_url = params[:back_url] || request.env['HTTP_REFERER'] | 1057 url = back_url |
892 back_url = CGI.unescape(back_url.to_s) | 1058 hidden_field_tag('back_url', url, :id => nil) unless url.blank? |
893 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? | |
894 end | 1059 end |
895 | 1060 |
896 def check_all_links(form_name) | 1061 def check_all_links(form_name) |
897 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + | 1062 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
898 " | ".html_safe + | 1063 " | ".html_safe + |
932 stylesheet_link_tag('context_menu_rtl') | 1097 stylesheet_link_tag('context_menu_rtl') |
933 end | 1098 end |
934 end | 1099 end |
935 @context_menu_included = true | 1100 @context_menu_included = true |
936 end | 1101 end |
937 javascript_tag "new ContextMenu('#{ url_for(url) }')" | 1102 javascript_tag "contextMenuInit('#{ url_for(url) }')" |
938 end | |
939 | |
940 def context_menu_link(name, url, options={}) | |
941 options[:class] ||= '' | |
942 if options.delete(:selected) | |
943 options[:class] << ' icon-checked disabled' | |
944 options[:disabled] = true | |
945 end | |
946 if options.delete(:disabled) | |
947 options.delete(:method) | |
948 options.delete(:confirm) | |
949 options.delete(:onclick) | |
950 options[:class] << ' disabled' | |
951 url = '#' | |
952 end | |
953 link_to h(name), url, options | |
954 end | 1103 end |
955 | 1104 |
956 def calendar_for(field_id) | 1105 def calendar_for(field_id) |
957 include_calendar_headers_tags | 1106 include_calendar_headers_tags |
958 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + | 1107 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") |
959 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });") | |
960 end | 1108 end |
961 | 1109 |
962 def include_calendar_headers_tags | 1110 def include_calendar_headers_tags |
963 unless @calendar_headers_tags_included | 1111 unless @calendar_headers_tags_included |
964 @calendar_headers_tags_included = true | 1112 @calendar_headers_tags_included = true |
965 content_for :header_tags do | 1113 content_for :header_tags do |
966 start_of_week = case Setting.start_of_week.to_i | 1114 start_of_week = Setting.start_of_week |
967 when 1 | 1115 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? |
968 'Calendar._FD = 1;' # Monday | 1116 # Redmine uses 1..7 (monday..sunday) in settings and locales |
969 when 7 | 1117 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 |
970 'Calendar._FD = 0;' # Sunday | 1118 start_of_week = start_of_week.to_i % 7 |
971 when 6 | 1119 |
972 'Calendar._FD = 6;' # Saturday | 1120 tags = javascript_tag( |
1121 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + | |
1122 "showOn: 'button', buttonImageOnly: true, buttonImage: '" + | |
1123 path_to_image('/images/calendar.png') + | |
1124 "', showButtonPanel: true};") | |
1125 jquery_locale = l('jquery.locale', :default => current_language.to_s) | |
1126 unless jquery_locale == 'en' | |
1127 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") | |
1128 end | |
1129 tags | |
1130 end | |
1131 end | |
1132 end | |
1133 | |
1134 # Overrides Rails' stylesheet_link_tag with themes and plugins support. | |
1135 # Examples: | |
1136 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults | |
1137 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets | |
1138 # | |
1139 def stylesheet_link_tag(*sources) | |
1140 options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1141 plugin = options.delete(:plugin) | |
1142 sources = sources.map do |source| | |
1143 if plugin | |
1144 "/plugin_assets/#{plugin}/stylesheets/#{source}" | |
1145 elsif current_theme && current_theme.stylesheets.include?(source) | |
1146 current_theme.stylesheet_path(source) | |
1147 else | |
1148 source | |
1149 end | |
1150 end | |
1151 super sources, options | |
1152 end | |
1153 | |
1154 # Overrides Rails' image_tag with themes and plugins support. | |
1155 # Examples: | |
1156 # image_tag('image.png') # => picks image.png from the current theme or defaults | |
1157 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets | |
1158 # | |
1159 def image_tag(source, options={}) | |
1160 if plugin = options.delete(:plugin) | |
1161 source = "/plugin_assets/#{plugin}/images/#{source}" | |
1162 elsif current_theme && current_theme.images.include?(source) | |
1163 source = current_theme.image_path(source) | |
1164 end | |
1165 super source, options | |
1166 end | |
1167 | |
1168 # Overrides Rails' javascript_include_tag with plugins support | |
1169 # Examples: | |
1170 # javascript_include_tag('scripts') # => picks scripts.js from defaults | |
1171 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets | |
1172 # | |
1173 def javascript_include_tag(*sources) | |
1174 options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1175 if plugin = options.delete(:plugin) | |
1176 sources = sources.map do |source| | |
1177 if plugin | |
1178 "/plugin_assets/#{plugin}/javascripts/#{source}" | |
973 else | 1179 else |
974 '' # use language | 1180 source |
975 end | 1181 end |
976 | 1182 end |
977 javascript_include_tag('calendar/calendar') + | 1183 end |
978 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") + | 1184 super sources, options |
979 javascript_tag(start_of_week) + | |
980 javascript_include_tag('calendar/calendar-setup') + | |
981 stylesheet_link_tag('calendar') | |
982 end | |
983 end | |
984 end | 1185 end |
985 | 1186 |
986 def content_for(name, content = nil, &block) | 1187 def content_for(name, content = nil, &block) |
987 @has_content ||= {} | 1188 @has_content ||= {} |
988 @has_content[name] = true | 1189 @has_content[name] = true |
991 | 1192 |
992 def has_content?(name) | 1193 def has_content?(name) |
993 (@has_content && @has_content[name]) || false | 1194 (@has_content && @has_content[name]) || false |
994 end | 1195 end |
995 | 1196 |
1197 def sidebar_content? | |
1198 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? | |
1199 end | |
1200 | |
1201 def view_layouts_base_sidebar_hook_response | |
1202 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) | |
1203 end | |
1204 | |
996 def email_delivery_enabled? | 1205 def email_delivery_enabled? |
997 !!ActionMailer::Base.perform_deliveries | 1206 !!ActionMailer::Base.perform_deliveries |
998 end | 1207 end |
999 | 1208 |
1000 # Returns the avatar image tag for the given +user+ if avatars are enabled | 1209 # Returns the avatar image tag for the given +user+ if avatars are enabled |
1001 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') | 1210 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
1002 def avatar(user, options = { }) | 1211 def avatar(user, options = { }) |
1003 if Setting.gravatar_enabled? | 1212 if Setting.gravatar_enabled? |
1004 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) | 1213 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) |
1005 email = nil | 1214 email = nil |
1006 if user.respond_to?(:mail) | 1215 if user.respond_to?(:mail) |
1007 email = user.mail | 1216 email = user.mail |
1008 elsif user.to_s =~ %r{<(.+?)>} | 1217 elsif user.to_s =~ %r{<(.+?)>} |
1009 email = $1 | 1218 email = $1 |
1013 '' | 1222 '' |
1014 end | 1223 end |
1015 end | 1224 end |
1016 | 1225 |
1017 def sanitize_anchor_name(anchor) | 1226 def sanitize_anchor_name(anchor) |
1018 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | 1227 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' |
1228 anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1229 else | |
1230 # TODO: remove when ruby1.8 is no longer supported | |
1231 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1232 end | |
1019 end | 1233 end |
1020 | 1234 |
1021 # Returns the javascript tags that are included in the html layout head | 1235 # Returns the javascript tags that are included in the html layout head |
1022 def javascript_heads | 1236 def javascript_heads |
1023 tags = javascript_include_tag(:defaults) | 1237 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') |
1024 unless User.current.pref.warn_on_leaving_unsaved == '0' | 1238 unless User.current.pref.warn_on_leaving_unsaved == '0' |
1025 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") | 1239 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") |
1026 end | 1240 end |
1027 tags | 1241 tags |
1028 end | 1242 end |
1029 | 1243 |
1030 def favicon | 1244 def favicon |