##// END OF EJS Templates
Adds a helper for preview links....
Jean-Philippe Lang -
r9848:ff68fff80ed1
parent child
Show More
@@ -1,1224 +1,1232
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2012 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 opt_only_path = {}
101 101 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
102 102 options.delete(:only_path)
103 103 link_to(h(text),
104 104 {:controller => 'attachments', :action => action,
105 105 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
106 106 options)
107 107 end
108 108
109 109 # Generates a link to a SCM revision
110 110 # Options:
111 111 # * :text - Link text (default to the formatted revision)
112 112 def link_to_revision(revision, repository, options={})
113 113 if repository.is_a?(Project)
114 114 repository = repository.repository
115 115 end
116 116 text = options.delete(:text) || format_revision(revision)
117 117 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
118 118 link_to(
119 119 h(text),
120 120 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
121 121 :title => l(:label_revision_id, format_revision(revision))
122 122 )
123 123 end
124 124
125 125 # Generates a link to a message
126 126 def link_to_message(message, options={}, html_options = nil)
127 127 link_to(
128 128 h(truncate(message.subject, :length => 60)),
129 129 { :controller => 'messages', :action => 'show',
130 130 :board_id => message.board_id,
131 131 :id => (message.parent_id || message.id),
132 132 :r => (message.parent_id && message.id),
133 133 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
134 134 }.merge(options),
135 135 html_options
136 136 )
137 137 end
138 138
139 139 # Generates a link to a project if active
140 140 # Examples:
141 141 #
142 142 # link_to_project(project) # => link to the specified project overview
143 143 # link_to_project(project, :action=>'settings') # => link to project settings
144 144 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
145 145 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
146 146 #
147 147 def link_to_project(project, options={}, html_options = nil)
148 148 if project.archived?
149 149 h(project)
150 150 else
151 151 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
152 152 link_to(h(project), url, html_options)
153 153 end
154 154 end
155 155
156 156 def thumbnail_tag(attachment)
157 157 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
158 158 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
159 159 :title => attachment.filename
160 160 end
161 161
162 162 def toggle_link(name, id, options={})
163 163 onclick = "Element.toggle('#{id}'); "
164 164 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
165 165 onclick << "return false;"
166 166 link_to(name, "#", :onclick => onclick)
167 167 end
168 168
169 169 def image_to_function(name, function, html_options = {})
170 170 html_options.symbolize_keys!
171 171 tag(:input, html_options.merge({
172 172 :type => "image", :src => image_path(name),
173 173 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
174 174 }))
175 175 end
176 176
177 177 def prompt_to_remote(name, text, param, url, html_options = {})
178 178 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
179 179 link_to name, {}, html_options
180 180 end
181 181
182 182 def format_activity_title(text)
183 183 h(truncate_single_line(text, :length => 100))
184 184 end
185 185
186 186 def format_activity_day(date)
187 187 date == User.current.today ? l(:label_today).titleize : format_date(date)
188 188 end
189 189
190 190 def format_activity_description(text)
191 191 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
192 192 ).gsub(/[\r\n]+/, "<br />").html_safe
193 193 end
194 194
195 195 def format_version_name(version)
196 196 if version.project == @project
197 197 h(version)
198 198 else
199 199 h("#{version.project} - #{version}")
200 200 end
201 201 end
202 202
203 203 def due_date_distance_in_words(date)
204 204 if date
205 205 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
206 206 end
207 207 end
208 208
209 209 def render_page_hierarchy(pages, node=nil, options={})
210 210 content = ''
211 211 if pages[node]
212 212 content << "<ul class=\"pages-hierarchy\">\n"
213 213 pages[node].each do |page|
214 214 content << "<li>"
215 215 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
216 216 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
217 217 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
218 218 content << "</li>\n"
219 219 end
220 220 content << "</ul>\n"
221 221 end
222 222 content.html_safe
223 223 end
224 224
225 225 # Renders flash messages
226 226 def render_flash_messages
227 227 s = ''
228 228 flash.each do |k,v|
229 229 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
230 230 end
231 231 s.html_safe
232 232 end
233 233
234 234 # Renders tabs and their content
235 235 def render_tabs(tabs)
236 236 if tabs.any?
237 237 render :partial => 'common/tabs', :locals => {:tabs => tabs}
238 238 else
239 239 content_tag 'p', l(:label_no_data), :class => "nodata"
240 240 end
241 241 end
242 242
243 243 # Renders the project quick-jump box
244 244 def render_project_jump_box
245 245 return unless User.current.logged?
246 246 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
247 247 if projects.any?
248 248 options =
249 249 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
250 250 '<option value="" disabled="disabled">---</option>').html_safe
251 251
252 252 options << project_tree_options_for_select(projects, :selected => @project) do |p|
253 253 { :value => project_path(:id => p, :jump => current_menu_item) }
254 254 end
255 255
256 256 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
257 257 end
258 258 end
259 259
260 260 def project_tree_options_for_select(projects, options = {})
261 261 s = ''
262 262 project_tree(projects) do |project, level|
263 263 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
264 264 tag_options = {:value => project.id}
265 265 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
266 266 tag_options[:selected] = 'selected'
267 267 else
268 268 tag_options[:selected] = nil
269 269 end
270 270 tag_options.merge!(yield(project)) if block_given?
271 271 s << content_tag('option', name_prefix + h(project), tag_options)
272 272 end
273 273 s.html_safe
274 274 end
275 275
276 276 # Yields the given block for each project with its level in the tree
277 277 #
278 278 # Wrapper for Project#project_tree
279 279 def project_tree(projects, &block)
280 280 Project.project_tree(projects, &block)
281 281 end
282 282
283 283 def project_nested_ul(projects, &block)
284 284 s = ''
285 285 if projects.any?
286 286 ancestors = []
287 287 projects.sort_by(&:lft).each do |project|
288 288 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
289 289 s << "<ul>\n"
290 290 else
291 291 ancestors.pop
292 292 s << "</li>"
293 293 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
294 294 ancestors.pop
295 295 s << "</ul></li>\n"
296 296 end
297 297 end
298 298 s << "<li>"
299 299 s << yield(project).to_s
300 300 ancestors << project
301 301 end
302 302 s << ("</li></ul>\n" * ancestors.size)
303 303 end
304 304 s.html_safe
305 305 end
306 306
307 307 def principals_check_box_tags(name, principals)
308 308 s = ''
309 309 principals.sort.each do |principal|
310 310 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
311 311 end
312 312 s.html_safe
313 313 end
314 314
315 315 # Returns a string for users/groups option tags
316 316 def principals_options_for_select(collection, selected=nil)
317 317 s = ''
318 318 if collection.include?(User.current)
319 319 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
320 320 end
321 321 groups = ''
322 322 collection.sort.each do |element|
323 323 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
324 324 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
325 325 end
326 326 unless groups.empty?
327 327 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
328 328 end
329 329 s.html_safe
330 330 end
331 331
332 332 # Truncates and returns the string as a single line
333 333 def truncate_single_line(string, *args)
334 334 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
335 335 end
336 336
337 337 # Truncates at line break after 250 characters or options[:length]
338 338 def truncate_lines(string, options={})
339 339 length = options[:length] || 250
340 340 if string.to_s =~ /\A(.{#{length}}.*?)$/m
341 341 "#{$1}..."
342 342 else
343 343 string
344 344 end
345 345 end
346 346
347 347 def anchor(text)
348 348 text.to_s.gsub(' ', '_')
349 349 end
350 350
351 351 def html_hours(text)
352 352 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
353 353 end
354 354
355 355 def authoring(created, author, options={})
356 356 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
357 357 end
358 358
359 359 def time_tag(time)
360 360 text = distance_of_time_in_words(Time.now, time)
361 361 if @project
362 362 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
363 363 else
364 364 content_tag('acronym', text, :title => format_time(time))
365 365 end
366 366 end
367 367
368 368 def syntax_highlight_lines(name, content)
369 369 lines = []
370 370 syntax_highlight(name, content).each_line { |line| lines << line }
371 371 lines
372 372 end
373 373
374 374 def syntax_highlight(name, content)
375 375 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
376 376 end
377 377
378 378 def to_path_param(path)
379 379 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
380 380 str.blank? ? nil : str
381 381 end
382 382
383 383 def pagination_links_full(paginator, count=nil, options={})
384 384 page_param = options.delete(:page_param) || :page
385 385 per_page_links = options.delete(:per_page_links)
386 386 url_param = params.dup
387 387
388 388 html = ''
389 389 if paginator.current.previous
390 390 # \xc2\xab(utf-8) = &#171;
391 391 html << link_to_content_update(
392 392 "\xc2\xab " + l(:label_previous),
393 393 url_param.merge(page_param => paginator.current.previous)) + ' '
394 394 end
395 395
396 396 html << (pagination_links_each(paginator, options) do |n|
397 397 link_to_content_update(n.to_s, url_param.merge(page_param => n))
398 398 end || '')
399 399
400 400 if paginator.current.next
401 401 # \xc2\xbb(utf-8) = &#187;
402 402 html << ' ' + link_to_content_update(
403 403 (l(:label_next) + " \xc2\xbb"),
404 404 url_param.merge(page_param => paginator.current.next))
405 405 end
406 406
407 407 unless count.nil?
408 408 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
409 409 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
410 410 html << " | #{links}"
411 411 end
412 412 end
413 413
414 414 html.html_safe
415 415 end
416 416
417 417 def per_page_links(selected=nil, item_count=nil)
418 418 values = Setting.per_page_options_array
419 419 if item_count && values.any?
420 420 if item_count > values.first
421 421 max = values.detect {|value| value >= item_count} || item_count
422 422 else
423 423 max = item_count
424 424 end
425 425 values = values.select {|value| value <= max || value == selected}
426 426 end
427 427 if values.empty? || (values.size == 1 && values.first == selected)
428 428 return nil
429 429 end
430 430 links = values.collect do |n|
431 431 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
432 432 end
433 433 l(:label_display_per_page, links.join(', '))
434 434 end
435 435
436 436 def reorder_links(name, url, method = :post)
437 437 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
438 438 url.merge({"#{name}[move_to]" => 'highest'}),
439 439 :method => method, :title => l(:label_sort_highest)) +
440 440 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
441 441 url.merge({"#{name}[move_to]" => 'higher'}),
442 442 :method => method, :title => l(:label_sort_higher)) +
443 443 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
444 444 url.merge({"#{name}[move_to]" => 'lower'}),
445 445 :method => method, :title => l(:label_sort_lower)) +
446 446 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
447 447 url.merge({"#{name}[move_to]" => 'lowest'}),
448 448 :method => method, :title => l(:label_sort_lowest))
449 449 end
450 450
451 451 def breadcrumb(*args)
452 452 elements = args.flatten
453 453 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
454 454 end
455 455
456 456 def other_formats_links(&block)
457 457 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
458 458 yield Redmine::Views::OtherFormatsBuilder.new(self)
459 459 concat('</p>'.html_safe)
460 460 end
461 461
462 462 def page_header_title
463 463 if @project.nil? || @project.new_record?
464 464 h(Setting.app_title)
465 465 else
466 466 b = []
467 467 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
468 468 if ancestors.any?
469 469 root = ancestors.shift
470 470 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
471 471 if ancestors.size > 2
472 472 b << "\xe2\x80\xa6"
473 473 ancestors = ancestors[-2, 2]
474 474 end
475 475 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
476 476 end
477 477 b << h(@project)
478 478 b.join(" \xc2\xbb ").html_safe
479 479 end
480 480 end
481 481
482 482 def html_title(*args)
483 483 if args.empty?
484 484 title = @html_title || []
485 485 title << @project.name if @project
486 486 title << Setting.app_title unless Setting.app_title == title.last
487 487 title.select {|t| !t.blank? }.join(' - ')
488 488 else
489 489 @html_title ||= []
490 490 @html_title += args
491 491 end
492 492 end
493 493
494 494 # Returns the theme, controller name, and action as css classes for the
495 495 # HTML body.
496 496 def body_css_classes
497 497 css = []
498 498 if theme = Redmine::Themes.theme(Setting.ui_theme)
499 499 css << 'theme-' + theme.name
500 500 end
501 501
502 502 css << 'controller-' + controller_name
503 503 css << 'action-' + action_name
504 504 css.join(' ')
505 505 end
506 506
507 507 def accesskey(s)
508 508 Redmine::AccessKeys.key_for s
509 509 end
510 510
511 511 # Formats text according to system settings.
512 512 # 2 ways to call this method:
513 513 # * with a String: textilizable(text, options)
514 514 # * with an object and one of its attribute: textilizable(issue, :description, options)
515 515 def textilizable(*args)
516 516 options = args.last.is_a?(Hash) ? args.pop : {}
517 517 case args.size
518 518 when 1
519 519 obj = options[:object]
520 520 text = args.shift
521 521 when 2
522 522 obj = args.shift
523 523 attr = args.shift
524 524 text = obj.send(attr).to_s
525 525 else
526 526 raise ArgumentError, 'invalid arguments to textilizable'
527 527 end
528 528 return '' if text.blank?
529 529 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
530 530 only_path = options.delete(:only_path) == false ? false : true
531 531
532 532 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
533 533
534 534 @parsed_headings = []
535 535 @heading_anchors = {}
536 536 @current_section = 0 if options[:edit_section_links]
537 537
538 538 parse_sections(text, project, obj, attr, only_path, options)
539 539 text = parse_non_pre_blocks(text) do |text|
540 540 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
541 541 send method_name, text, project, obj, attr, only_path, options
542 542 end
543 543 end
544 544 parse_headings(text, project, obj, attr, only_path, options)
545 545
546 546 if @parsed_headings.any?
547 547 replace_toc(text, @parsed_headings)
548 548 end
549 549
550 550 text.html_safe
551 551 end
552 552
553 553 def parse_non_pre_blocks(text)
554 554 s = StringScanner.new(text)
555 555 tags = []
556 556 parsed = ''
557 557 while !s.eos?
558 558 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
559 559 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
560 560 if tags.empty?
561 561 yield text
562 562 end
563 563 parsed << text
564 564 if tag
565 565 if closing
566 566 if tags.last == tag.downcase
567 567 tags.pop
568 568 end
569 569 else
570 570 tags << tag.downcase
571 571 end
572 572 parsed << full_tag
573 573 end
574 574 end
575 575 # Close any non closing tags
576 576 while tag = tags.pop
577 577 parsed << "</#{tag}>"
578 578 end
579 579 parsed
580 580 end
581 581
582 582 def parse_inline_attachments(text, project, obj, attr, only_path, options)
583 583 # when using an image link, try to use an attachment, if possible
584 584 if options[:attachments] || (obj && obj.respond_to?(:attachments))
585 585 attachments = options[:attachments] || obj.attachments
586 586 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
587 587 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
588 588 # search for the picture in attachments
589 589 if found = Attachment.latest_attach(attachments, filename)
590 590 image_url = url_for :only_path => only_path, :controller => 'attachments',
591 591 :action => 'download', :id => found
592 592 desc = found.description.to_s.gsub('"', '')
593 593 if !desc.blank? && alttext.blank?
594 594 alt = " title=\"#{desc}\" alt=\"#{desc}\""
595 595 end
596 596 "src=\"#{image_url}\"#{alt}"
597 597 else
598 598 m
599 599 end
600 600 end
601 601 end
602 602 end
603 603
604 604 # Wiki links
605 605 #
606 606 # Examples:
607 607 # [[mypage]]
608 608 # [[mypage|mytext]]
609 609 # wiki links can refer other project wikis, using project name or identifier:
610 610 # [[project:]] -> wiki starting page
611 611 # [[project:|mytext]]
612 612 # [[project:mypage]]
613 613 # [[project:mypage|mytext]]
614 614 def parse_wiki_links(text, project, obj, attr, only_path, options)
615 615 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
616 616 link_project = project
617 617 esc, all, page, title = $1, $2, $3, $5
618 618 if esc.nil?
619 619 if page =~ /^([^\:]+)\:(.*)$/
620 620 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
621 621 page = $2
622 622 title ||= $1 if page.blank?
623 623 end
624 624
625 625 if link_project && link_project.wiki
626 626 # extract anchor
627 627 anchor = nil
628 628 if page =~ /^(.+?)\#(.+)$/
629 629 page, anchor = $1, $2
630 630 end
631 631 anchor = sanitize_anchor_name(anchor) if anchor.present?
632 632 # check if page exists
633 633 wiki_page = link_project.wiki.find_page(page)
634 634 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
635 635 "##{anchor}"
636 636 else
637 637 case options[:wiki_links]
638 638 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
639 639 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
640 640 else
641 641 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
642 642 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
643 643 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
644 644 :id => wiki_page_id, :anchor => anchor, :parent => parent)
645 645 end
646 646 end
647 647 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
648 648 else
649 649 # project or wiki doesn't exist
650 650 all
651 651 end
652 652 else
653 653 all
654 654 end
655 655 end
656 656 end
657 657
658 658 # Redmine links
659 659 #
660 660 # Examples:
661 661 # Issues:
662 662 # #52 -> Link to issue #52
663 663 # Changesets:
664 664 # r52 -> Link to revision 52
665 665 # commit:a85130f -> Link to scmid starting with a85130f
666 666 # Documents:
667 667 # document#17 -> Link to document with id 17
668 668 # document:Greetings -> Link to the document with title "Greetings"
669 669 # document:"Some document" -> Link to the document with title "Some document"
670 670 # Versions:
671 671 # version#3 -> Link to version with id 3
672 672 # version:1.0.0 -> Link to version named "1.0.0"
673 673 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
674 674 # Attachments:
675 675 # attachment:file.zip -> Link to the attachment of the current object named file.zip
676 676 # Source files:
677 677 # source:some/file -> Link to the file located at /some/file in the project's repository
678 678 # source:some/file@52 -> Link to the file's revision 52
679 679 # source:some/file#L120 -> Link to line 120 of the file
680 680 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
681 681 # export:some/file -> Force the download of the file
682 682 # Forum messages:
683 683 # message#1218 -> Link to message with id 1218
684 684 #
685 685 # Links can refer other objects from other projects, using project identifier:
686 686 # identifier:r52
687 687 # identifier:document:"Some document"
688 688 # identifier:version:1.0.0
689 689 # identifier:source:some/file
690 690 def parse_redmine_links(text, project, obj, attr, only_path, options)
691 691 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|
692 692 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
693 693 link = nil
694 694 if project_identifier
695 695 project = Project.visible.find_by_identifier(project_identifier)
696 696 end
697 697 if esc.nil?
698 698 if prefix.nil? && sep == 'r'
699 699 if project
700 700 repository = nil
701 701 if repo_identifier
702 702 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
703 703 else
704 704 repository = project.repository
705 705 end
706 706 # project.changesets.visible raises an SQL error because of a double join on repositories
707 707 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
708 708 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},
709 709 :class => 'changeset',
710 710 :title => truncate_single_line(changeset.comments, :length => 100))
711 711 end
712 712 end
713 713 elsif sep == '#'
714 714 oid = identifier.to_i
715 715 case prefix
716 716 when nil
717 717 if issue = Issue.visible.find_by_id(oid, :include => :status)
718 718 anchor = comment_id ? "note-#{comment_id}" : nil
719 719 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
720 720 :class => issue.css_classes,
721 721 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
722 722 end
723 723 when 'document'
724 724 if document = Document.visible.find_by_id(oid)
725 725 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
726 726 :class => 'document'
727 727 end
728 728 when 'version'
729 729 if version = Version.visible.find_by_id(oid)
730 730 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
731 731 :class => 'version'
732 732 end
733 733 when 'message'
734 734 if message = Message.visible.find_by_id(oid, :include => :parent)
735 735 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
736 736 end
737 737 when 'forum'
738 738 if board = Board.visible.find_by_id(oid)
739 739 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
740 740 :class => 'board'
741 741 end
742 742 when 'news'
743 743 if news = News.visible.find_by_id(oid)
744 744 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
745 745 :class => 'news'
746 746 end
747 747 when 'project'
748 748 if p = Project.visible.find_by_id(oid)
749 749 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
750 750 end
751 751 end
752 752 elsif sep == ':'
753 753 # removes the double quotes if any
754 754 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
755 755 case prefix
756 756 when 'document'
757 757 if project && document = project.documents.visible.find_by_title(name)
758 758 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
759 759 :class => 'document'
760 760 end
761 761 when 'version'
762 762 if project && version = project.versions.visible.find_by_name(name)
763 763 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
764 764 :class => 'version'
765 765 end
766 766 when 'forum'
767 767 if project && board = project.boards.visible.find_by_name(name)
768 768 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
769 769 :class => 'board'
770 770 end
771 771 when 'news'
772 772 if project && news = project.news.visible.find_by_title(name)
773 773 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
774 774 :class => 'news'
775 775 end
776 776 when 'commit', 'source', 'export'
777 777 if project
778 778 repository = nil
779 779 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
780 780 repo_prefix, repo_identifier, name = $1, $2, $3
781 781 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
782 782 else
783 783 repository = project.repository
784 784 end
785 785 if prefix == 'commit'
786 786 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
787 787 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},
788 788 :class => 'changeset',
789 789 :title => truncate_single_line(h(changeset.comments), :length => 100)
790 790 end
791 791 else
792 792 if repository && User.current.allowed_to?(:browse_repository, project)
793 793 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
794 794 path, rev, anchor = $1, $3, $5
795 795 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
796 796 :path => to_path_param(path),
797 797 :rev => rev,
798 798 :anchor => anchor,
799 799 :format => (prefix == 'export' ? 'raw' : nil)},
800 800 :class => (prefix == 'export' ? 'source download' : 'source')
801 801 end
802 802 end
803 803 repo_prefix = nil
804 804 end
805 805 when 'attachment'
806 806 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
807 807 if attachments && attachment = attachments.detect {|a| a.filename == name }
808 808 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
809 809 :class => 'attachment'
810 810 end
811 811 when 'project'
812 812 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
813 813 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
814 814 end
815 815 end
816 816 end
817 817 end
818 818 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
819 819 end
820 820 end
821 821
822 822 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
823 823
824 824 def parse_sections(text, project, obj, attr, only_path, options)
825 825 return unless options[:edit_section_links]
826 826 text.gsub!(HEADING_RE) do
827 827 heading = $1
828 828 @current_section += 1
829 829 if @current_section > 1
830 830 content_tag('div',
831 831 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
832 832 :class => 'contextual',
833 833 :title => l(:button_edit_section)) + heading.html_safe
834 834 else
835 835 heading
836 836 end
837 837 end
838 838 end
839 839
840 840 # Headings and TOC
841 841 # Adds ids and links to headings unless options[:headings] is set to false
842 842 def parse_headings(text, project, obj, attr, only_path, options)
843 843 return if options[:headings] == false
844 844
845 845 text.gsub!(HEADING_RE) do
846 846 level, attrs, content = $2.to_i, $3, $4
847 847 item = strip_tags(content).strip
848 848 anchor = sanitize_anchor_name(item)
849 849 # used for single-file wiki export
850 850 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
851 851 @heading_anchors[anchor] ||= 0
852 852 idx = (@heading_anchors[anchor] += 1)
853 853 if idx > 1
854 854 anchor = "#{anchor}-#{idx}"
855 855 end
856 856 @parsed_headings << [level, anchor, item]
857 857 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
858 858 end
859 859 end
860 860
861 861 MACROS_RE = /
862 862 (!)? # escaping
863 863 (
864 864 \{\{ # opening tag
865 865 ([\w]+) # macro name
866 866 (\(([^\}]*)\))? # optional arguments
867 867 \}\} # closing tag
868 868 )
869 869 /x unless const_defined?(:MACROS_RE)
870 870
871 871 # Macros substitution
872 872 def parse_macros(text, project, obj, attr, only_path, options)
873 873 text.gsub!(MACROS_RE) do
874 874 esc, all, macro = $1, $2, $3.downcase
875 875 args = ($5 || '').split(',').each(&:strip)
876 876 if esc.nil?
877 877 begin
878 878 exec_macro(macro, obj, args)
879 879 rescue => e
880 880 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
881 881 end || all
882 882 else
883 883 all
884 884 end
885 885 end
886 886 end
887 887
888 888 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
889 889
890 890 # Renders the TOC with given headings
891 891 def replace_toc(text, headings)
892 892 text.gsub!(TOC_RE) do
893 893 if headings.empty?
894 894 ''
895 895 else
896 896 div_class = 'toc'
897 897 div_class << ' right' if $1 == '>'
898 898 div_class << ' left' if $1 == '<'
899 899 out = "<ul class=\"#{div_class}\"><li>"
900 900 root = headings.map(&:first).min
901 901 current = root
902 902 started = false
903 903 headings.each do |level, anchor, item|
904 904 if level > current
905 905 out << '<ul><li>' * (level - current)
906 906 elsif level < current
907 907 out << "</li></ul>\n" * (current - level) + "</li><li>"
908 908 elsif started
909 909 out << '</li><li>'
910 910 end
911 911 out << "<a href=\"##{anchor}\">#{item}</a>"
912 912 current = level
913 913 started = true
914 914 end
915 915 out << '</li></ul>' * (current - root)
916 916 out << '</li></ul>'
917 917 end
918 918 end
919 919 end
920 920
921 921 # Same as Rails' simple_format helper without using paragraphs
922 922 def simple_format_without_paragraph(text)
923 923 text.to_s.
924 924 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
925 925 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
926 926 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
927 927 html_safe
928 928 end
929 929
930 930 def lang_options_for_select(blank=true)
931 931 (blank ? [["(auto)", ""]] : []) +
932 932 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
933 933 end
934 934
935 935 def label_tag_for(name, option_tags = nil, options = {})
936 936 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
937 937 content_tag("label", label_text)
938 938 end
939 939
940 940 def labelled_tabular_form_for(*args, &proc)
941 941 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
942 942 args << {} unless args.last.is_a?(Hash)
943 943 options = args.last
944 944 options[:html] ||= {}
945 945 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
946 946 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
947 947 form_for(*args, &proc)
948 948 end
949 949
950 950 def labelled_form_for(*args, &proc)
951 951 args << {} unless args.last.is_a?(Hash)
952 952 options = args.last
953 953 if args.first.is_a?(Symbol)
954 954 options.merge!(:as => args.shift)
955 955 end
956 956 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
957 957 form_for(*args, &proc)
958 958 end
959 959
960 960 def labelled_fields_for(*args, &proc)
961 961 args << {} unless args.last.is_a?(Hash)
962 962 options = args.last
963 963 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
964 964 fields_for(*args, &proc)
965 965 end
966 966
967 967 def labelled_remote_form_for(*args, &proc)
968 968 args << {} unless args.last.is_a?(Hash)
969 969 options = args.last
970 970 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
971 971 form_for(*args, &proc)
972 972 end
973 973
974 974 def error_messages_for(*objects)
975 975 html = ""
976 976 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
977 977 errors = objects.map {|o| o.errors.full_messages}.flatten
978 978 if errors.any?
979 979 html << "<div id='errorExplanation'><ul>\n"
980 980 errors.each do |error|
981 981 html << "<li>#{h error}</li>\n"
982 982 end
983 983 html << "</ul></div>\n"
984 984 end
985 985 html.html_safe
986 986 end
987 987
988 988 def delete_link(url, options={})
989 989 options = {
990 990 :method => :delete,
991 991 :data => {:confirm => l(:text_are_you_sure)},
992 992 :class => 'icon icon-del'
993 993 }.merge(options)
994 994
995 995 link_to l(:button_delete), url, options
996 996 end
997 997
998 def preview_link(url, form, target='preview', options={})
999 content_tag 'a', l(:label_preview), {
1000 :href => "#",
1001 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1002 :accesskey => accesskey(:preview)
1003 }.merge(options)
1004 end
1005
998 1006 def back_url_hidden_field_tag
999 1007 back_url = params[:back_url] || request.env['HTTP_REFERER']
1000 1008 back_url = CGI.unescape(back_url.to_s)
1001 1009 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
1002 1010 end
1003 1011
1004 1012 def check_all_links(form_name)
1005 1013 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1006 1014 " | ".html_safe +
1007 1015 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1008 1016 end
1009 1017
1010 1018 def progress_bar(pcts, options={})
1011 1019 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1012 1020 pcts = pcts.collect(&:round)
1013 1021 pcts[1] = pcts[1] - pcts[0]
1014 1022 pcts << (100 - pcts[1] - pcts[0])
1015 1023 width = options[:width] || '100px;'
1016 1024 legend = options[:legend] || ''
1017 1025 content_tag('table',
1018 1026 content_tag('tr',
1019 1027 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1020 1028 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1021 1029 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1022 1030 ), :class => 'progress', :style => "width: #{width};").html_safe +
1023 1031 content_tag('p', legend, :class => 'pourcent').html_safe
1024 1032 end
1025 1033
1026 1034 def checked_image(checked=true)
1027 1035 if checked
1028 1036 image_tag 'toggle_check.png'
1029 1037 end
1030 1038 end
1031 1039
1032 1040 def context_menu(url)
1033 1041 unless @context_menu_included
1034 1042 content_for :header_tags do
1035 1043 javascript_include_tag('context_menu') +
1036 1044 stylesheet_link_tag('context_menu')
1037 1045 end
1038 1046 if l(:direction) == 'rtl'
1039 1047 content_for :header_tags do
1040 1048 stylesheet_link_tag('context_menu_rtl')
1041 1049 end
1042 1050 end
1043 1051 @context_menu_included = true
1044 1052 end
1045 1053 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1046 1054 end
1047 1055
1048 1056 def calendar_for(field_id)
1049 1057 include_calendar_headers_tags
1050 1058 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1051 1059 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1052 1060 end
1053 1061
1054 1062 def include_calendar_headers_tags
1055 1063 unless @calendar_headers_tags_included
1056 1064 @calendar_headers_tags_included = true
1057 1065 content_for :header_tags do
1058 1066 start_of_week = case Setting.start_of_week.to_i
1059 1067 when 1
1060 1068 'Calendar._FD = 1;' # Monday
1061 1069 when 7
1062 1070 'Calendar._FD = 0;' # Sunday
1063 1071 when 6
1064 1072 'Calendar._FD = 6;' # Saturday
1065 1073 else
1066 1074 '' # use language
1067 1075 end
1068 1076
1069 1077 javascript_include_tag('calendar/calendar') +
1070 1078 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1071 1079 javascript_tag(start_of_week) +
1072 1080 javascript_include_tag('calendar/calendar-setup') +
1073 1081 stylesheet_link_tag('calendar')
1074 1082 end
1075 1083 end
1076 1084 end
1077 1085
1078 1086 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1079 1087 # Examples:
1080 1088 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1081 1089 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1082 1090 #
1083 1091 def stylesheet_link_tag(*sources)
1084 1092 options = sources.last.is_a?(Hash) ? sources.pop : {}
1085 1093 plugin = options.delete(:plugin)
1086 1094 sources = sources.map do |source|
1087 1095 if plugin
1088 1096 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1089 1097 elsif current_theme && current_theme.stylesheets.include?(source)
1090 1098 current_theme.stylesheet_path(source)
1091 1099 else
1092 1100 source
1093 1101 end
1094 1102 end
1095 1103 super sources, options
1096 1104 end
1097 1105
1098 1106 # Overrides Rails' image_tag with themes and plugins support.
1099 1107 # Examples:
1100 1108 # image_tag('image.png') # => picks image.png from the current theme or defaults
1101 1109 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1102 1110 #
1103 1111 def image_tag(source, options={})
1104 1112 if plugin = options.delete(:plugin)
1105 1113 source = "/plugin_assets/#{plugin}/images/#{source}"
1106 1114 elsif current_theme && current_theme.images.include?(source)
1107 1115 source = current_theme.image_path(source)
1108 1116 end
1109 1117 super source, options
1110 1118 end
1111 1119
1112 1120 # Overrides Rails' javascript_include_tag with plugins support
1113 1121 # Examples:
1114 1122 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1115 1123 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1116 1124 #
1117 1125 def javascript_include_tag(*sources)
1118 1126 options = sources.last.is_a?(Hash) ? sources.pop : {}
1119 1127 if plugin = options.delete(:plugin)
1120 1128 sources = sources.map do |source|
1121 1129 if plugin
1122 1130 "/plugin_assets/#{plugin}/javascripts/#{source}"
1123 1131 else
1124 1132 source
1125 1133 end
1126 1134 end
1127 1135 end
1128 1136 super sources, options
1129 1137 end
1130 1138
1131 1139 def content_for(name, content = nil, &block)
1132 1140 @has_content ||= {}
1133 1141 @has_content[name] = true
1134 1142 super(name, content, &block)
1135 1143 end
1136 1144
1137 1145 def has_content?(name)
1138 1146 (@has_content && @has_content[name]) || false
1139 1147 end
1140 1148
1141 1149 def sidebar_content?
1142 1150 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1143 1151 end
1144 1152
1145 1153 def view_layouts_base_sidebar_hook_response
1146 1154 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1147 1155 end
1148 1156
1149 1157 def email_delivery_enabled?
1150 1158 !!ActionMailer::Base.perform_deliveries
1151 1159 end
1152 1160
1153 1161 # Returns the avatar image tag for the given +user+ if avatars are enabled
1154 1162 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1155 1163 def avatar(user, options = { })
1156 1164 if Setting.gravatar_enabled?
1157 1165 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1158 1166 email = nil
1159 1167 if user.respond_to?(:mail)
1160 1168 email = user.mail
1161 1169 elsif user.to_s =~ %r{<(.+?)>}
1162 1170 email = $1
1163 1171 end
1164 1172 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1165 1173 else
1166 1174 ''
1167 1175 end
1168 1176 end
1169 1177
1170 1178 def sanitize_anchor_name(anchor)
1171 1179 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1172 1180 end
1173 1181
1174 1182 # Returns the javascript tags that are included in the html layout head
1175 1183 def javascript_heads
1176 1184 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1177 1185 unless User.current.pref.warn_on_leaving_unsaved == '0'
1178 1186 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1179 1187 end
1180 1188 tags
1181 1189 end
1182 1190
1183 1191 def favicon
1184 1192 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1185 1193 end
1186 1194
1187 1195 def robot_exclusion_tag
1188 1196 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1189 1197 end
1190 1198
1191 1199 # Returns true if arg is expected in the API response
1192 1200 def include_in_api_response?(arg)
1193 1201 unless @included_in_api_response
1194 1202 param = params[:include]
1195 1203 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1196 1204 @included_in_api_response.collect!(&:strip)
1197 1205 end
1198 1206 @included_in_api_response.include?(arg.to_s)
1199 1207 end
1200 1208
1201 1209 # Returns options or nil if nometa param or X-Redmine-Nometa header
1202 1210 # was set in the request
1203 1211 def api_meta(options)
1204 1212 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1205 1213 # compatibility mode for activeresource clients that raise
1206 1214 # an error when unserializing an array with attributes
1207 1215 nil
1208 1216 else
1209 1217 options
1210 1218 end
1211 1219 end
1212 1220
1213 1221 private
1214 1222
1215 1223 def wiki_helper
1216 1224 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1217 1225 extend helper
1218 1226 return self
1219 1227 end
1220 1228
1221 1229 def link_to_content_update(text, url_params = {}, html_options = {})
1222 1230 link_to(text, url_params, html_options)
1223 1231 end
1224 1232 end
@@ -1,73 +1,67
1 1 <%= breadcrumb link_to(l(:label_board_plural), project_boards_path(@project)) %>
2 2
3 3 <div class="contextual">
4 4 <%= link_to_if_authorized l(:label_message_new),
5 5 {:controller => 'messages', :action => 'new', :board_id => @board},
6 6 :class => 'icon icon-add',
7 7 :onclick => 'Element.show("add-message"); Form.Element.focus("message_subject"); return false;' %>
8 8 <%= watcher_tag(@board, User.current) %>
9 9 </div>
10 10
11 11 <div id="add-message" style="display:none;">
12 12 <% if authorize_for('messages', 'new') %>
13 13 <h2><%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> &#187; <%= l(:label_message_new) %></h2>
14 14 <%= form_for @message, :url => {:controller => 'messages', :action => 'new', :board_id => @board}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
15 15 <%= render :partial => 'messages/form', :locals => {:f => f} %>
16 16 <p><%= submit_tag l(:button_create) %>
17 <%= link_to_remote l(:label_preview),
18 { :url => { :controller => 'messages', :action => 'preview', :board_id => @board },
19 :method => 'post',
20 :update => 'preview',
21 :with => "Form.serialize('message-form')",
22 :complete => "Element.scrollTo('preview')"
23 }, :accesskey => accesskey(:preview) %> |
17 <%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %> |
24 18 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-message")' %></p>
25 19 <% end %>
26 20 <div id="preview" class="wiki"></div>
27 21 <% end %>
28 22 </div>
29 23
30 24 <h2><%=h @board.name %></h2>
31 25 <p class="subtitle"><%=h @board.description %></p>
32 26
33 27 <% if @topics.any? %>
34 28 <table class="list messages">
35 29 <thead><tr>
36 30 <th><%= l(:field_subject) %></th>
37 31 <th><%= l(:field_author) %></th>
38 32 <%= sort_header_tag('created_on', :caption => l(:field_created_on)) %>
39 33 <%= sort_header_tag('replies', :caption => l(:label_reply_plural)) %>
40 34 <%= sort_header_tag('updated_on', :caption => l(:label_message_last)) %>
41 35 </tr></thead>
42 36 <tbody>
43 37 <% @topics.each do |topic| %>
44 38 <tr class="message <%= cycle 'odd', 'even' %> <%= topic.sticky? ? 'sticky' : '' %> <%= topic.locked? ? 'locked' : '' %>">
45 39 <td class="subject"><%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => topic } %></td>
46 40 <td class="author" align="center"><%= link_to_user(topic.author) %></td>
47 41 <td class="created_on" align="center"><%= format_time(topic.created_on) %></td>
48 42 <td class="replies" align="center"><%= topic.replies_count %></td>
49 43 <td class="last_message">
50 44 <% if topic.last_reply %>
51 45 <%= authoring topic.last_reply.created_on, topic.last_reply.author %><br />
52 46 <%= link_to_message topic.last_reply %>
53 47 <% end %>
54 48 </td>
55 49 </tr>
56 50 <% end %>
57 51 </tbody>
58 52 </table>
59 53 <p class="pagination"><%= pagination_links_full @topic_pages, @topic_count %></p>
60 54 <% else %>
61 55 <p class="nodata"><%= l(:label_no_data) %></p>
62 56 <% end %>
63 57
64 58 <% other_formats_links do |f| %>
65 59 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
66 60 <% end %>
67 61
68 62 <% html_title @board.name %>
69 63
70 64 <% content_for :header_tags do %>
71 65 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
72 66 <%= stylesheet_link_tag 'scm' %>
73 67 <% end %>
@@ -1,50 +1,44
1 1 <%= labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
2 2 <%= error_messages_for 'issue', 'time_entry' %>
3 3 <%= render :partial => 'conflict' if @conflict %>
4 4 <div class="box">
5 5 <% if @edit_allowed || !@allowed_statuses.empty? %>
6 6 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
7 7 <div id="all_attributes">
8 8 <%= render :partial => 'form', :locals => {:f => f} %>
9 9 </div>
10 10 </fieldset>
11 11 <% end %>
12 12 <% if User.current.allowed_to?(:log_time, @project) %>
13 13 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
14 14 <%= labelled_fields_for :time_entry, @time_entry do |time_entry| %>
15 15 <div class="splitcontentleft">
16 16 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
17 17 </div>
18 18 <div class="splitcontentright">
19 19 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
20 20 </div>
21 21 <p><%= time_entry.text_field :comments, :size => 60 %></p>
22 22 <% @time_entry.custom_field_values.each do |value| %>
23 23 <p><%= custom_field_tag_with_label :time_entry, value %></p>
24 24 <% end %>
25 25 <% end %>
26 26 </fieldset>
27 27 <% end %>
28 28
29 29 <fieldset><legend><%= l(:field_notes) %></legend>
30 30 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
31 31 <%= wikitoolbar_for 'notes' %>
32 32 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
33 33
34 34 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
35 35 </fieldset>
36 36 </div>
37 37
38 38 <%= f.hidden_field :lock_version %>
39 39 <%= hidden_field_tag 'last_journal_id', params[:last_journal_id] || @issue.last_journal_id %>
40 40 <%= submit_tag l(:button_submit) %>
41 <%= link_to_remote l(:label_preview),
42 { :url => preview_edit_issue_path(:project_id => @project, :id => @issue),
43 :method => 'post',
44 :update => 'preview',
45 :with => 'Form.serialize("issue-form")',
46 :complete => "Element.scrollTo('preview')"
47 }, :accesskey => accesskey(:preview) %>
41 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @issue), 'issue-form' %>
48 42 <% end %>
49 43
50 44 <div id="preview" class="wiki"></div>
@@ -1,55 +1,49
1 1 <h2><%=l(:label_issue_new)%></h2>
2 2
3 3 <%= call_hook(:view_issues_new_top, {:issue => @issue}) %>
4 4
5 5 <%= labelled_form_for @issue, :url => project_issues_path(@project),
6 6 :html => {:id => 'issue-form', :multipart => true} do |f| %>
7 7 <%= error_messages_for 'issue' %>
8 8 <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
9 9 <div class="box tabular">
10 10 <div id="all_attributes">
11 11 <%= render :partial => 'issues/form', :locals => {:f => f} %>
12 12 </div>
13 13
14 14 <% if @copy_from && @copy_from.attachments.any? %>
15 15 <p>
16 16 <label for="copy_attachments"><%= l(:label_copy_attachments) %></label>
17 17 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
18 18 </p>
19 19 <% end %>
20 20
21 21 <p id="attachments_form"><%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
22 22
23 23 <% if @issue.safe_attribute? 'watcher_user_ids' -%>
24 24 <p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
25 25 <span id="watchers_inputs">
26 26 <%= watchers_checkboxes(@issue, @available_watchers) %>
27 27 </span>
28 28 <span class="search_for_watchers">
29 29 <%= link_to_remote l(:label_search_for_watchers),
30 30 :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project},
31 31 :method => 'get' %>
32 32 </span>
33 33 </p>
34 34 <% end %>
35 35 </div>
36 36
37 37 <%= submit_tag l(:button_create) %>
38 38 <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
39 <%= link_to_remote l(:label_preview),
40 { :url => preview_new_issue_path(:project_id => @project),
41 :method => 'post',
42 :update => 'preview',
43 :with => "Form.serialize('issue-form')",
44 :complete => "Element.scrollTo('preview')"
45 }, :accesskey => accesskey(:preview) %>
39 <%= preview_link preview_new_issue_path(:project_id => @project), 'issue-form' %>
46 40
47 41 <%= javascript_tag "Form.Element.focus('issue_subject');" %>
48 42 <% end %>
49 43
50 44 <div id="preview" class="wiki"></div>
51 45
52 46 <% content_for :header_tags do %>
53 47 <%= stylesheet_link_tag 'scm' %>
54 48 <%= robot_exclusion_tag %>
55 49 <% end %>
@@ -1,22 +1,17
1 1 <%= form_remote_tag(:url => {}, :html => { :id => "journal-#{@journal.id}-form" }) do %>
2 2 <%= label_tag "notes", l(:description_notes), :class => "hidden-for-sighted" %>
3 3 <%= text_area_tag :notes, @journal.notes,
4 4 :id => "journal_#{@journal.id}_notes",
5 5 :class => 'wiki-edit',
6 6 :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min) %>
7 7 <%= call_hook(:view_journals_notes_form_after_notes, { :journal => @journal}) %>
8 8 <p><%= submit_tag l(:button_save) %>
9 <%= link_to_remote l(:label_preview),
10 { :url => preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
11 :method => 'post',
12 :update => "journal_#{@journal.id}_preview",
13 :with => "Form.serialize('journal-#{@journal.id}-form')",
14 :complete => "Element.scrollTo('journal_#{@journal.id}_preview')"
15 }, :accesskey => accesskey(:preview) %>
16 |
9 <%= preview_link preview_edit_issue_path(:project_id => @project, :id => @journal.issue),
10 "journal-#{@journal.id}-form",
11 "journal_#{@journal.id}_preview" %> |
17 12 <%= link_to l(:button_cancel), '#', :onclick => "Element.remove('journal-#{@journal.id}-form'); " +
18 13 "Element.show('journal-#{@journal.id}-notes'); return false;" %></p>
19 14
20 15 <div id="journal_<%= @journal.id %>_preview" class="wiki"></div>
21 16 <% end %>
22 17 <%= wikitoolbar_for "journal_#{@journal.id}_notes" %>
@@ -1,28 +1,21
1 1 <h2><%= link_to h(@board.name), :controller => 'boards',
2 2 :action => 'show', :project_id => @project,
3 3 :id => @board %> &#187; <%= h @message.subject %></h2>
4 4
5 5 <%= form_for @message, {
6 6 :as => :message,
7 7 :url => {:action => 'edit'},
8 8 :html => {:multipart => true,
9 9 :id => 'message-form',
10 10 :method => :post}
11 11 } do |f| %>
12 12 <%= render :partial => 'form',
13 13 :locals => {:f => f, :replying => !@message.parent.nil?} %>
14 14 <%= submit_tag l(:button_save) %>
15 <%= link_to_remote l(:label_preview),
16 { :url => { :controller => 'messages',
17 :action => 'preview', :board_id => @board, :id => @message },
18 :method => 'post',
19 :update => 'preview',
20 :with => "Form.serialize('message-form')",
21 :complete => "Element.scrollTo('preview')"
22 }, :accesskey => accesskey(:preview) %>
15 <%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board, :id => @message}, 'message-form') %>
23 16 <% end %>
24 17 <div id="preview" class="wiki"></div>
25 18
26 19 <% content_for :header_tags do %>
27 20 <%= stylesheet_link_tag 'scm' %>
28 21 <% end %>
@@ -1,15 +1,9
1 1 <h2><%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> &#187; <%= l(:label_message_new) %></h2>
2 2
3 3 <%= form_for @message, :url => {:action => 'new'}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
4 4 <%= render :partial => 'form', :locals => {:f => f} %>
5 5 <%= submit_tag l(:button_create) %>
6 <%= link_to_remote l(:label_preview),
7 { :url => { :controller => 'messages', :action => 'preview', :board_id => @board },
8 :method => 'post',
9 :update => 'preview',
10 :with => "Form.serialize('message-form')",
11 :complete => "Element.scrollTo('preview')"
12 }, :accesskey => accesskey(:preview) %>
6 <%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %>
13 7 <% end %>
14 8
15 9 <div id="preview" class="wiki"></div>
@@ -1,94 +1,88
1 1 <%= breadcrumb link_to(l(:label_board_plural), project_boards_path(@project)),
2 2 link_to(h(@board.name), project_board_path(@project, @board)) %>
3 3
4 4 <div class="contextual">
5 5 <%= watcher_tag(@topic, User.current) %>
6 6 <%= link_to_remote_if_authorized(
7 7 l(:button_quote),
8 8 { :url => {:action => 'quote', :id => @topic} },
9 9 :class => 'icon icon-comment'
10 10 ) unless @topic.locked? %>
11 11 <%= link_to(
12 12 l(:button_edit),
13 13 {:action => 'edit', :id => @topic},
14 14 :class => 'icon icon-edit'
15 15 ) if @message.editable_by?(User.current) %>
16 16 <%= link_to(
17 17 l(:button_delete),
18 18 {:action => 'destroy', :id => @topic},
19 19 :method => :post,
20 20 :data => {:confirm => l(:text_are_you_sure)},
21 21 :class => 'icon icon-del'
22 22 ) if @message.destroyable_by?(User.current) %>
23 23 </div>
24 24
25 25 <h2><%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %></h2>
26 26
27 27 <div class="message">
28 28 <p><span class="author"><%= authoring @topic.created_on, @topic.author %></span></p>
29 29 <div class="wiki">
30 30 <%= textilizable(@topic, :content) %>
31 31 </div>
32 32 <%= link_to_attachments @topic, :author => false %>
33 33 </div>
34 34 <br />
35 35
36 36 <% unless @replies.empty? %>
37 37 <h3 class="comments"><%= l(:label_reply_plural) %> (<%= @reply_count %>)</h3>
38 38 <% @replies.each do |message| %>
39 39 <div class="message reply" id="<%= "message-#{message.id}" %>">
40 40 <div class="contextual">
41 41 <%= link_to_remote_if_authorized(
42 42 image_tag('comment.png'),
43 43 { :url => {:action => 'quote', :id => message} },
44 44 :title => l(:button_quote)
45 45 ) unless @topic.locked? %>
46 46 <%= link_to(
47 47 image_tag('edit.png'),
48 48 {:action => 'edit', :id => message},
49 49 :title => l(:button_edit)
50 50 ) if message.editable_by?(User.current) %>
51 51 <%= link_to(
52 52 image_tag('delete.png'),
53 53 {:action => 'destroy', :id => message},
54 54 :method => :post,
55 55 :data => {:confirm => l(:text_are_you_sure)},
56 56 :title => l(:button_delete)
57 57 ) if message.destroyable_by?(User.current) %>
58 58 </div>
59 59 <h4>
60 60 <%= avatar(message.author, :size => "24") %>
61 61 <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :r => message, :anchor => "message-#{message.id}" } %>
62 62 -
63 63 <%= authoring message.created_on, message.author %>
64 64 </h4>
65 65 <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div>
66 66 <%= link_to_attachments message, :author => false %>
67 67 </div>
68 68 <% end %>
69 69 <p class="pagination"><%= pagination_links_full @reply_pages, @reply_count, :per_page_links => false %></p>
70 70 <% end %>
71 71
72 72 <% if !@topic.locked? && authorize_for('messages', 'reply') %>
73 73 <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p>
74 74 <div id="reply" style="display:none;">
75 75 <%= form_for @reply, :as => :reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
76 76 <%= render :partial => 'form', :locals => {:f => f, :replying => true} %>
77 77 <%= submit_tag l(:button_submit) %>
78 <%= link_to_remote l(:label_preview),
79 { :url => { :controller => 'messages', :action => 'preview', :board_id => @board },
80 :method => 'post',
81 :update => 'preview',
82 :with => "Form.serialize('message-form')",
83 :complete => "Element.scrollTo('preview')"
84 }, :accesskey => accesskey(:preview) %>
78 <%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %>
85 79 <% end %>
86 80 <div id="preview" class="wiki"></div>
87 81 </div>
88 82 <% end %>
89 83
90 84 <% content_for :header_tags do %>
91 85 <%= stylesheet_link_tag 'scm' %>
92 86 <% end %>
93 87
94 88 <% html_title @topic.subject %>
@@ -1,17 +1,12
1 1 <h2><%=l(:label_news)%></h2>
2 2
3 3 <%= labelled_form_for @news, :html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
4 4 <%= render :partial => 'form', :locals => { :f => f } %>
5 5 <%= submit_tag l(:button_save) %>
6 <%= link_to_remote l(:label_preview),
7 { :url => preview_news_path(:project_id => @project),
8 :method => 'get',
9 :update => 'preview',
10 :with => "Form.serialize('news-form')"
11 }, :accesskey => accesskey(:preview) %>
6 <%= preview_link preview_news_path(:project_id => @project), 'news-form' %>
12 7 <% end %>
13 8 <div id="preview" class="wiki"></div>
14 9
15 10 <% content_for :header_tags do %>
16 11 <%= stylesheet_link_tag 'scm' %>
17 12 <% end %>
@@ -1,51 +1,46
1 1 <div class="contextual">
2 2 <%= link_to(l(:label_news_new),
3 3 new_project_news_path(@project),
4 4 :class => 'icon icon-add',
5 5 :onclick => 'Element.show("add-news"); Form.Element.focus("news_title"); return false;') if @project && User.current.allowed_to?(:manage_news, @project) %>
6 6 </div>
7 7
8 8 <div id="add-news" style="display:none;">
9 9 <h2><%=l(:label_news_new)%></h2>
10 10 <%= labelled_form_for @news, :url => project_news_index_path(@project),
11 11 :html => { :id => 'news-form', :multipart => true } do |f| %>
12 12 <%= render :partial => 'news/form', :locals => { :f => f } %>
13 13 <%= submit_tag l(:button_create) %>
14 <%= link_to_remote l(:label_preview),
15 { :url => preview_news_path(:project_id => @project),
16 :method => 'get',
17 :update => 'preview',
18 :with => "Form.serialize('news-form')"
19 }, :accesskey => accesskey(:preview) %> |
14 <%= preview_link preview_news_path(:project_id => @project), 'news-form' %> |
20 15 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("add-news")' %>
21 16 <% end if @project %>
22 17 <div id="preview" class="wiki"></div>
23 18 </div>
24 19
25 20 <h2><%=l(:label_news_plural)%></h2>
26 21
27 22 <% if @newss.empty? %>
28 23 <p class="nodata"><%= l(:label_no_data) %></p>
29 24 <% else %>
30 25 <% @newss.each do |news| %>
31 26 <h3><%= avatar(news.author, :size => "24") %><%= link_to_project(news.project) + ': ' unless news.project == @project %>
32 27 <%= link_to h(news.title), news_path(news) %>
33 28 <%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></h3>
34 29 <p class="author"><%= authoring news.created_on, news.author %></p>
35 30 <div class="wiki">
36 31 <%= textilizable(news, :description) %>
37 32 </div>
38 33 <% end %>
39 34 <% end %>
40 35 <p class="pagination"><%= pagination_links_full @news_pages %></p>
41 36
42 37 <% other_formats_links do |f| %>
43 38 <%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
44 39 <% end %>
45 40
46 41 <% content_for :header_tags do %>
47 42 <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
48 43 <%= stylesheet_link_tag 'scm' %>
49 44 <% end %>
50 45
51 46 <% html_title(l(:label_news_plural)) -%>
@@ -1,14 +1,9
1 1 <h2><%=l(:label_news_new)%></h2>
2 2
3 3 <%= labelled_form_for @news, :url => project_news_index_path(@project),
4 4 :html => { :id => 'news-form', :multipart => true } do |f| %>
5 5 <%= render :partial => 'news/form', :locals => { :f => f } %>
6 6 <%= submit_tag l(:button_create) %>
7 <%= link_to_remote l(:label_preview),
8 { :url => preview_news_path(:project_id => @project),
9 :method => 'get',
10 :update => 'preview',
11 :with => "Form.serialize('news-form')"
12 }, :accesskey => accesskey(:preview) %>
7 <%= preview_link preview_news_path(:project_id => @project), 'news-form' %>
13 8 <% end %>
14 9 <div id="preview" class="wiki"></div>
@@ -1,67 +1,62
1 1 <div class="contextual">
2 2 <%= watcher_tag(@news, User.current) %>
3 3 <%= link_to(l(:button_edit),
4 4 edit_news_path(@news),
5 5 :class => 'icon icon-edit',
6 6 :accesskey => accesskey(:edit),
7 7 :onclick => 'Element.show("edit-news"); return false;') if User.current.allowed_to?(:manage_news, @project) %>
8 8 <%= delete_link news_path(@news) if User.current.allowed_to?(:manage_news, @project) %>
9 9 </div>
10 10
11 11 <h2><%= avatar(@news.author, :size => "24") %><%=h @news.title %></h2>
12 12
13 13 <% if authorize_for('news', 'edit') %>
14 14 <div id="edit-news" style="display:none;">
15 15 <%= labelled_form_for :news, @news, :url => news_path(@news),
16 16 :html => { :id => 'news-form', :multipart => true, :method => :put } do |f| %>
17 17 <%= render :partial => 'form', :locals => { :f => f } %>
18 18 <%= submit_tag l(:button_save) %>
19 <%= link_to_remote l(:label_preview),
20 { :url => preview_news_path(:project_id => @project),
21 :method => 'get',
22 :update => 'preview',
23 :with => "Form.serialize('news-form')"
24 }, :accesskey => accesskey(:preview) %> |
19 <%= preview_link preview_news_path(:project_id => @project), 'news-form' %> |
25 20 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("edit-news"); return false;' %>
26 21 <% end %>
27 22 <div id="preview" class="wiki"></div>
28 23 </div>
29 24 <% end %>
30 25
31 26 <p><% unless @news.summary.blank? %><em><%=h @news.summary %></em><br /><% end %>
32 27 <span class="author"><%= authoring @news.created_on, @news.author %></span></p>
33 28 <div class="wiki">
34 29 <%= textilizable(@news, :description) %>
35 30 </div>
36 31 <%= link_to_attachments @news %>
37 32 <br />
38 33
39 34 <div id="comments" style="margin-bottom:16px;">
40 35 <h3 class="comments"><%= l(:label_comment_plural) %></h3>
41 36 <% @comments.each do |comment| %>
42 37 <% next if comment.new_record? %>
43 38 <div class="contextual">
44 39 <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'comments', :action => 'destroy', :id => @news, :comment_id => comment},
45 40 :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :title => l(:button_delete) %>
46 41 </div>
47 42 <h4><%= avatar(comment.author, :size => "24") %><%= authoring comment.created_on, comment.author %></h4>
48 43 <%= textilizable(comment.comments) %>
49 44 <% end if @comments.any? %>
50 45 </div>
51 46
52 47 <% if @news.commentable? %>
53 48 <p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments" %></p>
54 49 <%= form_tag({:controller => 'comments', :action => 'create', :id => @news}, :id => "add_comment_form", :style => "display:none;") do %>
55 50 <div class="box">
56 51 <%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit' %>
57 52 <%= wikitoolbar_for 'comment_comments' %>
58 53 </div>
59 54 <p><%= submit_tag l(:button_add) %></p>
60 55 <% end %>
61 56 <% end %>
62 57
63 58 <% html_title @news.title -%>
64 59
65 60 <% content_for :header_tags do %>
66 61 <%= stylesheet_link_tag 'scm' %>
67 62 <% end %>
@@ -1,54 +1,47
1 1 <%= wiki_page_breadcrumb(@page) %>
2 2
3 3 <h2><%= h @page.pretty_title %></h2>
4 4
5 5 <%= form_for @content, :as => :content,
6 6 :url => {:action => 'update', :id => @page.title},
7 7 :html => {:method => :put, :multipart => true, :id => 'wiki_form'} do |f| %>
8 8 <%= f.hidden_field :version %>
9 9 <% if @section %>
10 10 <%= hidden_field_tag 'section', @section %>
11 11 <%= hidden_field_tag 'section_hash', @section_hash %>
12 12 <% end %>
13 13 <%= error_messages_for 'content' %>
14 14
15 15 <div class="box tabular">
16 16 <%= text_area_tag 'content[text]', @text, :cols => 100, :rows => 25,
17 17 :class => 'wiki-edit', :accesskey => accesskey(:edit) %>
18 18
19 19 <% if @page.safe_attribute_names.include?('parent_id') && @wiki.pages.any? %>
20 20 <%= fields_for @page do |fp| %>
21 21 <p>
22 22 <label><%= l(:field_parent_title) %></label>
23 23 <%= fp.select :parent_id,
24 24 content_tag('option', '', :value => '') +
25 25 wiki_page_options_for_select(@wiki.pages.all(:include => :parent) -
26 26 @page.self_and_descendants, @page.parent) %>
27 27 </p>
28 28 <% end %>
29 29 <% end %>
30 30
31 31 <p><label><%= l(:field_comments) %></label><%= f.text_field :comments, :size => 120 %></p>
32 32 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
33 33 </div>
34 34
35 35 <p><%= submit_tag l(:button_save) %>
36 <%= link_to_remote l(:label_preview),
37 { :url => { :controller => 'wiki', :action => 'preview',
38 :project_id => @project, :id => @page.title },
39 :method => :post,
40 :update => 'preview',
41 :with => "Form.serialize('wiki_form')",
42 :complete => "Element.scrollTo('preview')"
43 }, :accesskey => accesskey(:preview) %></p>
36 <%= preview_link({:controller => 'wiki', :action => 'preview', :project_id => @project, :id => @page.title }, 'wiki_form') %></p>
44 37 <%= wikitoolbar_for 'content_text' %>
45 38 <% end %>
46 39
47 40 <div id="preview" class="wiki"></div>
48 41
49 42 <% content_for :header_tags do %>
50 43 <%= stylesheet_link_tag 'scm' %>
51 44 <%= robot_exclusion_tag %>
52 45 <% end %>
53 46
54 47 <% html_title @page.pretty_title %>
@@ -1,538 +1,548
1 1 /* Redmine - project management software
2 2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3 3
4 4 function checkAll (id, checked) {
5 5 var els = Element.descendants(id);
6 6 for (var i = 0; i < els.length; i++) {
7 7 if (els[i].disabled==false) {
8 8 els[i].checked = checked;
9 9 }
10 10 }
11 11 }
12 12
13 13 function toggleCheckboxesBySelector(selector) {
14 14 boxes = $$(selector);
15 15 var all_checked = true;
16 16 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
17 17 for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; }
18 18 }
19 19
20 20 function setCheckboxesBySelector(checked, selector) {
21 21 var boxes = $$(selector);
22 22 boxes.each(function(ele) {
23 23 ele.checked = checked;
24 24 });
25 25 }
26 26
27 27 function showAndScrollTo(id, focus) {
28 28 Element.show(id);
29 29 if (focus!=null) { Form.Element.focus(focus); }
30 30 Element.scrollTo(id);
31 31 }
32 32
33 33 function toggleRowGroup(el) {
34 34 var tr = Element.up(el, 'tr');
35 35 var n = Element.next(tr);
36 36 tr.toggleClassName('open');
37 37 while (n != undefined && !n.hasClassName('group')) {
38 38 Element.toggle(n);
39 39 n = Element.next(n);
40 40 }
41 41 }
42 42
43 43 function collapseAllRowGroups(el) {
44 44 var tbody = Element.up(el, 'tbody');
45 45 tbody.childElements('tr').each(function(tr) {
46 46 if (tr.hasClassName('group')) {
47 47 tr.removeClassName('open');
48 48 } else {
49 49 tr.hide();
50 50 }
51 51 })
52 52 }
53 53
54 54 function expandAllRowGroups(el) {
55 55 var tbody = Element.up(el, 'tbody');
56 56 tbody.childElements('tr').each(function(tr) {
57 57 if (tr.hasClassName('group')) {
58 58 tr.addClassName('open');
59 59 } else {
60 60 tr.show();
61 61 }
62 62 })
63 63 }
64 64
65 65 function toggleAllRowGroups(el) {
66 66 var tr = Element.up(el, 'tr');
67 67 if (tr.hasClassName('open')) {
68 68 collapseAllRowGroups(el);
69 69 } else {
70 70 expandAllRowGroups(el);
71 71 }
72 72 }
73 73
74 74 function toggleFieldset(el) {
75 75 var fieldset = Element.up(el, 'fieldset');
76 76 fieldset.toggleClassName('collapsed');
77 77 Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2});
78 78 }
79 79
80 80 function hideFieldset(el) {
81 81 var fieldset = Element.up(el, 'fieldset');
82 82 fieldset.toggleClassName('collapsed');
83 83 fieldset.down('div').hide();
84 84 }
85 85
86 86 function add_filter() {
87 87 select = $('add_filter_select');
88 88 field = select.value
89 89 Element.show('tr_' + field);
90 90 check_box = $('cb_' + field);
91 91 check_box.checked = true;
92 92 toggle_filter(field);
93 93 select.selectedIndex = 0;
94 94
95 95 for (i=0; i<select.options.length; i++) {
96 96 if (select.options[i].value == field) {
97 97 select.options[i].disabled = true;
98 98 }
99 99 }
100 100 }
101 101
102 102 function toggle_filter(field) {
103 103 check_box = $('cb_' + field);
104 104 if (check_box.checked) {
105 105 Element.show("operators_" + field);
106 106 Form.Element.enable("operators_" + field);
107 107 toggle_operator(field);
108 108 } else {
109 109 Element.hide("operators_" + field);
110 110 Form.Element.disable("operators_" + field);
111 111 enableValues(field, []);
112 112 }
113 113 }
114 114
115 115 function enableValues(field, indexes) {
116 116 var f = $$(".values_" + field);
117 117 for(var i=0;i<f.length;i++) {
118 118 if (indexes.include(i)) {
119 119 Form.Element.enable(f[i]);
120 120 f[i].up('span').show();
121 121 } else {
122 122 f[i].value = '';
123 123 Form.Element.disable(f[i]);
124 124 f[i].up('span').hide();
125 125 }
126 126 }
127 127 if (indexes.length > 0) {
128 128 Element.show("div_values_" + field);
129 129 } else {
130 130 Element.hide("div_values_" + field);
131 131 }
132 132 }
133 133
134 134 function toggle_operator(field) {
135 135 operator = $("operators_" + field);
136 136 switch (operator.value) {
137 137 case "!*":
138 138 case "*":
139 139 case "t":
140 140 case "w":
141 141 case "o":
142 142 case "c":
143 143 enableValues(field, []);
144 144 break;
145 145 case "><":
146 146 enableValues(field, [0,1]);
147 147 break;
148 148 case "<t+":
149 149 case ">t+":
150 150 case "t+":
151 151 case ">t-":
152 152 case "<t-":
153 153 case "t-":
154 154 enableValues(field, [2]);
155 155 break;
156 156 default:
157 157 enableValues(field, [0]);
158 158 break;
159 159 }
160 160 }
161 161
162 162 function toggle_multi_select(el) {
163 163 var select = $(el);
164 164 if (select.multiple == true) {
165 165 select.multiple = false;
166 166 } else {
167 167 select.multiple = true;
168 168 }
169 169 }
170 170
171 171 function submit_query_form(id) {
172 172 selectAllOptions("selected_columns");
173 173 $(id).submit();
174 174 }
175 175
176 176 function apply_filters_observer() {
177 177 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
178 178 if(e.keyCode == Event.KEY_RETURN) {
179 179 submit_query_form("query_form");
180 180 }
181 181 });
182 182 }
183 183
184 184 var fileFieldCount = 1;
185 185
186 186 function addFileField() {
187 187 var fields = $('attachments_fields');
188 188 if (fields.childElements().length >= 10) return false;
189 189 fileFieldCount++;
190 190 var s = new Element('span');
191 191 s.update(fields.down('span').innerHTML);
192 192 s.down('input.file').name = "attachments[" + fileFieldCount + "][file]";
193 193 s.down('input.description').name = "attachments[" + fileFieldCount + "][description]";
194 194 fields.appendChild(s);
195 195 }
196 196
197 197 function removeFileField(el) {
198 198 var fields = $('attachments_fields');
199 199 var s = Element.up(el, 'span');
200 200 if (fields.childElements().length > 1) {
201 201 s.remove();
202 202 } else {
203 203 s.update(s.innerHTML);
204 204 }
205 205 }
206 206
207 207 function checkFileSize(el, maxSize, message) {
208 208 var files = el.files;
209 209 if (files) {
210 210 for (var i=0; i<files.length; i++) {
211 211 if (files[i].size > maxSize) {
212 212 alert(message);
213 213 el.value = "";
214 214 }
215 215 }
216 216 }
217 217 }
218 218
219 219 function showTab(name) {
220 220 var f = $$('div#content .tab-content');
221 221 for(var i=0; i<f.length; i++){
222 222 Element.hide(f[i]);
223 223 }
224 224 var f = $$('div.tabs a');
225 225 for(var i=0; i<f.length; i++){
226 226 Element.removeClassName(f[i], "selected");
227 227 }
228 228 Element.show('tab-content-' + name);
229 229 Element.addClassName('tab-' + name, "selected");
230 230 return false;
231 231 }
232 232
233 233 function moveTabRight(el) {
234 234 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
235 235 var tabsWidth = 0;
236 236 var i;
237 237 for (i=0; i<lis.length; i++) {
238 238 if (lis[i].visible()) {
239 239 tabsWidth += lis[i].getWidth() + 6;
240 240 }
241 241 }
242 242 if (tabsWidth < Element.up(el, 'div.tabs').getWidth() - 60) {
243 243 return;
244 244 }
245 245 i=0;
246 246 while (i<lis.length && !lis[i].visible()) {
247 247 i++;
248 248 }
249 249 lis[i].hide();
250 250 }
251 251
252 252 function moveTabLeft(el) {
253 253 var lis = Element.up(el, 'div.tabs').down('ul').childElements();
254 254 var i = 0;
255 255 while (i<lis.length && !lis[i].visible()) {
256 256 i++;
257 257 }
258 258 if (i>0) {
259 259 lis[i-1].show();
260 260 }
261 261 }
262 262
263 263 function displayTabsButtons() {
264 264 var lis;
265 265 var tabsWidth = 0;
266 266 var i;
267 267 $$('div.tabs').each(function(el) {
268 268 lis = el.down('ul').childElements();
269 269 for (i=0; i<lis.length; i++) {
270 270 if (lis[i].visible()) {
271 271 tabsWidth += lis[i].getWidth() + 6;
272 272 }
273 273 }
274 274 if ((tabsWidth < el.getWidth() - 60) && (lis[0].visible())) {
275 275 el.down('div.tabs-buttons').hide();
276 276 } else {
277 277 el.down('div.tabs-buttons').show();
278 278 }
279 279 });
280 280 }
281 281
282 282 function setPredecessorFieldsVisibility() {
283 283 relationType = $('relation_relation_type');
284 284 if (relationType && (relationType.value == "precedes" || relationType.value == "follows")) {
285 285 Element.show('predecessor_fields');
286 286 } else {
287 287 Element.hide('predecessor_fields');
288 288 }
289 289 }
290 290
291 291 function promptToRemote(text, param, url) {
292 292 value = prompt(text + ':');
293 293 if (value) {
294 294 new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
295 295 return false;
296 296 }
297 297 }
298 298
299 299 function showModal(id, width) {
300 300 el = $(id);
301 301 if (el == undefined || el.visible()) {return;}
302 302 var h = $$('body')[0].getHeight();
303 303 var d = document.createElement("div");
304 304 d.id = 'modalbg';
305 305 $('main').appendChild(d);
306 306 $('modalbg').setStyle({ width: '100%', height: h + 'px' });
307 307 $('modalbg').show();
308 308
309 309 var pageWidth = document.viewport.getWidth();
310 310 if (width) {
311 311 el.setStyle({'width': width});
312 312 }
313 313 el.setStyle({'left': (((pageWidth - el.getWidth())/2 *100) / pageWidth) + '%'});
314 314 el.addClassName('modal');
315 315 el.show();
316 316
317 317 if (el.down("input[type=text]")) {
318 318 el.down("input[type=text]").focus();
319 319 } else if (el.down("input[type=submit]")) {
320 320 el.down("input[type=submit]").focus();
321 321 }
322 322 }
323 323
324 324 function hideModal(el) {
325 325 var modal;
326 326 if (el) {
327 327 modal = Element.up(el, 'div.modal');
328 328 } else {
329 329 modal = $('ajax-modal');
330 330 }
331 331 if (modal) {
332 332 modal.hide();
333 333 }
334 334 var bg = $('modalbg');
335 335 if (bg) {
336 336 bg.remove();
337 337 }
338 338 }
339 339
340 function submitPreview(url, form, target) {
341 new Ajax.Updater(target, url, {
342 asynchronous:true,
343 evalScripts:true,
344 method:'post',
345 onComplete:function(request){Element.scrollTo(target)},
346 parameters:Form.serialize(form)
347 });
348 }
349
340 350 function collapseScmEntry(id) {
341 351 var els = document.getElementsByClassName(id, 'browser');
342 352 for (var i = 0; i < els.length; i++) {
343 353 if (els[i].hasClassName('open')) {
344 354 collapseScmEntry(els[i].id);
345 355 }
346 356 Element.hide(els[i]);
347 357 }
348 358 $(id).removeClassName('open');
349 359 }
350 360
351 361 function expandScmEntry(id) {
352 362 var els = document.getElementsByClassName(id, 'browser');
353 363 for (var i = 0; i < els.length; i++) {
354 364 Element.show(els[i]);
355 365 if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
356 366 expandScmEntry(els[i].id);
357 367 }
358 368 }
359 369 $(id).addClassName('open');
360 370 }
361 371
362 372 function scmEntryClick(id) {
363 373 el = $(id);
364 374 if (el.hasClassName('open')) {
365 375 collapseScmEntry(id);
366 376 el.addClassName('collapsed');
367 377 return false;
368 378 } else if (el.hasClassName('loaded')) {
369 379 expandScmEntry(id);
370 380 el.removeClassName('collapsed');
371 381 return false;
372 382 }
373 383 if (el.hasClassName('loading')) {
374 384 return false;
375 385 }
376 386 el.addClassName('loading');
377 387 return true;
378 388 }
379 389
380 390 function scmEntryLoaded(id) {
381 391 Element.addClassName(id, 'open');
382 392 Element.addClassName(id, 'loaded');
383 393 Element.removeClassName(id, 'loading');
384 394 }
385 395
386 396 function randomKey(size) {
387 397 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
388 398 var key = '';
389 399 for (i = 0; i < size; i++) {
390 400 key += chars[Math.floor(Math.random() * chars.length)];
391 401 }
392 402 return key;
393 403 }
394 404
395 405 function observeParentIssueField(url) {
396 406 new Ajax.Autocompleter('issue_parent_issue_id',
397 407 'parent_issue_candidates',
398 408 url,
399 409 { minChars: 3,
400 410 frequency: 0.5,
401 411 paramName: 'q',
402 412 method: 'get',
403 413 updateElement: function(value) {
404 414 document.getElementById('issue_parent_issue_id').value = value.id;
405 415 }});
406 416 }
407 417
408 418 function observeRelatedIssueField(url) {
409 419 new Ajax.Autocompleter('relation_issue_to_id',
410 420 'related_issue_candidates',
411 421 url,
412 422 { minChars: 3,
413 423 frequency: 0.5,
414 424 paramName: 'q',
415 425 method: 'get',
416 426 updateElement: function(value) {
417 427 document.getElementById('relation_issue_to_id').value = value.id;
418 428 },
419 429 parameters: 'scope=all'
420 430 });
421 431 }
422 432
423 433 function setVisible(id, visible) {
424 434 var el = $(id);
425 435 if (el) {if (visible) {el.show();} else {el.hide();}}
426 436 }
427 437
428 438 function observeProjectModules() {
429 439 var f = function() {
430 440 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
431 441 var c = ($('project_enabled_module_names_issue_tracking').checked == true);
432 442 setVisible('project_trackers', c);
433 443 setVisible('project_issue_custom_fields', c);
434 444 };
435 445
436 446 Event.observe(window, 'load', f);
437 447 Event.observe('project_enabled_module_names_issue_tracking', 'change', f);
438 448 }
439 449
440 450 /*
441 451 * Class used to warn user when leaving a page with unsaved textarea
442 452 * Author: mathias.fischer@berlinonline.de
443 453 */
444 454
445 455 var WarnLeavingUnsaved = Class.create({
446 456 observedForms: false,
447 457 observedElements: false,
448 458 changedForms: false,
449 459 message: null,
450 460
451 461 initialize: function(message){
452 462 this.observedForms = $$('form');
453 463 this.observedElements = $$('textarea');
454 464 this.message = message;
455 465
456 466 this.observedElements.each(this.observeChange.bind(this));
457 467 this.observedForms.each(this.submitAction.bind(this));
458 468
459 469 window.onbeforeunload = this.unload.bind(this);
460 470 },
461 471
462 472 unload: function(){
463 473 this.observedElements.each(function(el) {el.blur();})
464 474 if(this.changedForms)
465 475 return this.message;
466 476 },
467 477
468 478 setChanged: function(){
469 479 this.changedForms = true;
470 480 },
471 481
472 482 setUnchanged: function(){
473 483 this.changedForms = false;
474 484 },
475 485
476 486 observeChange: function(element){
477 487 element.observe('change',this.setChanged.bindAsEventListener(this));
478 488 },
479 489
480 490 submitAction: function(element){
481 491 element.observe('submit',this.setUnchanged.bindAsEventListener(this));
482 492 }
483 493 });
484 494
485 495 /*
486 496 * 1 - registers a callback which copies the csrf token into the
487 497 * X-CSRF-Token header with each ajax request. Necessary to
488 498 * work with rails applications which have fixed
489 499 * CVE-2011-0447
490 500 * 2 - shows and hides ajax indicator
491 501 */
492 502 Ajax.Responders.register({
493 503 onCreate: function(request){
494 504 var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
495 505
496 506 if (csrf_meta_tag) {
497 507 var header = 'X-CSRF-Token',
498 508 token = csrf_meta_tag.readAttribute('content');
499 509
500 510 if (!request.options.requestHeaders) {
501 511 request.options.requestHeaders = {};
502 512 }
503 513 request.options.requestHeaders[header] = token;
504 514 }
505 515
506 516 if ($('ajax-indicator') && Ajax.activeRequestCount > 0 && $$('input.ajax-loading').size() == 0) {
507 517 Element.show('ajax-indicator');
508 518 }
509 519 },
510 520 onComplete: function(){
511 521 if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
512 522 Element.hide('ajax-indicator');
513 523 }
514 524 }
515 525 });
516 526
517 527 function hideOnLoad() {
518 528 $$('.hol').each(function(el) {
519 529 el.hide();
520 530 });
521 531 }
522 532
523 533 function addFormObserversForDoubleSubmit() {
524 534 $$('form[method=post]').each(function(form) {
525 535 if (!form.hasClassName('multiple-submit')) {
526 536 form.on('submit', function(form_submission) {
527 537 if (form.getStorage().get('submitted')) {
528 538 form_submission.stop();
529 539 } else {
530 540 form.getStorage().set('submitted', true);
531 541 }
532 542 });
533 543 }
534 544 });
535 545 }
536 546
537 547 Event.observe(window, 'load', hideOnLoad);
538 548 Event.observe(window, 'load', addFormObserversForDoubleSubmit);
General Comments 0
You need to be logged in to leave comments. Login now