##// END OF EJS Templates
Rails3: prevent double rendering file view and annotate on Rails 3.0.11 and Rails 3.1.3...
Toshi MARUYAMA -
r8868:b4630c03f732
parent child
Show More
@@ -1,1107 +1,1113
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require 'forwardable'
21 21 require 'cgi'
22 22
23 23 module ApplicationHelper
24 24 include Redmine::WikiFormatting::Macros::Definitions
25 25 include Redmine::I18n
26 26 include GravatarHelper::PublicMethods
27 27
28 28 extend Forwardable
29 29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 30
31 31 # Return true if user is authorized for controller/action, otherwise false
32 32 def authorize_for(controller, action)
33 33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 34 end
35 35
36 36 # Display a link if user is authorized
37 37 #
38 38 # @param [String] name Anchor text (passed to link_to)
39 39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 40 # @param [optional, Hash] html_options Options passed to link_to
41 41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 44 end
45 45
46 46 # Display a link to remote if user is authorized
47 47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 48 url = options[:url] || {}
49 49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 50 end
51 51
52 52 # Displays a link to user's account page if active
53 53 def link_to_user(user, options={})
54 54 if user.is_a?(User)
55 55 name = h(user.name(options[:format]))
56 56 if user.active?
57 57 link_to name, :controller => 'users', :action => 'show', :id => user
58 58 else
59 59 name
60 60 end
61 61 else
62 62 h(user.to_s)
63 63 end
64 64 end
65 65
66 66 # Displays a link to +issue+ with its subject.
67 67 # Examples:
68 68 #
69 69 # link_to_issue(issue) # => Defect #6: This is the subject
70 70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 71 # link_to_issue(issue, :subject => false) # => Defect #6
72 72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 73 #
74 74 def link_to_issue(issue, options={})
75 75 title = nil
76 76 subject = nil
77 77 if options[:subject] == false
78 78 title = truncate(issue.subject, :length => 60)
79 79 else
80 80 subject = issue.subject
81 81 if options[:truncate]
82 82 subject = truncate(subject, :length => options[:truncate])
83 83 end
84 84 end
85 85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 86 :class => issue.css_classes,
87 87 :title => title
88 88 s << h(": #{subject}") if subject
89 89 s = h("#{issue.project} - ") + s if options[:project]
90 90 s
91 91 end
92 92
93 93 # Generates a link to an attachment.
94 94 # Options:
95 95 # * :text - Link text (default to attachment filename)
96 96 # * :download - Force download (default: false)
97 97 def link_to_attachment(attachment, options={})
98 98 text = options.delete(:text) || attachment.filename
99 99 action = options.delete(:download) ? 'download' : 'show'
100 100 link_to(h(text),
101 101 {:controller => 'attachments', :action => action,
102 102 :id => attachment, :filename => attachment.filename },
103 103 options)
104 104 end
105 105
106 106 # Generates a link to a SCM revision
107 107 # Options:
108 108 # * :text - Link text (default to the formatted revision)
109 109 def link_to_revision(revision, repository, options={})
110 110 if repository.is_a?(Project)
111 111 repository = repository.repository
112 112 end
113 113 text = options.delete(:text) || format_revision(revision)
114 114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
115 115 link_to(
116 116 h(text),
117 117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
118 118 :title => l(:label_revision_id, format_revision(revision))
119 119 )
120 120 end
121 121
122 122 # Generates a link to a message
123 123 def link_to_message(message, options={}, html_options = nil)
124 124 link_to(
125 125 h(truncate(message.subject, :length => 60)),
126 126 { :controller => 'messages', :action => 'show',
127 127 :board_id => message.board_id,
128 128 :id => message.root,
129 129 :r => (message.parent_id && message.id),
130 130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
131 131 }.merge(options),
132 132 html_options
133 133 )
134 134 end
135 135
136 136 # Generates a link to a project if active
137 137 # Examples:
138 138 #
139 139 # link_to_project(project) # => link to the specified project overview
140 140 # link_to_project(project, :action=>'settings') # => link to project settings
141 141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
142 142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
143 143 #
144 144 def link_to_project(project, options={}, html_options = nil)
145 145 if project.active?
146 146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
147 147 link_to(h(project), url, html_options)
148 148 else
149 149 h(project)
150 150 end
151 151 end
152 152
153 153 def toggle_link(name, id, options={})
154 154 onclick = "Element.toggle('#{id}'); "
155 155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
156 156 onclick << "return false;"
157 157 link_to(name, "#", :onclick => onclick)
158 158 end
159 159
160 160 def image_to_function(name, function, html_options = {})
161 161 html_options.symbolize_keys!
162 162 tag(:input, html_options.merge({
163 163 :type => "image", :src => image_path(name),
164 164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
165 165 }))
166 166 end
167 167
168 168 def prompt_to_remote(name, text, param, url, html_options = {})
169 169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
170 170 link_to name, {}, html_options
171 171 end
172 172
173 173 def format_activity_title(text)
174 174 h(truncate_single_line(text, :length => 100))
175 175 end
176 176
177 177 def format_activity_day(date)
178 178 date == Date.today ? l(:label_today).titleize : format_date(date)
179 179 end
180 180
181 181 def format_activity_description(text)
182 182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
183 183 ).gsub(/[\r\n]+/, "<br />").html_safe
184 184 end
185 185
186 186 def format_version_name(version)
187 187 if version.project == @project
188 188 h(version)
189 189 else
190 190 h("#{version.project} - #{version}")
191 191 end
192 192 end
193 193
194 194 def due_date_distance_in_words(date)
195 195 if date
196 196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
197 197 end
198 198 end
199 199
200 200 def render_page_hierarchy(pages, node=nil, options={})
201 201 content = ''
202 202 if pages[node]
203 203 content << "<ul class=\"pages-hierarchy\">\n"
204 204 pages[node].each do |page|
205 205 content << "<li>"
206 206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
207 207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
208 208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
209 209 content << "</li>\n"
210 210 end
211 211 content << "</ul>\n"
212 212 end
213 213 content.html_safe
214 214 end
215 215
216 216 # Renders flash messages
217 217 def render_flash_messages
218 218 s = ''
219 219 flash.each do |k,v|
220 220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
221 221 end
222 222 s.html_safe
223 223 end
224 224
225 225 # Renders tabs and their content
226 226 def render_tabs(tabs)
227 227 if tabs.any?
228 228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
229 229 else
230 230 content_tag 'p', l(:label_no_data), :class => "nodata"
231 231 end
232 232 end
233 233
234 234 # Renders the project quick-jump box
235 235 def render_project_jump_box
236 236 return unless User.current.logged?
237 237 projects = User.current.memberships.collect(&:project).compact.uniq
238 238 if projects.any?
239 239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
240 240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
241 241 '<option value="" disabled="disabled">---</option>'
242 242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
243 243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
244 244 end
245 245 s << '</select>'
246 246 s.html_safe
247 247 end
248 248 end
249 249
250 250 def project_tree_options_for_select(projects, options = {})
251 251 s = ''
252 252 project_tree(projects) do |project, level|
253 253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
254 254 tag_options = {:value => project.id}
255 255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
256 256 tag_options[:selected] = 'selected'
257 257 else
258 258 tag_options[:selected] = nil
259 259 end
260 260 tag_options.merge!(yield(project)) if block_given?
261 261 s << content_tag('option', name_prefix + h(project), tag_options)
262 262 end
263 263 s.html_safe
264 264 end
265 265
266 266 # Yields the given block for each project with its level in the tree
267 267 #
268 268 # Wrapper for Project#project_tree
269 269 def project_tree(projects, &block)
270 270 Project.project_tree(projects, &block)
271 271 end
272 272
273 273 def project_nested_ul(projects, &block)
274 274 s = ''
275 275 if projects.any?
276 276 ancestors = []
277 277 projects.sort_by(&:lft).each do |project|
278 278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
279 279 s << "<ul>\n"
280 280 else
281 281 ancestors.pop
282 282 s << "</li>"
283 283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
284 284 ancestors.pop
285 285 s << "</ul></li>\n"
286 286 end
287 287 end
288 288 s << "<li>"
289 289 s << yield(project).to_s
290 290 ancestors << project
291 291 end
292 292 s << ("</li></ul>\n" * ancestors.size)
293 293 end
294 294 s.html_safe
295 295 end
296 296
297 297 def principals_check_box_tags(name, principals)
298 298 s = ''
299 299 principals.sort.each do |principal|
300 300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
301 301 end
302 302 s.html_safe
303 303 end
304 304
305 305 # Returns a string for users/groups option tags
306 306 def principals_options_for_select(collection, selected=nil)
307 307 s = ''
308 308 if collection.include?(User.current)
309 309 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
310 310 end
311 311 groups = ''
312 312 collection.sort.each do |element|
313 313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
314 314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
315 315 end
316 316 unless groups.empty?
317 317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
318 318 end
319 319 s
320 320 end
321 321
322 322 # Truncates and returns the string as a single line
323 323 def truncate_single_line(string, *args)
324 324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
325 325 end
326 326
327 327 # Truncates at line break after 250 characters or options[:length]
328 328 def truncate_lines(string, options={})
329 329 length = options[:length] || 250
330 330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
331 331 "#{$1}..."
332 332 else
333 333 string
334 334 end
335 335 end
336 336
337 337 def anchor(text)
338 338 text.to_s.gsub(' ', '_')
339 339 end
340 340
341 341 def html_hours(text)
342 342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
343 343 end
344 344
345 345 def authoring(created, author, options={})
346 346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
347 347 end
348 348
349 349 def time_tag(time)
350 350 text = distance_of_time_in_words(Time.now, time)
351 351 if @project
352 352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
353 353 else
354 354 content_tag('acronym', text, :title => format_time(time))
355 355 end
356 356 end
357 357
358 def syntax_highlight_lines(name, content)
359 lines = []
360 syntax_highlight(name, content).each_line { |line| lines << line }
361 lines
362 end
363
358 364 def syntax_highlight(name, content)
359 365 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
360 366 end
361 367
362 368 def to_path_param(path)
363 369 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
364 370 end
365 371
366 372 def pagination_links_full(paginator, count=nil, options={})
367 373 page_param = options.delete(:page_param) || :page
368 374 per_page_links = options.delete(:per_page_links)
369 375 url_param = params.dup
370 376
371 377 html = ''
372 378 if paginator.current.previous
373 379 # \xc2\xab(utf-8) = &#171;
374 380 html << link_to_content_update(
375 381 "\xc2\xab " + l(:label_previous),
376 382 url_param.merge(page_param => paginator.current.previous)) + ' '
377 383 end
378 384
379 385 html << (pagination_links_each(paginator, options) do |n|
380 386 link_to_content_update(n.to_s, url_param.merge(page_param => n))
381 387 end || '')
382 388
383 389 if paginator.current.next
384 390 # \xc2\xbb(utf-8) = &#187;
385 391 html << ' ' + link_to_content_update(
386 392 (l(:label_next) + " \xc2\xbb"),
387 393 url_param.merge(page_param => paginator.current.next))
388 394 end
389 395
390 396 unless count.nil?
391 397 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
392 398 if per_page_links != false && links = per_page_links(paginator.items_per_page)
393 399 html << " | #{links}"
394 400 end
395 401 end
396 402
397 403 html.html_safe
398 404 end
399 405
400 406 def per_page_links(selected=nil)
401 407 links = Setting.per_page_options_array.collect do |n|
402 408 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
403 409 end
404 410 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
405 411 end
406 412
407 413 def reorder_links(name, url, method = :post)
408 414 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
409 415 url.merge({"#{name}[move_to]" => 'highest'}),
410 416 :method => method, :title => l(:label_sort_highest)) +
411 417 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
412 418 url.merge({"#{name}[move_to]" => 'higher'}),
413 419 :method => method, :title => l(:label_sort_higher)) +
414 420 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
415 421 url.merge({"#{name}[move_to]" => 'lower'}),
416 422 :method => method, :title => l(:label_sort_lower)) +
417 423 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
418 424 url.merge({"#{name}[move_to]" => 'lowest'}),
419 425 :method => method, :title => l(:label_sort_lowest))
420 426 end
421 427
422 428 def breadcrumb(*args)
423 429 elements = args.flatten
424 430 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
425 431 end
426 432
427 433 def other_formats_links(&block)
428 434 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
429 435 yield Redmine::Views::OtherFormatsBuilder.new(self)
430 436 concat('</p>'.html_safe)
431 437 end
432 438
433 439 def page_header_title
434 440 if @project.nil? || @project.new_record?
435 441 h(Setting.app_title)
436 442 else
437 443 b = []
438 444 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
439 445 if ancestors.any?
440 446 root = ancestors.shift
441 447 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
442 448 if ancestors.size > 2
443 449 b << "\xe2\x80\xa6"
444 450 ancestors = ancestors[-2, 2]
445 451 end
446 452 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
447 453 end
448 454 b << h(@project)
449 455 b.join(" \xc2\xbb ").html_safe
450 456 end
451 457 end
452 458
453 459 def html_title(*args)
454 460 if args.empty?
455 461 title = @html_title || []
456 462 title << @project.name if @project
457 463 title << Setting.app_title unless Setting.app_title == title.last
458 464 title.select {|t| !t.blank? }.join(' - ')
459 465 else
460 466 @html_title ||= []
461 467 @html_title += args
462 468 end
463 469 end
464 470
465 471 # Returns the theme, controller name, and action as css classes for the
466 472 # HTML body.
467 473 def body_css_classes
468 474 css = []
469 475 if theme = Redmine::Themes.theme(Setting.ui_theme)
470 476 css << 'theme-' + theme.name
471 477 end
472 478
473 479 css << 'controller-' + params[:controller]
474 480 css << 'action-' + params[:action]
475 481 css.join(' ')
476 482 end
477 483
478 484 def accesskey(s)
479 485 Redmine::AccessKeys.key_for s
480 486 end
481 487
482 488 # Formats text according to system settings.
483 489 # 2 ways to call this method:
484 490 # * with a String: textilizable(text, options)
485 491 # * with an object and one of its attribute: textilizable(issue, :description, options)
486 492 def textilizable(*args)
487 493 options = args.last.is_a?(Hash) ? args.pop : {}
488 494 case args.size
489 495 when 1
490 496 obj = options[:object]
491 497 text = args.shift
492 498 when 2
493 499 obj = args.shift
494 500 attr = args.shift
495 501 text = obj.send(attr).to_s
496 502 else
497 503 raise ArgumentError, 'invalid arguments to textilizable'
498 504 end
499 505 return '' if text.blank?
500 506 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
501 507 only_path = options.delete(:only_path) == false ? false : true
502 508
503 509 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
504 510
505 511 @parsed_headings = []
506 512 @heading_anchors = {}
507 513 @current_section = 0 if options[:edit_section_links]
508 514
509 515 parse_sections(text, project, obj, attr, only_path, options)
510 516 text = parse_non_pre_blocks(text) do |text|
511 517 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
512 518 send method_name, text, project, obj, attr, only_path, options
513 519 end
514 520 end
515 521 parse_headings(text, project, obj, attr, only_path, options)
516 522
517 523 if @parsed_headings.any?
518 524 replace_toc(text, @parsed_headings)
519 525 end
520 526
521 527 text.html_safe
522 528 end
523 529
524 530 def parse_non_pre_blocks(text)
525 531 s = StringScanner.new(text)
526 532 tags = []
527 533 parsed = ''
528 534 while !s.eos?
529 535 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
530 536 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
531 537 if tags.empty?
532 538 yield text
533 539 end
534 540 parsed << text
535 541 if tag
536 542 if closing
537 543 if tags.last == tag.downcase
538 544 tags.pop
539 545 end
540 546 else
541 547 tags << tag.downcase
542 548 end
543 549 parsed << full_tag
544 550 end
545 551 end
546 552 # Close any non closing tags
547 553 while tag = tags.pop
548 554 parsed << "</#{tag}>"
549 555 end
550 556 parsed
551 557 end
552 558
553 559 def parse_inline_attachments(text, project, obj, attr, only_path, options)
554 560 # when using an image link, try to use an attachment, if possible
555 561 if options[:attachments] || (obj && obj.respond_to?(:attachments))
556 562 attachments = options[:attachments] || obj.attachments
557 563 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
558 564 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
559 565 # search for the picture in attachments
560 566 if found = Attachment.latest_attach(attachments, filename)
561 567 image_url = url_for :only_path => only_path, :controller => 'attachments',
562 568 :action => 'download', :id => found
563 569 desc = found.description.to_s.gsub('"', '')
564 570 if !desc.blank? && alttext.blank?
565 571 alt = " title=\"#{desc}\" alt=\"#{desc}\""
566 572 end
567 573 "src=\"#{image_url}\"#{alt}"
568 574 else
569 575 m
570 576 end
571 577 end
572 578 end
573 579 end
574 580
575 581 # Wiki links
576 582 #
577 583 # Examples:
578 584 # [[mypage]]
579 585 # [[mypage|mytext]]
580 586 # wiki links can refer other project wikis, using project name or identifier:
581 587 # [[project:]] -> wiki starting page
582 588 # [[project:|mytext]]
583 589 # [[project:mypage]]
584 590 # [[project:mypage|mytext]]
585 591 def parse_wiki_links(text, project, obj, attr, only_path, options)
586 592 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
587 593 link_project = project
588 594 esc, all, page, title = $1, $2, $3, $5
589 595 if esc.nil?
590 596 if page =~ /^([^\:]+)\:(.*)$/
591 597 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
592 598 page = $2
593 599 title ||= $1 if page.blank?
594 600 end
595 601
596 602 if link_project && link_project.wiki
597 603 # extract anchor
598 604 anchor = nil
599 605 if page =~ /^(.+?)\#(.+)$/
600 606 page, anchor = $1, $2
601 607 end
602 608 anchor = sanitize_anchor_name(anchor) if anchor.present?
603 609 # check if page exists
604 610 wiki_page = link_project.wiki.find_page(page)
605 611 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
606 612 "##{anchor}"
607 613 else
608 614 case options[:wiki_links]
609 615 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
610 616 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
611 617 else
612 618 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
613 619 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
614 620 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
615 621 :id => wiki_page_id, :anchor => anchor, :parent => parent)
616 622 end
617 623 end
618 624 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
619 625 else
620 626 # project or wiki doesn't exist
621 627 all
622 628 end
623 629 else
624 630 all
625 631 end
626 632 end
627 633 end
628 634
629 635 # Redmine links
630 636 #
631 637 # Examples:
632 638 # Issues:
633 639 # #52 -> Link to issue #52
634 640 # Changesets:
635 641 # r52 -> Link to revision 52
636 642 # commit:a85130f -> Link to scmid starting with a85130f
637 643 # Documents:
638 644 # document#17 -> Link to document with id 17
639 645 # document:Greetings -> Link to the document with title "Greetings"
640 646 # document:"Some document" -> Link to the document with title "Some document"
641 647 # Versions:
642 648 # version#3 -> Link to version with id 3
643 649 # version:1.0.0 -> Link to version named "1.0.0"
644 650 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
645 651 # Attachments:
646 652 # attachment:file.zip -> Link to the attachment of the current object named file.zip
647 653 # Source files:
648 654 # source:some/file -> Link to the file located at /some/file in the project's repository
649 655 # source:some/file@52 -> Link to the file's revision 52
650 656 # source:some/file#L120 -> Link to line 120 of the file
651 657 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
652 658 # export:some/file -> Force the download of the file
653 659 # Forum messages:
654 660 # message#1218 -> Link to message with id 1218
655 661 #
656 662 # Links can refer other objects from other projects, using project identifier:
657 663 # identifier:r52
658 664 # identifier:document:"Some document"
659 665 # identifier:version:1.0.0
660 666 # identifier:source:some/file
661 667 def parse_redmine_links(text, project, obj, attr, only_path, options)
662 668 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|
663 669 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
664 670 link = nil
665 671 if project_identifier
666 672 project = Project.visible.find_by_identifier(project_identifier)
667 673 end
668 674 if esc.nil?
669 675 if prefix.nil? && sep == 'r'
670 676 if project
671 677 repository = nil
672 678 if repo_identifier
673 679 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
674 680 else
675 681 repository = project.repository
676 682 end
677 683 # project.changesets.visible raises an SQL error because of a double join on repositories
678 684 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
679 685 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},
680 686 :class => 'changeset',
681 687 :title => truncate_single_line(changeset.comments, :length => 100))
682 688 end
683 689 end
684 690 elsif sep == '#'
685 691 oid = identifier.to_i
686 692 case prefix
687 693 when nil
688 694 if issue = Issue.visible.find_by_id(oid, :include => :status)
689 695 anchor = comment_id ? "note-#{comment_id}" : nil
690 696 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
691 697 :class => issue.css_classes,
692 698 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
693 699 end
694 700 when 'document'
695 701 if document = Document.visible.find_by_id(oid)
696 702 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
697 703 :class => 'document'
698 704 end
699 705 when 'version'
700 706 if version = Version.visible.find_by_id(oid)
701 707 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
702 708 :class => 'version'
703 709 end
704 710 when 'message'
705 711 if message = Message.visible.find_by_id(oid, :include => :parent)
706 712 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
707 713 end
708 714 when 'forum'
709 715 if board = Board.visible.find_by_id(oid)
710 716 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
711 717 :class => 'board'
712 718 end
713 719 when 'news'
714 720 if news = News.visible.find_by_id(oid)
715 721 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
716 722 :class => 'news'
717 723 end
718 724 when 'project'
719 725 if p = Project.visible.find_by_id(oid)
720 726 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
721 727 end
722 728 end
723 729 elsif sep == ':'
724 730 # removes the double quotes if any
725 731 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
726 732 case prefix
727 733 when 'document'
728 734 if project && document = project.documents.visible.find_by_title(name)
729 735 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
730 736 :class => 'document'
731 737 end
732 738 when 'version'
733 739 if project && version = project.versions.visible.find_by_name(name)
734 740 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
735 741 :class => 'version'
736 742 end
737 743 when 'forum'
738 744 if project && board = project.boards.visible.find_by_name(name)
739 745 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
740 746 :class => 'board'
741 747 end
742 748 when 'news'
743 749 if project && news = project.news.visible.find_by_title(name)
744 750 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
745 751 :class => 'news'
746 752 end
747 753 when 'commit', 'source', 'export'
748 754 if project
749 755 repository = nil
750 756 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
751 757 repo_prefix, repo_identifier, name = $1, $2, $3
752 758 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
753 759 else
754 760 repository = project.repository
755 761 end
756 762 if prefix == 'commit'
757 763 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
758 764 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},
759 765 :class => 'changeset',
760 766 :title => truncate_single_line(h(changeset.comments), :length => 100)
761 767 end
762 768 else
763 769 if repository && User.current.allowed_to?(:browse_repository, project)
764 770 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
765 771 path, rev, anchor = $1, $3, $5
766 772 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
767 773 :path => to_path_param(path),
768 774 :rev => rev,
769 775 :anchor => anchor,
770 776 :format => (prefix == 'export' ? 'raw' : nil)},
771 777 :class => (prefix == 'export' ? 'source download' : 'source')
772 778 end
773 779 end
774 780 repo_prefix = nil
775 781 end
776 782 when 'attachment'
777 783 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
778 784 if attachments && attachment = attachments.detect {|a| a.filename == name }
779 785 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
780 786 :class => 'attachment'
781 787 end
782 788 when 'project'
783 789 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
784 790 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
785 791 end
786 792 end
787 793 end
788 794 end
789 795 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
790 796 end
791 797 end
792 798
793 799 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
794 800
795 801 def parse_sections(text, project, obj, attr, only_path, options)
796 802 return unless options[:edit_section_links]
797 803 text.gsub!(HEADING_RE) do
798 804 heading = $1
799 805 @current_section += 1
800 806 if @current_section > 1
801 807 content_tag('div',
802 808 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
803 809 :class => 'contextual',
804 810 :title => l(:button_edit_section)) + heading.html_safe
805 811 else
806 812 heading
807 813 end
808 814 end
809 815 end
810 816
811 817 # Headings and TOC
812 818 # Adds ids and links to headings unless options[:headings] is set to false
813 819 def parse_headings(text, project, obj, attr, only_path, options)
814 820 return if options[:headings] == false
815 821
816 822 text.gsub!(HEADING_RE) do
817 823 level, attrs, content = $2.to_i, $3, $4
818 824 item = strip_tags(content).strip
819 825 anchor = sanitize_anchor_name(item)
820 826 # used for single-file wiki export
821 827 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
822 828 @heading_anchors[anchor] ||= 0
823 829 idx = (@heading_anchors[anchor] += 1)
824 830 if idx > 1
825 831 anchor = "#{anchor}-#{idx}"
826 832 end
827 833 @parsed_headings << [level, anchor, item]
828 834 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
829 835 end
830 836 end
831 837
832 838 MACROS_RE = /
833 839 (!)? # escaping
834 840 (
835 841 \{\{ # opening tag
836 842 ([\w]+) # macro name
837 843 (\(([^\}]*)\))? # optional arguments
838 844 \}\} # closing tag
839 845 )
840 846 /x unless const_defined?(:MACROS_RE)
841 847
842 848 # Macros substitution
843 849 def parse_macros(text, project, obj, attr, only_path, options)
844 850 text.gsub!(MACROS_RE) do
845 851 esc, all, macro = $1, $2, $3.downcase
846 852 args = ($5 || '').split(',').each(&:strip)
847 853 if esc.nil?
848 854 begin
849 855 exec_macro(macro, obj, args)
850 856 rescue => e
851 857 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
852 858 end || all
853 859 else
854 860 all
855 861 end
856 862 end
857 863 end
858 864
859 865 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
860 866
861 867 # Renders the TOC with given headings
862 868 def replace_toc(text, headings)
863 869 text.gsub!(TOC_RE) do
864 870 if headings.empty?
865 871 ''
866 872 else
867 873 div_class = 'toc'
868 874 div_class << ' right' if $1 == '>'
869 875 div_class << ' left' if $1 == '<'
870 876 out = "<ul class=\"#{div_class}\"><li>"
871 877 root = headings.map(&:first).min
872 878 current = root
873 879 started = false
874 880 headings.each do |level, anchor, item|
875 881 if level > current
876 882 out << '<ul><li>' * (level - current)
877 883 elsif level < current
878 884 out << "</li></ul>\n" * (current - level) + "</li><li>"
879 885 elsif started
880 886 out << '</li><li>'
881 887 end
882 888 out << "<a href=\"##{anchor}\">#{item}</a>"
883 889 current = level
884 890 started = true
885 891 end
886 892 out << '</li></ul>' * (current - root)
887 893 out << '</li></ul>'
888 894 end
889 895 end
890 896 end
891 897
892 898 # Same as Rails' simple_format helper without using paragraphs
893 899 def simple_format_without_paragraph(text)
894 900 text.to_s.
895 901 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
896 902 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
897 903 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
898 904 html_safe
899 905 end
900 906
901 907 def lang_options_for_select(blank=true)
902 908 (blank ? [["(auto)", ""]] : []) +
903 909 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
904 910 end
905 911
906 912 def label_tag_for(name, option_tags = nil, options = {})
907 913 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
908 914 content_tag("label", label_text)
909 915 end
910 916
911 917 def labelled_tabular_form_for(*args, &proc)
912 918 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
913 919 args << {} unless args.last.is_a?(Hash)
914 920 options = args.last
915 921 options[:html] ||= {}
916 922 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
917 923 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
918 924 form_for(*args, &proc)
919 925 end
920 926
921 927 def labelled_form_for(*args, &proc)
922 928 args << {} unless args.last.is_a?(Hash)
923 929 options = args.last
924 930 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
925 931 form_for(*args, &proc)
926 932 end
927 933
928 934 def labelled_fields_for(*args, &proc)
929 935 args << {} unless args.last.is_a?(Hash)
930 936 options = args.last
931 937 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
932 938 fields_for(*args, &proc)
933 939 end
934 940
935 941 def labelled_remote_form_for(*args, &proc)
936 942 args << {} unless args.last.is_a?(Hash)
937 943 options = args.last
938 944 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
939 945 remote_form_for(*args, &proc)
940 946 end
941 947
942 948 def back_url_hidden_field_tag
943 949 back_url = params[:back_url] || request.env['HTTP_REFERER']
944 950 back_url = CGI.unescape(back_url.to_s)
945 951 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
946 952 end
947 953
948 954 def check_all_links(form_name)
949 955 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
950 956 " | ".html_safe +
951 957 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
952 958 end
953 959
954 960 def progress_bar(pcts, options={})
955 961 pcts = [pcts, pcts] unless pcts.is_a?(Array)
956 962 pcts = pcts.collect(&:round)
957 963 pcts[1] = pcts[1] - pcts[0]
958 964 pcts << (100 - pcts[1] - pcts[0])
959 965 width = options[:width] || '100px;'
960 966 legend = options[:legend] || ''
961 967 content_tag('table',
962 968 content_tag('tr',
963 969 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
964 970 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
965 971 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
966 972 ), :class => 'progress', :style => "width: #{width};").html_safe +
967 973 content_tag('p', legend, :class => 'pourcent').html_safe
968 974 end
969 975
970 976 def checked_image(checked=true)
971 977 if checked
972 978 image_tag 'toggle_check.png'
973 979 end
974 980 end
975 981
976 982 def context_menu(url)
977 983 unless @context_menu_included
978 984 content_for :header_tags do
979 985 javascript_include_tag('context_menu') +
980 986 stylesheet_link_tag('context_menu')
981 987 end
982 988 if l(:direction) == 'rtl'
983 989 content_for :header_tags do
984 990 stylesheet_link_tag('context_menu_rtl')
985 991 end
986 992 end
987 993 @context_menu_included = true
988 994 end
989 995 javascript_tag "new ContextMenu('#{ url_for(url) }')"
990 996 end
991 997
992 998 def calendar_for(field_id)
993 999 include_calendar_headers_tags
994 1000 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
995 1001 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
996 1002 end
997 1003
998 1004 def include_calendar_headers_tags
999 1005 unless @calendar_headers_tags_included
1000 1006 @calendar_headers_tags_included = true
1001 1007 content_for :header_tags do
1002 1008 start_of_week = case Setting.start_of_week.to_i
1003 1009 when 1
1004 1010 'Calendar._FD = 1;' # Monday
1005 1011 when 7
1006 1012 'Calendar._FD = 0;' # Sunday
1007 1013 when 6
1008 1014 'Calendar._FD = 6;' # Saturday
1009 1015 else
1010 1016 '' # use language
1011 1017 end
1012 1018
1013 1019 javascript_include_tag('calendar/calendar') +
1014 1020 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1015 1021 javascript_tag(start_of_week) +
1016 1022 javascript_include_tag('calendar/calendar-setup') +
1017 1023 stylesheet_link_tag('calendar')
1018 1024 end
1019 1025 end
1020 1026 end
1021 1027
1022 1028 def content_for(name, content = nil, &block)
1023 1029 @has_content ||= {}
1024 1030 @has_content[name] = true
1025 1031 super(name, content, &block)
1026 1032 end
1027 1033
1028 1034 def has_content?(name)
1029 1035 (@has_content && @has_content[name]) || false
1030 1036 end
1031 1037
1032 1038 def email_delivery_enabled?
1033 1039 !!ActionMailer::Base.perform_deliveries
1034 1040 end
1035 1041
1036 1042 # Returns the avatar image tag for the given +user+ if avatars are enabled
1037 1043 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1038 1044 def avatar(user, options = { })
1039 1045 if Setting.gravatar_enabled?
1040 1046 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1041 1047 email = nil
1042 1048 if user.respond_to?(:mail)
1043 1049 email = user.mail
1044 1050 elsif user.to_s =~ %r{<(.+?)>}
1045 1051 email = $1
1046 1052 end
1047 1053 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1048 1054 else
1049 1055 ''
1050 1056 end
1051 1057 end
1052 1058
1053 1059 def sanitize_anchor_name(anchor)
1054 1060 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1055 1061 end
1056 1062
1057 1063 # Returns the javascript tags that are included in the html layout head
1058 1064 def javascript_heads
1059 1065 tags = javascript_include_tag(:defaults)
1060 1066 unless User.current.pref.warn_on_leaving_unsaved == '0'
1061 1067 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1062 1068 end
1063 1069 tags
1064 1070 end
1065 1071
1066 1072 def favicon
1067 1073 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1068 1074 end
1069 1075
1070 1076 def robot_exclusion_tag
1071 1077 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1072 1078 end
1073 1079
1074 1080 # Returns true if arg is expected in the API response
1075 1081 def include_in_api_response?(arg)
1076 1082 unless @included_in_api_response
1077 1083 param = params[:include]
1078 1084 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1079 1085 @included_in_api_response.collect!(&:strip)
1080 1086 end
1081 1087 @included_in_api_response.include?(arg.to_s)
1082 1088 end
1083 1089
1084 1090 # Returns options or nil if nometa param or X-Redmine-Nometa header
1085 1091 # was set in the request
1086 1092 def api_meta(options)
1087 1093 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1088 1094 # compatibility mode for activeresource clients that raise
1089 1095 # an error when unserializing an array with attributes
1090 1096 nil
1091 1097 else
1092 1098 options
1093 1099 end
1094 1100 end
1095 1101
1096 1102 private
1097 1103
1098 1104 def wiki_helper
1099 1105 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1100 1106 extend helper
1101 1107 return self
1102 1108 end
1103 1109
1104 1110 def link_to_content_update(text, url_params = {}, html_options = {})
1105 1111 link_to(text, url_params, html_options)
1106 1112 end
1107 1113 end
@@ -1,18 +1,18
1 1 <div class="autoscroll">
2 2 <table class="filecontent syntaxhl">
3 3 <tbody>
4 4 <% line_num = 1 %>
5 <% syntax_highlight(filename, Redmine::CodesetUtil.to_utf8_by_setting(content)).each_line do |line| %>
5 <% syntax_highlight_lines(filename, Redmine::CodesetUtil.to_utf8_by_setting(content)).each do |line| %>
6 6 <tr>
7 7 <th class="line-num" id="L<%= line_num %>">
8 8 <a href="#L<%= line_num %>"><%= line_num %></a>
9 9 </th>
10 10 <td class="line-code">
11 <pre><%= line %></pre>
11 <pre><%= line.html_safe %></pre>
12 12 </td>
13 13 </tr>
14 14 <% line_num += 1 %>
15 15 <% end %>
16 16 </tbody>
17 17 </table>
18 18 </div>
@@ -1,36 +1,36
1 1 <%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
2 2
3 3 <div class="contextual">
4 4 <%= render :partial => 'navigation' %>
5 5 </div>
6 6
7 7 <h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
8 8
9 9 <p><%= render :partial => 'link_to_functions' %></p>
10 10
11 11 <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
12 12
13 13 <div class="autoscroll">
14 14 <table class="filecontent annotate syntaxhl">
15 15 <tbody>
16 16 <% line_num = 1 %>
17 <% syntax_highlight(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each_line do |line| %>
17 <% syntax_highlight_lines(@path, Redmine::CodesetUtil.to_utf8_by_setting(@annotate.content)).each do |line| %>
18 18 <% revision = @annotate.revisions[line_num - 1] %>
19 19 <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
20 20 <th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th>
21 21 <td class="revision">
22 22 <%= (revision.identifier ? link_to_revision(revision, @repository) : format_revision(revision)) if revision %></td>
23 23 <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
24 <td class="line-code"><pre><%= line %></pre></td>
24 <td class="line-code"><pre><%= line.html_safe %></pre></td>
25 25 </tr>
26 26 <% line_num += 1 %>
27 27 <% end %>
28 28 </tbody>
29 29 </table>
30 30 </div>
31 31
32 32 <% html_title(l(:button_annotate)) -%>
33 33
34 34 <% content_for :header_tags do %>
35 35 <%= stylesheet_link_tag 'scm' %>
36 36 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now