##// END OF EJS Templates
Makes issue update link work without javascript (#1337)....
Jean-Philippe Lang -
r1591:47064c02f144
parent child
Show More
@@ -1,525 +1,525
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'coderay'
19 19 require 'coderay/helpers/file_type'
20 20
21 21 module ApplicationHelper
22 22 include Redmine::WikiFormatting::Macros::Definitions
23 23
24 24 def current_role
25 25 @current_role ||= User.current.role_for_project(@project)
26 26 end
27 27
28 28 # Return true if user is authorized for controller/action, otherwise false
29 29 def authorize_for(controller, action)
30 30 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 31 end
32 32
33 33 # Display a link if user is authorized
34 34 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
35 35 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
36 36 end
37 37
38 38 # Display a link to user's account page
39 39 def link_to_user(user)
40 40 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
41 41 end
42 42
43 43 def link_to_issue(issue, options={})
44 44 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
45 45 end
46 46
47 47 def toggle_link(name, id, options={})
48 48 onclick = "Element.toggle('#{id}'); "
49 49 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
50 50 onclick << "return false;"
51 51 link_to(name, "#", :onclick => onclick)
52 52 end
53 53
54 def show_and_goto_link(name, id, options={})
54 def show_and_goto_js(id, options={})
55 55 onclick = "Element.show('#{id}'); "
56 56 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
57 57 onclick << "Element.scrollTo('#{id}'); "
58 58 onclick << "return false;"
59 59 link_to(name, "#", options.merge(:onclick => onclick))
60 60 end
61 61
62 62 def image_to_function(name, function, html_options = {})
63 63 html_options.symbolize_keys!
64 64 tag(:input, html_options.merge({
65 65 :type => "image", :src => image_path(name),
66 66 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
67 67 }))
68 68 end
69 69
70 70 def prompt_to_remote(name, text, param, url, html_options = {})
71 71 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
72 72 link_to name, {}, html_options
73 73 end
74 74
75 75 def format_date(date)
76 76 return nil unless date
77 77 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
78 78 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
79 79 date.strftime(@date_format)
80 80 end
81 81
82 82 def format_time(time, include_date = true)
83 83 return nil unless time
84 84 time = time.to_time if time.is_a?(String)
85 85 zone = User.current.time_zone
86 86 if time.utc?
87 87 local = zone ? zone.adjust(time) : time.getlocal
88 88 else
89 89 local = zone ? zone.adjust(time.getutc) : time
90 90 end
91 91 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
92 92 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
93 93 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
94 94 end
95 95
96 96 # Truncates and returns the string as a single line
97 97 def truncate_single_line(string, *args)
98 98 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
99 99 end
100 100
101 101 def html_hours(text)
102 102 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
103 103 end
104 104
105 105 def authoring(created, author)
106 106 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
107 107 l(:label_added_time_by, author || 'Anonymous', time_tag)
108 108 end
109 109
110 110 def l_or_humanize(s)
111 111 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
112 112 end
113 113
114 114 def day_name(day)
115 115 l(:general_day_names).split(',')[day-1]
116 116 end
117 117
118 118 def month_name(month)
119 119 l(:actionview_datehelper_select_month_names).split(',')[month-1]
120 120 end
121 121
122 122 def syntax_highlight(name, content)
123 123 type = CodeRay::FileType[name]
124 124 type ? CodeRay.scan(content, type).html : h(content)
125 125 end
126 126
127 127 def pagination_links_full(paginator, count=nil, options={})
128 128 page_param = options.delete(:page_param) || :page
129 129 url_param = params.dup
130 130 # don't reuse params if filters are present
131 131 url_param.clear if url_param.has_key?(:set_filter)
132 132
133 133 html = ''
134 134 html << link_to_remote(('&#171; ' + l(:label_previous)),
135 135 {:update => 'content',
136 136 :url => url_param.merge(page_param => paginator.current.previous),
137 137 :complete => 'window.scrollTo(0,0)'},
138 138 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
139 139
140 140 html << (pagination_links_each(paginator, options) do |n|
141 141 link_to_remote(n.to_s,
142 142 {:url => {:params => url_param.merge(page_param => n)},
143 143 :update => 'content',
144 144 :complete => 'window.scrollTo(0,0)'},
145 145 {:href => url_for(:params => url_param.merge(page_param => n))})
146 146 end || '')
147 147
148 148 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
149 149 {:update => 'content',
150 150 :url => url_param.merge(page_param => paginator.current.next),
151 151 :complete => 'window.scrollTo(0,0)'},
152 152 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
153 153
154 154 unless count.nil?
155 155 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
156 156 end
157 157
158 158 html
159 159 end
160 160
161 161 def per_page_links(selected=nil)
162 162 url_param = params.dup
163 163 url_param.clear if url_param.has_key?(:set_filter)
164 164
165 165 links = Setting.per_page_options_array.collect do |n|
166 166 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
167 167 {:href => url_for(url_param.merge(:per_page => n))})
168 168 end
169 169 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
170 170 end
171 171
172 172 def breadcrumb(*args)
173 173 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
174 174 end
175 175
176 176 def html_title(*args)
177 177 if args.empty?
178 178 title = []
179 179 title << @project.name if @project
180 180 title += @html_title if @html_title
181 181 title << Setting.app_title
182 182 title.compact.join(' - ')
183 183 else
184 184 @html_title ||= []
185 185 @html_title += args
186 186 end
187 187 end
188 188
189 189 def accesskey(s)
190 190 Redmine::AccessKeys.key_for s
191 191 end
192 192
193 193 # Formats text according to system settings.
194 194 # 2 ways to call this method:
195 195 # * with a String: textilizable(text, options)
196 196 # * with an object and one of its attribute: textilizable(issue, :description, options)
197 197 def textilizable(*args)
198 198 options = args.last.is_a?(Hash) ? args.pop : {}
199 199 case args.size
200 200 when 1
201 201 obj = nil
202 202 text = args.shift
203 203 when 2
204 204 obj = args.shift
205 205 text = obj.send(args.shift).to_s
206 206 else
207 207 raise ArgumentError, 'invalid arguments to textilizable'
208 208 end
209 209 return '' if text.blank?
210 210
211 211 only_path = options.delete(:only_path) == false ? false : true
212 212
213 213 # when using an image link, try to use an attachment, if possible
214 214 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
215 215
216 216 if attachments
217 217 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
218 218 style = $1
219 219 filename = $6
220 220 rf = Regexp.new(filename, Regexp::IGNORECASE)
221 221 # search for the picture in attachments
222 222 if found = attachments.detect { |att| att.filename =~ rf }
223 223 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
224 224 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
225 225 alt = desc.blank? ? nil : "(#{desc})"
226 226 "!#{style}#{image_url}#{alt}!"
227 227 else
228 228 "!#{style}#{filename}!"
229 229 end
230 230 end
231 231 end
232 232
233 233 text = (Setting.text_formatting == 'textile') ?
234 234 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
235 235 simple_format(auto_link(h(text)))
236 236
237 237 # different methods for formatting wiki links
238 238 case options[:wiki_links]
239 239 when :local
240 240 # used for local links to html files
241 241 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
242 242 when :anchor
243 243 # used for single-file wiki export
244 244 format_wiki_link = Proc.new {|project, title| "##{title}" }
245 245 else
246 246 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
247 247 end
248 248
249 249 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
250 250
251 251 # Wiki links
252 252 #
253 253 # Examples:
254 254 # [[mypage]]
255 255 # [[mypage|mytext]]
256 256 # wiki links can refer other project wikis, using project name or identifier:
257 257 # [[project:]] -> wiki starting page
258 258 # [[project:|mytext]]
259 259 # [[project:mypage]]
260 260 # [[project:mypage|mytext]]
261 261 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
262 262 link_project = project
263 263 esc, all, page, title = $1, $2, $3, $5
264 264 if esc.nil?
265 265 if page =~ /^([^\:]+)\:(.*)$/
266 266 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
267 267 page = $2
268 268 title ||= $1 if page.blank?
269 269 end
270 270
271 271 if link_project && link_project.wiki
272 272 # check if page exists
273 273 wiki_page = link_project.wiki.find_page(page)
274 274 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
275 275 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
276 276 else
277 277 # project or wiki doesn't exist
278 278 title || page
279 279 end
280 280 else
281 281 all
282 282 end
283 283 end
284 284
285 285 # Redmine links
286 286 #
287 287 # Examples:
288 288 # Issues:
289 289 # #52 -> Link to issue #52
290 290 # Changesets:
291 291 # r52 -> Link to revision 52
292 292 # commit:a85130f -> Link to scmid starting with a85130f
293 293 # Documents:
294 294 # document#17 -> Link to document with id 17
295 295 # document:Greetings -> Link to the document with title "Greetings"
296 296 # document:"Some document" -> Link to the document with title "Some document"
297 297 # Versions:
298 298 # version#3 -> Link to version with id 3
299 299 # version:1.0.0 -> Link to version named "1.0.0"
300 300 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
301 301 # Attachments:
302 302 # attachment:file.zip -> Link to the attachment of the current object named file.zip
303 303 # Source files:
304 304 # source:some/file -> Link to the file located at /some/file in the project's repository
305 305 # source:some/file@52 -> Link to the file's revision 52
306 306 # source:some/file#L120 -> Link to line 120 of the file
307 307 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
308 308 # export:some/file -> Force the download of the file
309 309 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
310 310 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
311 311 link = nil
312 312 if esc.nil?
313 313 if prefix.nil? && sep == 'r'
314 314 if project && (changeset = project.changesets.find_by_revision(oid))
315 315 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
316 316 :class => 'changeset',
317 317 :title => truncate_single_line(changeset.comments, 100))
318 318 end
319 319 elsif sep == '#'
320 320 oid = oid.to_i
321 321 case prefix
322 322 when nil
323 323 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
324 324 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
325 325 :class => (issue.closed? ? 'issue closed' : 'issue'),
326 326 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
327 327 link = content_tag('del', link) if issue.closed?
328 328 end
329 329 when 'document'
330 330 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
331 331 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
332 332 :class => 'document'
333 333 end
334 334 when 'version'
335 335 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
336 336 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
337 337 :class => 'version'
338 338 end
339 339 end
340 340 elsif sep == ':'
341 341 # removes the double quotes if any
342 342 name = oid.gsub(%r{^"(.*)"$}, "\\1")
343 343 case prefix
344 344 when 'document'
345 345 if project && document = project.documents.find_by_title(name)
346 346 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
347 347 :class => 'document'
348 348 end
349 349 when 'version'
350 350 if project && version = project.versions.find_by_name(name)
351 351 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
352 352 :class => 'version'
353 353 end
354 354 when 'commit'
355 355 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
356 356 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
357 357 :class => 'changeset',
358 358 :title => truncate_single_line(changeset.comments, 100)
359 359 end
360 360 when 'source', 'export'
361 361 if project && project.repository
362 362 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
363 363 path, rev, anchor = $1, $3, $5
364 364 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
365 365 :rev => rev,
366 366 :anchor => anchor,
367 367 :format => (prefix == 'export' ? 'raw' : nil)},
368 368 :class => (prefix == 'export' ? 'source download' : 'source')
369 369 end
370 370 when 'attachment'
371 371 if attachments && attachment = attachments.detect {|a| a.filename == name }
372 372 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
373 373 :class => 'attachment'
374 374 end
375 375 end
376 376 end
377 377 end
378 378 leading + (link || "#{prefix}#{sep}#{oid}")
379 379 end
380 380
381 381 text
382 382 end
383 383
384 384 # Same as Rails' simple_format helper without using paragraphs
385 385 def simple_format_without_paragraph(text)
386 386 text.to_s.
387 387 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
388 388 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
389 389 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
390 390 end
391 391
392 392 def error_messages_for(object_name, options = {})
393 393 options = options.symbolize_keys
394 394 object = instance_variable_get("@#{object_name}")
395 395 if object && !object.errors.empty?
396 396 # build full_messages here with controller current language
397 397 full_messages = []
398 398 object.errors.each do |attr, msg|
399 399 next if msg.nil?
400 400 msg = msg.first if msg.is_a? Array
401 401 if attr == "base"
402 402 full_messages << l(msg)
403 403 else
404 404 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
405 405 end
406 406 end
407 407 # retrieve custom values error messages
408 408 if object.errors[:custom_values]
409 409 object.custom_values.each do |v|
410 410 v.errors.each do |attr, msg|
411 411 next if msg.nil?
412 412 msg = msg.first if msg.is_a? Array
413 413 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
414 414 end
415 415 end
416 416 end
417 417 content_tag("div",
418 418 content_tag(
419 419 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
420 420 ) +
421 421 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
422 422 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
423 423 )
424 424 else
425 425 ""
426 426 end
427 427 end
428 428
429 429 def lang_options_for_select(blank=true)
430 430 (blank ? [["(auto)", ""]] : []) +
431 431 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
432 432 end
433 433
434 434 def label_tag_for(name, option_tags = nil, options = {})
435 435 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
436 436 content_tag("label", label_text)
437 437 end
438 438
439 439 def labelled_tabular_form_for(name, object, options, &proc)
440 440 options[:html] ||= {}
441 441 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
442 442 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
443 443 end
444 444
445 445 def back_url_hidden_field_tag
446 446 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
447 447 end
448 448
449 449 def check_all_links(form_name)
450 450 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
451 451 " | " +
452 452 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
453 453 end
454 454
455 455 def progress_bar(pcts, options={})
456 456 pcts = [pcts, pcts] unless pcts.is_a?(Array)
457 457 pcts[1] = pcts[1] - pcts[0]
458 458 pcts << (100 - pcts[1] - pcts[0])
459 459 width = options[:width] || '100px;'
460 460 legend = options[:legend] || ''
461 461 content_tag('table',
462 462 content_tag('tr',
463 463 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
464 464 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
465 465 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
466 466 ), :class => 'progress', :style => "width: #{width};") +
467 467 content_tag('p', legend, :class => 'pourcent')
468 468 end
469 469
470 470 def context_menu_link(name, url, options={})
471 471 options[:class] ||= ''
472 472 if options.delete(:selected)
473 473 options[:class] << ' icon-checked disabled'
474 474 options[:disabled] = true
475 475 end
476 476 if options.delete(:disabled)
477 477 options.delete(:method)
478 478 options.delete(:confirm)
479 479 options.delete(:onclick)
480 480 options[:class] << ' disabled'
481 481 url = '#'
482 482 end
483 483 link_to name, url, options
484 484 end
485 485
486 486 def calendar_for(field_id)
487 487 include_calendar_headers_tags
488 488 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
489 489 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
490 490 end
491 491
492 492 def include_calendar_headers_tags
493 493 unless @calendar_headers_tags_included
494 494 @calendar_headers_tags_included = true
495 495 content_for :header_tags do
496 496 javascript_include_tag('calendar/calendar') +
497 497 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
498 498 javascript_include_tag('calendar/calendar-setup') +
499 499 stylesheet_link_tag('calendar')
500 500 end
501 501 end
502 502 end
503 503
504 504 def wikitoolbar_for(field_id)
505 505 return '' unless Setting.text_formatting == 'textile'
506 506
507 507 help_link = l(:setting_text_formatting) + ': ' +
508 508 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
509 509 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
510 510
511 511 javascript_include_tag('jstoolbar/jstoolbar') +
512 512 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
513 513 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
514 514 end
515 515
516 516 def content_for(name, content = nil, &block)
517 517 @has_content ||= {}
518 518 @has_content[name] = true
519 519 super(name, content, &block)
520 520 end
521 521
522 522 def has_content?(name)
523 523 (@has_content && @has_content[name]) || false
524 524 end
525 525 end
@@ -1,120 +1,120
1 1 <div class="contextual">
2 <%= show_and_goto_link(l(:button_update), 'update', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) if authorize_for('issues', 'edit') %>
2 <%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
3 3 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
4 4 <%= watcher_tag(@issue, User.current) %>
5 5 <%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
6 6 <%= link_to_if_authorized l(:button_move), {:controller => 'issues', :action => 'move', :id => @issue }, :class => 'icon icon-move' %>
7 7 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 8 </div>
9 9
10 10 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11 11
12 12 <div class="issue <%= "status-#{@issue.status.position} priority-#{@issue.priority.position}" %>">
13 13 <h3><%=h @issue.subject %></h3>
14 14 <p class="author">
15 15 <%= authoring @issue.created_on, @issue.author %>.
16 16 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) + '.' if @issue.created_on != @issue.updated_on %>
17 17 </p>
18 18
19 19 <table width="100%">
20 20 <tr>
21 21 <td style="width:15%"><b><%=l(:field_status)%>:</b></td><td style="width:35%"><%= @issue.status.name %></td>
22 22 <td style="width:15%"><b><%=l(:field_start_date)%>:</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
23 23 </tr>
24 24 <tr>
25 25 <td><b><%=l(:field_priority)%>:</b></td><td><%= @issue.priority.name %></td>
26 26 <td><b><%=l(:field_due_date)%>:</b></td><td><%= format_date(@issue.due_date) %></td>
27 27 </tr>
28 28 <tr>
29 29 <td><b><%=l(:field_assigned_to)%>:</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
30 30 <td><b><%=l(:field_done_ratio)%>:</b></td><td><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
31 31 </tr>
32 32 <tr>
33 33 <td><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 34 <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 35 <td><b><%=l(:label_spent_time)%>:</b></td>
36 36 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
37 37 <% end %>
38 38 </tr>
39 39 <tr>
40 40 <td><b><%=l(:field_fixed_version)%>:</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
41 41 <% if @issue.estimated_hours %>
42 42 <td><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 43 <% end %>
44 44 </tr>
45 45 <tr>
46 46 <% n = 0 -%>
47 47 <% @issue.custom_values.each do |value| -%>
48 48 <td valign="top"><b><%=h value.custom_field.name %>:</b></td><td valign="top"><%= simple_format(h(show_value(value))) %></td>
49 49 <% n = n + 1
50 50 if (n > 1)
51 51 n = 0 %>
52 52 </tr><tr>
53 53 <%end
54 54 end %>
55 55 </tr>
56 56 </table>
57 57 <hr />
58 58
59 59 <div class="contextual">
60 60 <%= link_to_remote(image_tag('comment.png'),
61 61 { :url => {:controller => 'issues', :action => 'reply', :id => @issue} },
62 62 :title => l(:button_reply)) if authorize_for('issues', 'edit') %>
63 63 </div>
64 64
65 65 <p><strong><%=l(:field_description)%></strong></p>
66 66 <div class="wiki">
67 67 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
68 68 </div>
69 69
70 70 <% if @issue.attachments.any? %>
71 71 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
72 72 <% end %>
73 73
74 74 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
75 75 <hr />
76 76 <div id="relations">
77 77 <%= render :partial => 'relations' %>
78 78 </div>
79 79 <% end %>
80 80
81 81 </div>
82 82
83 83 <% if @issue.changesets.any? && User.current.allowed_to?(:view_changesets, @project) %>
84 84 <div id="issue-changesets">
85 85 <h3><%=l(:label_associated_revisions)%></h3>
86 86 <%= render :partial => 'changesets', :locals => { :changesets => @issue.changesets} %>
87 87 </div>
88 88 <% end %>
89 89
90 90 <% if @journals.any? %>
91 91 <div id="history">
92 92 <h3><%=l(:label_history)%></h3>
93 93 <%= render :partial => 'history', :locals => { :journals => @journals } %>
94 94 </div>
95 95 <% end %>
96 96 <div style="clear: both;"></div>
97 97
98 98 <% if authorize_for('issues', 'edit') %>
99 99 <div id="update" style="display:none;">
100 100 <h3><%= l(:button_update) %></h3>
101 101 <%= render :partial => 'edit' %>
102 102 </div>
103 103 <% end %>
104 104
105 105 <p class="other-formats">
106 106 <%= l(:label_export_to) %>
107 107 <span><%= link_to 'Atom', {:format => 'atom', :key => User.current.rss_key}, :class => 'feed' %></span>
108 108 <span><%= link_to 'PDF', {:format => 'pdf'}, :class => 'pdf' %></span>
109 109 </p>
110 110
111 111 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
112 112
113 113 <% content_for :sidebar do %>
114 114 <%= render :partial => 'issues/sidebar' %>
115 115 <% end %>
116 116
117 117 <% content_for :header_tags do %>
118 118 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
119 119 <%= stylesheet_link_tag 'scm' %>
120 120 <% end %>
@@ -1,131 +1,137
1 1 /* redMine - project management software
2 2 Copyright (C) 2006-2008 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 function showAndScrollTo(id, focus) {
14 Element.show(id);
15 if (focus!=null) { Form.Element.focus(focus); }
16 Element.scrollTo(id);
17 }
18
13 19 var fileFieldCount = 1;
14 20
15 21 function addFileField() {
16 22 if (fileFieldCount >= 10) return false
17 23 fileFieldCount++;
18 24 var f = document.createElement("input");
19 25 f.type = "file";
20 26 f.name = "attachments[" + fileFieldCount + "][file]";
21 27 f.size = 30;
22 28 var d = document.createElement("input");
23 29 d.type = "text";
24 30 d.name = "attachments[" + fileFieldCount + "][description]";
25 31 d.size = 60;
26 32
27 33 p = document.getElementById("attachments_fields");
28 34 p.appendChild(document.createElement("br"));
29 35 p.appendChild(f);
30 36 p.appendChild(d);
31 37 }
32 38
33 39 function showTab(name) {
34 40 var f = $$('div#content .tab-content');
35 41 for(var i=0; i<f.length; i++){
36 42 Element.hide(f[i]);
37 43 }
38 44 var f = $$('div.tabs a');
39 45 for(var i=0; i<f.length; i++){
40 46 Element.removeClassName(f[i], "selected");
41 47 }
42 48 Element.show('tab-content-' + name);
43 49 Element.addClassName('tab-' + name, "selected");
44 50 return false;
45 51 }
46 52
47 53 function setPredecessorFieldsVisibility() {
48 54 relationType = $('relation_relation_type');
49 55 if (relationType && relationType.value == "precedes") {
50 56 Element.show('predecessor_fields');
51 57 } else {
52 58 Element.hide('predecessor_fields');
53 59 }
54 60 }
55 61
56 62 function promptToRemote(text, param, url) {
57 63 value = prompt(text + ':');
58 64 if (value) {
59 65 new Ajax.Request(url + '?' + param + '=' + encodeURIComponent(value), {asynchronous:true, evalScripts:true});
60 66 return false;
61 67 }
62 68 }
63 69
64 70 function collapseScmEntry(id) {
65 71 var els = document.getElementsByClassName(id, 'browser');
66 72 for (var i = 0; i < els.length; i++) {
67 73 if (els[i].hasClassName('open')) {
68 74 collapseScmEntry(els[i].id);
69 75 }
70 76 Element.hide(els[i]);
71 77 }
72 78 $(id).removeClassName('open');
73 79 }
74 80
75 81 function expandScmEntry(id) {
76 82 var els = document.getElementsByClassName(id, 'browser');
77 83 for (var i = 0; i < els.length; i++) {
78 84 Element.show(els[i]);
79 85 if (els[i].hasClassName('loaded') && !els[i].hasClassName('collapsed')) {
80 86 expandScmEntry(els[i].id);
81 87 }
82 88 }
83 89 $(id).addClassName('open');
84 90 }
85 91
86 92 function scmEntryClick(id) {
87 93 el = $(id);
88 94 if (el.hasClassName('open')) {
89 95 collapseScmEntry(id);
90 96 el.addClassName('collapsed');
91 97 return false;
92 98 } else if (el.hasClassName('loaded')) {
93 99 expandScmEntry(id);
94 100 el.removeClassName('collapsed');
95 101 return false;
96 102 }
97 103 if (el.hasClassName('loading')) {
98 104 return false;
99 105 }
100 106 el.addClassName('loading');
101 107 return true;
102 108 }
103 109
104 110 function scmEntryLoaded(id) {
105 111 Element.addClassName(id, 'open');
106 112 Element.addClassName(id, 'loaded');
107 113 Element.removeClassName(id, 'loading');
108 114 }
109 115
110 116 function randomKey(size) {
111 117 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');
112 118 var key = '';
113 119 for (i = 0; i < size; i++) {
114 120 key += chars[Math.floor(Math.random() * chars.length)];
115 121 }
116 122 return key;
117 123 }
118 124
119 125 /* shows and hides ajax indicator */
120 126 Ajax.Responders.register({
121 127 onCreate: function(){
122 128 if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
123 129 Element.show('ajax-indicator');
124 130 }
125 131 },
126 132 onComplete: function(){
127 133 if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
128 134 Element.hide('ajax-indicator');
129 135 }
130 136 }
131 137 });
General Comments 0
You need to be logged in to leave comments. Login now