##// END OF EJS Templates
Adds an helper for creating the context menu....
Jean-Philippe Lang -
r3428:bd5fe10c13b0
parent child
Show More
@@ -1,749 +1,760
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
37 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
38 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
38 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
39 end
39 end
40
40
41 # Display a link to remote if user is authorized
41 # Display a link to remote if user is authorized
42 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
42 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
43 url = options[:url] || {}
43 url = options[:url] || {}
44 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
44 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active?
51 if user.active?
52 link_to name, :controller => 'users', :action => 'show', :id => user
52 link_to name, :controller => 'users', :action => 'show', :id => user
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 # Displays a link to +issue+ with its subject.
61 # Displays a link to +issue+ with its subject.
62 # Examples:
62 # Examples:
63 #
63 #
64 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue) # => Defect #6: This is the subject
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
66 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :subject => false) # => Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
68 #
68 #
69 def link_to_issue(issue, options={})
69 def link_to_issue(issue, options={})
70 title = nil
70 title = nil
71 subject = nil
71 subject = nil
72 if options[:subject] == false
72 if options[:subject] == false
73 title = truncate(issue.subject, :length => 60)
73 title = truncate(issue.subject, :length => 60)
74 else
74 else
75 subject = issue.subject
75 subject = issue.subject
76 if options[:truncate]
76 if options[:truncate]
77 subject = truncate(subject, :length => options[:truncate])
77 subject = truncate(subject, :length => options[:truncate])
78 end
78 end
79 end
79 end
80 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
80 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
81 :class => issue.css_classes,
81 :class => issue.css_classes,
82 :title => title
82 :title => title
83 s << ": #{h subject}" if subject
83 s << ": #{h subject}" if subject
84 s = "#{h issue.project} - " + s if options[:project]
84 s = "#{h issue.project} - " + s if options[:project]
85 s
85 s
86 end
86 end
87
87
88 # Generates a link to an attachment.
88 # Generates a link to an attachment.
89 # Options:
89 # Options:
90 # * :text - Link text (default to attachment filename)
90 # * :text - Link text (default to attachment filename)
91 # * :download - Force download (default: false)
91 # * :download - Force download (default: false)
92 def link_to_attachment(attachment, options={})
92 def link_to_attachment(attachment, options={})
93 text = options.delete(:text) || attachment.filename
93 text = options.delete(:text) || attachment.filename
94 action = options.delete(:download) ? 'download' : 'show'
94 action = options.delete(:download) ? 'download' : 'show'
95
95
96 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
96 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
97 end
97 end
98
98
99 # Generates a link to a SCM revision
99 # Generates a link to a SCM revision
100 # Options:
100 # Options:
101 # * :text - Link text (default to the formatted revision)
101 # * :text - Link text (default to the formatted revision)
102 def link_to_revision(revision, project, options={})
102 def link_to_revision(revision, project, options={})
103 text = options.delete(:text) || format_revision(revision)
103 text = options.delete(:text) || format_revision(revision)
104
104
105 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
105 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
106 end
106 end
107
107
108 def toggle_link(name, id, options={})
108 def toggle_link(name, id, options={})
109 onclick = "Element.toggle('#{id}'); "
109 onclick = "Element.toggle('#{id}'); "
110 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
110 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
111 onclick << "return false;"
111 onclick << "return false;"
112 link_to(name, "#", :onclick => onclick)
112 link_to(name, "#", :onclick => onclick)
113 end
113 end
114
114
115 def image_to_function(name, function, html_options = {})
115 def image_to_function(name, function, html_options = {})
116 html_options.symbolize_keys!
116 html_options.symbolize_keys!
117 tag(:input, html_options.merge({
117 tag(:input, html_options.merge({
118 :type => "image", :src => image_path(name),
118 :type => "image", :src => image_path(name),
119 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
119 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
120 }))
120 }))
121 end
121 end
122
122
123 def prompt_to_remote(name, text, param, url, html_options = {})
123 def prompt_to_remote(name, text, param, url, html_options = {})
124 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
124 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
125 link_to name, {}, html_options
125 link_to name, {}, html_options
126 end
126 end
127
127
128 def format_activity_title(text)
128 def format_activity_title(text)
129 h(truncate_single_line(text, :length => 100))
129 h(truncate_single_line(text, :length => 100))
130 end
130 end
131
131
132 def format_activity_day(date)
132 def format_activity_day(date)
133 date == Date.today ? l(:label_today).titleize : format_date(date)
133 date == Date.today ? l(:label_today).titleize : format_date(date)
134 end
134 end
135
135
136 def format_activity_description(text)
136 def format_activity_description(text)
137 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
137 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
138 end
138 end
139
139
140 def format_version_name(version)
140 def format_version_name(version)
141 if version.project == @project
141 if version.project == @project
142 h(version)
142 h(version)
143 else
143 else
144 h("#{version.project} - #{version}")
144 h("#{version.project} - #{version}")
145 end
145 end
146 end
146 end
147
147
148 def due_date_distance_in_words(date)
148 def due_date_distance_in_words(date)
149 if date
149 if date
150 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
150 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
151 end
151 end
152 end
152 end
153
153
154 def render_page_hierarchy(pages, node=nil)
154 def render_page_hierarchy(pages, node=nil)
155 content = ''
155 content = ''
156 if pages[node]
156 if pages[node]
157 content << "<ul class=\"pages-hierarchy\">\n"
157 content << "<ul class=\"pages-hierarchy\">\n"
158 pages[node].each do |page|
158 pages[node].each do |page|
159 content << "<li>"
159 content << "<li>"
160 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
160 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
161 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
161 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
162 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
162 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
163 content << "</li>\n"
163 content << "</li>\n"
164 end
164 end
165 content << "</ul>\n"
165 content << "</ul>\n"
166 end
166 end
167 content
167 content
168 end
168 end
169
169
170 # Renders flash messages
170 # Renders flash messages
171 def render_flash_messages
171 def render_flash_messages
172 s = ''
172 s = ''
173 flash.each do |k,v|
173 flash.each do |k,v|
174 s << content_tag('div', v, :class => "flash #{k}")
174 s << content_tag('div', v, :class => "flash #{k}")
175 end
175 end
176 s
176 s
177 end
177 end
178
178
179 # Renders tabs and their content
179 # Renders tabs and their content
180 def render_tabs(tabs)
180 def render_tabs(tabs)
181 if tabs.any?
181 if tabs.any?
182 render :partial => 'common/tabs', :locals => {:tabs => tabs}
182 render :partial => 'common/tabs', :locals => {:tabs => tabs}
183 else
183 else
184 content_tag 'p', l(:label_no_data), :class => "nodata"
184 content_tag 'p', l(:label_no_data), :class => "nodata"
185 end
185 end
186 end
186 end
187
187
188 # Renders the project quick-jump box
188 # Renders the project quick-jump box
189 def render_project_jump_box
189 def render_project_jump_box
190 # Retrieve them now to avoid a COUNT query
190 # Retrieve them now to avoid a COUNT query
191 projects = User.current.projects.all
191 projects = User.current.projects.all
192 if projects.any?
192 if projects.any?
193 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
193 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
194 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
194 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
195 '<option value="" disabled="disabled">---</option>'
195 '<option value="" disabled="disabled">---</option>'
196 s << project_tree_options_for_select(projects, :selected => @project) do |p|
196 s << project_tree_options_for_select(projects, :selected => @project) do |p|
197 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
197 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
198 end
198 end
199 s << '</select>'
199 s << '</select>'
200 s
200 s
201 end
201 end
202 end
202 end
203
203
204 def project_tree_options_for_select(projects, options = {})
204 def project_tree_options_for_select(projects, options = {})
205 s = ''
205 s = ''
206 project_tree(projects) do |project, level|
206 project_tree(projects) do |project, level|
207 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
207 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
208 tag_options = {:value => project.id}
208 tag_options = {:value => project.id}
209 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
209 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
210 tag_options[:selected] = 'selected'
210 tag_options[:selected] = 'selected'
211 else
211 else
212 tag_options[:selected] = nil
212 tag_options[:selected] = nil
213 end
213 end
214 tag_options.merge!(yield(project)) if block_given?
214 tag_options.merge!(yield(project)) if block_given?
215 s << content_tag('option', name_prefix + h(project), tag_options)
215 s << content_tag('option', name_prefix + h(project), tag_options)
216 end
216 end
217 s
217 s
218 end
218 end
219
219
220 # Yields the given block for each project with its level in the tree
220 # Yields the given block for each project with its level in the tree
221 def project_tree(projects, &block)
221 def project_tree(projects, &block)
222 ancestors = []
222 ancestors = []
223 projects.sort_by(&:lft).each do |project|
223 projects.sort_by(&:lft).each do |project|
224 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
224 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
225 ancestors.pop
225 ancestors.pop
226 end
226 end
227 yield project, ancestors.size
227 yield project, ancestors.size
228 ancestors << project
228 ancestors << project
229 end
229 end
230 end
230 end
231
231
232 def project_nested_ul(projects, &block)
232 def project_nested_ul(projects, &block)
233 s = ''
233 s = ''
234 if projects.any?
234 if projects.any?
235 ancestors = []
235 ancestors = []
236 projects.sort_by(&:lft).each do |project|
236 projects.sort_by(&:lft).each do |project|
237 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
237 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
238 s << "<ul>\n"
238 s << "<ul>\n"
239 else
239 else
240 ancestors.pop
240 ancestors.pop
241 s << "</li>"
241 s << "</li>"
242 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
242 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
243 ancestors.pop
243 ancestors.pop
244 s << "</ul></li>\n"
244 s << "</ul></li>\n"
245 end
245 end
246 end
246 end
247 s << "<li>"
247 s << "<li>"
248 s << yield(project).to_s
248 s << yield(project).to_s
249 ancestors << project
249 ancestors << project
250 end
250 end
251 s << ("</li></ul>\n" * ancestors.size)
251 s << ("</li></ul>\n" * ancestors.size)
252 end
252 end
253 s
253 s
254 end
254 end
255
255
256 def principals_check_box_tags(name, principals)
256 def principals_check_box_tags(name, principals)
257 s = ''
257 s = ''
258 principals.sort.each do |principal|
258 principals.sort.each do |principal|
259 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
259 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
260 end
260 end
261 s
261 s
262 end
262 end
263
263
264 # Truncates and returns the string as a single line
264 # Truncates and returns the string as a single line
265 def truncate_single_line(string, *args)
265 def truncate_single_line(string, *args)
266 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
266 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
267 end
267 end
268
268
269 def html_hours(text)
269 def html_hours(text)
270 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
270 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
271 end
271 end
272
272
273 def authoring(created, author, options={})
273 def authoring(created, author, options={})
274 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
274 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
275 end
275 end
276
276
277 def time_tag(time)
277 def time_tag(time)
278 text = distance_of_time_in_words(Time.now, time)
278 text = distance_of_time_in_words(Time.now, time)
279 if @project
279 if @project
280 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
280 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
281 else
281 else
282 content_tag('acronym', text, :title => format_time(time))
282 content_tag('acronym', text, :title => format_time(time))
283 end
283 end
284 end
284 end
285
285
286 def syntax_highlight(name, content)
286 def syntax_highlight(name, content)
287 type = CodeRay::FileType[name]
287 type = CodeRay::FileType[name]
288 type ? CodeRay.scan(content, type).html : h(content)
288 type ? CodeRay.scan(content, type).html : h(content)
289 end
289 end
290
290
291 def to_path_param(path)
291 def to_path_param(path)
292 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
292 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
293 end
293 end
294
294
295 def pagination_links_full(paginator, count=nil, options={})
295 def pagination_links_full(paginator, count=nil, options={})
296 page_param = options.delete(:page_param) || :page
296 page_param = options.delete(:page_param) || :page
297 per_page_links = options.delete(:per_page_links)
297 per_page_links = options.delete(:per_page_links)
298 url_param = params.dup
298 url_param = params.dup
299 # don't reuse query params if filters are present
299 # don't reuse query params if filters are present
300 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
300 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
301
301
302 html = ''
302 html = ''
303 if paginator.current.previous
303 if paginator.current.previous
304 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
304 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
305 end
305 end
306
306
307 html << (pagination_links_each(paginator, options) do |n|
307 html << (pagination_links_each(paginator, options) do |n|
308 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
308 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
309 end || '')
309 end || '')
310
310
311 if paginator.current.next
311 if paginator.current.next
312 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
312 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
313 end
313 end
314
314
315 unless count.nil?
315 unless count.nil?
316 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
316 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
317 if per_page_links != false && links = per_page_links(paginator.items_per_page)
317 if per_page_links != false && links = per_page_links(paginator.items_per_page)
318 html << " | #{links}"
318 html << " | #{links}"
319 end
319 end
320 end
320 end
321
321
322 html
322 html
323 end
323 end
324
324
325 def per_page_links(selected=nil)
325 def per_page_links(selected=nil)
326 url_param = params.dup
326 url_param = params.dup
327 url_param.clear if url_param.has_key?(:set_filter)
327 url_param.clear if url_param.has_key?(:set_filter)
328
328
329 links = Setting.per_page_options_array.collect do |n|
329 links = Setting.per_page_options_array.collect do |n|
330 n == selected ? n : link_to_remote(n, {:update => "content",
330 n == selected ? n : link_to_remote(n, {:update => "content",
331 :url => params.dup.merge(:per_page => n),
331 :url => params.dup.merge(:per_page => n),
332 :method => :get},
332 :method => :get},
333 {:href => url_for(url_param.merge(:per_page => n))})
333 {:href => url_for(url_param.merge(:per_page => n))})
334 end
334 end
335 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
335 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
336 end
336 end
337
337
338 def reorder_links(name, url)
338 def reorder_links(name, url)
339 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
339 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
340 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
340 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
341 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
341 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
342 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
342 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
343 end
343 end
344
344
345 def breadcrumb(*args)
345 def breadcrumb(*args)
346 elements = args.flatten
346 elements = args.flatten
347 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
347 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
348 end
348 end
349
349
350 def other_formats_links(&block)
350 def other_formats_links(&block)
351 concat('<p class="other-formats">' + l(:label_export_to))
351 concat('<p class="other-formats">' + l(:label_export_to))
352 yield Redmine::Views::OtherFormatsBuilder.new(self)
352 yield Redmine::Views::OtherFormatsBuilder.new(self)
353 concat('</p>')
353 concat('</p>')
354 end
354 end
355
355
356 def page_header_title
356 def page_header_title
357 if @project.nil? || @project.new_record?
357 if @project.nil? || @project.new_record?
358 h(Setting.app_title)
358 h(Setting.app_title)
359 else
359 else
360 b = []
360 b = []
361 ancestors = (@project.root? ? [] : @project.ancestors.visible)
361 ancestors = (@project.root? ? [] : @project.ancestors.visible)
362 if ancestors.any?
362 if ancestors.any?
363 root = ancestors.shift
363 root = ancestors.shift
364 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
364 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
365 if ancestors.size > 2
365 if ancestors.size > 2
366 b << '&#8230;'
366 b << '&#8230;'
367 ancestors = ancestors[-2, 2]
367 ancestors = ancestors[-2, 2]
368 end
368 end
369 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
369 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
370 end
370 end
371 b << h(@project)
371 b << h(@project)
372 b.join(' &#187; ')
372 b.join(' &#187; ')
373 end
373 end
374 end
374 end
375
375
376 def html_title(*args)
376 def html_title(*args)
377 if args.empty?
377 if args.empty?
378 title = []
378 title = []
379 title << @project.name if @project
379 title << @project.name if @project
380 title += @html_title if @html_title
380 title += @html_title if @html_title
381 title << Setting.app_title
381 title << Setting.app_title
382 title.select {|t| !t.blank? }.join(' - ')
382 title.select {|t| !t.blank? }.join(' - ')
383 else
383 else
384 @html_title ||= []
384 @html_title ||= []
385 @html_title += args
385 @html_title += args
386 end
386 end
387 end
387 end
388
388
389 def accesskey(s)
389 def accesskey(s)
390 Redmine::AccessKeys.key_for s
390 Redmine::AccessKeys.key_for s
391 end
391 end
392
392
393 # Formats text according to system settings.
393 # Formats text according to system settings.
394 # 2 ways to call this method:
394 # 2 ways to call this method:
395 # * with a String: textilizable(text, options)
395 # * with a String: textilizable(text, options)
396 # * with an object and one of its attribute: textilizable(issue, :description, options)
396 # * with an object and one of its attribute: textilizable(issue, :description, options)
397 def textilizable(*args)
397 def textilizable(*args)
398 options = args.last.is_a?(Hash) ? args.pop : {}
398 options = args.last.is_a?(Hash) ? args.pop : {}
399 case args.size
399 case args.size
400 when 1
400 when 1
401 obj = options[:object]
401 obj = options[:object]
402 text = args.shift
402 text = args.shift
403 when 2
403 when 2
404 obj = args.shift
404 obj = args.shift
405 attr = args.shift
405 attr = args.shift
406 text = obj.send(attr).to_s
406 text = obj.send(attr).to_s
407 else
407 else
408 raise ArgumentError, 'invalid arguments to textilizable'
408 raise ArgumentError, 'invalid arguments to textilizable'
409 end
409 end
410 return '' if text.blank?
410 return '' if text.blank?
411
411
412 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
412 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
413
413
414 only_path = options.delete(:only_path) == false ? false : true
414 only_path = options.delete(:only_path) == false ? false : true
415
415
416 # when using an image link, try to use an attachment, if possible
416 # when using an image link, try to use an attachment, if possible
417 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
417 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
418
418
419 if attachments
419 if attachments
420 attachments = attachments.sort_by(&:created_on).reverse
420 attachments = attachments.sort_by(&:created_on).reverse
421 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
421 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
422 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
422 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
423
423
424 # search for the picture in attachments
424 # search for the picture in attachments
425 if found = attachments.detect { |att| att.filename.downcase == filename }
425 if found = attachments.detect { |att| att.filename.downcase == filename }
426 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
426 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
427 desc = found.description.to_s.gsub('"', '')
427 desc = found.description.to_s.gsub('"', '')
428 if !desc.blank? && alttext.blank?
428 if !desc.blank? && alttext.blank?
429 alt = " title=\"#{desc}\" alt=\"#{desc}\""
429 alt = " title=\"#{desc}\" alt=\"#{desc}\""
430 end
430 end
431 "src=\"#{image_url}\"#{alt}"
431 "src=\"#{image_url}\"#{alt}"
432 else
432 else
433 m
433 m
434 end
434 end
435 end
435 end
436 end
436 end
437
437
438
438
439 # different methods for formatting wiki links
439 # different methods for formatting wiki links
440 case options[:wiki_links]
440 case options[:wiki_links]
441 when :local
441 when :local
442 # used for local links to html files
442 # used for local links to html files
443 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
443 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
444 when :anchor
444 when :anchor
445 # used for single-file wiki export
445 # used for single-file wiki export
446 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
446 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
447 else
447 else
448 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
448 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
449 end
449 end
450
450
451 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
451 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
452
452
453 # Wiki links
453 # Wiki links
454 #
454 #
455 # Examples:
455 # Examples:
456 # [[mypage]]
456 # [[mypage]]
457 # [[mypage|mytext]]
457 # [[mypage|mytext]]
458 # wiki links can refer other project wikis, using project name or identifier:
458 # wiki links can refer other project wikis, using project name or identifier:
459 # [[project:]] -> wiki starting page
459 # [[project:]] -> wiki starting page
460 # [[project:|mytext]]
460 # [[project:|mytext]]
461 # [[project:mypage]]
461 # [[project:mypage]]
462 # [[project:mypage|mytext]]
462 # [[project:mypage|mytext]]
463 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
463 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
464 link_project = project
464 link_project = project
465 esc, all, page, title = $1, $2, $3, $5
465 esc, all, page, title = $1, $2, $3, $5
466 if esc.nil?
466 if esc.nil?
467 if page =~ /^([^\:]+)\:(.*)$/
467 if page =~ /^([^\:]+)\:(.*)$/
468 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
468 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
469 page = $2
469 page = $2
470 title ||= $1 if page.blank?
470 title ||= $1 if page.blank?
471 end
471 end
472
472
473 if link_project && link_project.wiki
473 if link_project && link_project.wiki
474 # extract anchor
474 # extract anchor
475 anchor = nil
475 anchor = nil
476 if page =~ /^(.+?)\#(.+)$/
476 if page =~ /^(.+?)\#(.+)$/
477 page, anchor = $1, $2
477 page, anchor = $1, $2
478 end
478 end
479 # check if page exists
479 # check if page exists
480 wiki_page = link_project.wiki.find_page(page)
480 wiki_page = link_project.wiki.find_page(page)
481 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
481 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
482 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
482 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
483 else
483 else
484 # project or wiki doesn't exist
484 # project or wiki doesn't exist
485 all
485 all
486 end
486 end
487 else
487 else
488 all
488 all
489 end
489 end
490 end
490 end
491
491
492 # Redmine links
492 # Redmine links
493 #
493 #
494 # Examples:
494 # Examples:
495 # Issues:
495 # Issues:
496 # #52 -> Link to issue #52
496 # #52 -> Link to issue #52
497 # Changesets:
497 # Changesets:
498 # r52 -> Link to revision 52
498 # r52 -> Link to revision 52
499 # commit:a85130f -> Link to scmid starting with a85130f
499 # commit:a85130f -> Link to scmid starting with a85130f
500 # Documents:
500 # Documents:
501 # document#17 -> Link to document with id 17
501 # document#17 -> Link to document with id 17
502 # document:Greetings -> Link to the document with title "Greetings"
502 # document:Greetings -> Link to the document with title "Greetings"
503 # document:"Some document" -> Link to the document with title "Some document"
503 # document:"Some document" -> Link to the document with title "Some document"
504 # Versions:
504 # Versions:
505 # version#3 -> Link to version with id 3
505 # version#3 -> Link to version with id 3
506 # version:1.0.0 -> Link to version named "1.0.0"
506 # version:1.0.0 -> Link to version named "1.0.0"
507 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
507 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
508 # Attachments:
508 # Attachments:
509 # attachment:file.zip -> Link to the attachment of the current object named file.zip
509 # attachment:file.zip -> Link to the attachment of the current object named file.zip
510 # Source files:
510 # Source files:
511 # source:some/file -> Link to the file located at /some/file in the project's repository
511 # source:some/file -> Link to the file located at /some/file in the project's repository
512 # source:some/file@52 -> Link to the file's revision 52
512 # source:some/file@52 -> Link to the file's revision 52
513 # source:some/file#L120 -> Link to line 120 of the file
513 # source:some/file#L120 -> Link to line 120 of the file
514 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
514 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
515 # export:some/file -> Force the download of the file
515 # export:some/file -> Force the download of the file
516 # Forum messages:
516 # Forum messages:
517 # message#1218 -> Link to message with id 1218
517 # message#1218 -> Link to message with id 1218
518 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
518 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
519 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
519 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
520 link = nil
520 link = nil
521 if esc.nil?
521 if esc.nil?
522 if prefix.nil? && sep == 'r'
522 if prefix.nil? && sep == 'r'
523 if project && (changeset = project.changesets.find_by_revision(identifier))
523 if project && (changeset = project.changesets.find_by_revision(identifier))
524 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
524 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
525 :class => 'changeset',
525 :class => 'changeset',
526 :title => truncate_single_line(changeset.comments, :length => 100))
526 :title => truncate_single_line(changeset.comments, :length => 100))
527 end
527 end
528 elsif sep == '#'
528 elsif sep == '#'
529 oid = identifier.to_i
529 oid = identifier.to_i
530 case prefix
530 case prefix
531 when nil
531 when nil
532 if issue = Issue.visible.find_by_id(oid, :include => :status)
532 if issue = Issue.visible.find_by_id(oid, :include => :status)
533 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
533 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
534 :class => issue.css_classes,
534 :class => issue.css_classes,
535 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
535 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
536 end
536 end
537 when 'document'
537 when 'document'
538 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
538 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
539 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
539 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
540 :class => 'document'
540 :class => 'document'
541 end
541 end
542 when 'version'
542 when 'version'
543 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
543 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
544 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
544 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
545 :class => 'version'
545 :class => 'version'
546 end
546 end
547 when 'message'
547 when 'message'
548 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
548 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
549 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
549 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
550 :controller => 'messages',
550 :controller => 'messages',
551 :action => 'show',
551 :action => 'show',
552 :board_id => message.board,
552 :board_id => message.board,
553 :id => message.root,
553 :id => message.root,
554 :anchor => (message.parent ? "message-#{message.id}" : nil)},
554 :anchor => (message.parent ? "message-#{message.id}" : nil)},
555 :class => 'message'
555 :class => 'message'
556 end
556 end
557 when 'project'
557 when 'project'
558 if p = Project.visible.find_by_id(oid)
558 if p = Project.visible.find_by_id(oid)
559 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
559 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
560 :class => 'project'
560 :class => 'project'
561 end
561 end
562 end
562 end
563 elsif sep == ':'
563 elsif sep == ':'
564 # removes the double quotes if any
564 # removes the double quotes if any
565 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
565 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
566 case prefix
566 case prefix
567 when 'document'
567 when 'document'
568 if project && document = project.documents.find_by_title(name)
568 if project && document = project.documents.find_by_title(name)
569 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
569 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
570 :class => 'document'
570 :class => 'document'
571 end
571 end
572 when 'version'
572 when 'version'
573 if project && version = project.versions.find_by_name(name)
573 if project && version = project.versions.find_by_name(name)
574 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
574 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
575 :class => 'version'
575 :class => 'version'
576 end
576 end
577 when 'commit'
577 when 'commit'
578 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
578 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
579 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
579 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
580 :class => 'changeset',
580 :class => 'changeset',
581 :title => truncate_single_line(changeset.comments, :length => 100)
581 :title => truncate_single_line(changeset.comments, :length => 100)
582 end
582 end
583 when 'source', 'export'
583 when 'source', 'export'
584 if project && project.repository
584 if project && project.repository
585 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
585 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
586 path, rev, anchor = $1, $3, $5
586 path, rev, anchor = $1, $3, $5
587 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
587 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
588 :path => to_path_param(path),
588 :path => to_path_param(path),
589 :rev => rev,
589 :rev => rev,
590 :anchor => anchor,
590 :anchor => anchor,
591 :format => (prefix == 'export' ? 'raw' : nil)},
591 :format => (prefix == 'export' ? 'raw' : nil)},
592 :class => (prefix == 'export' ? 'source download' : 'source')
592 :class => (prefix == 'export' ? 'source download' : 'source')
593 end
593 end
594 when 'attachment'
594 when 'attachment'
595 if attachments && attachment = attachments.detect {|a| a.filename == name }
595 if attachments && attachment = attachments.detect {|a| a.filename == name }
596 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
596 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
597 :class => 'attachment'
597 :class => 'attachment'
598 end
598 end
599 when 'project'
599 when 'project'
600 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
600 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
601 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
601 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
602 :class => 'project'
602 :class => 'project'
603 end
603 end
604 end
604 end
605 end
605 end
606 end
606 end
607 leading + (link || "#{prefix}#{sep}#{identifier}")
607 leading + (link || "#{prefix}#{sep}#{identifier}")
608 end
608 end
609
609
610 text
610 text
611 end
611 end
612
612
613 # Same as Rails' simple_format helper without using paragraphs
613 # Same as Rails' simple_format helper without using paragraphs
614 def simple_format_without_paragraph(text)
614 def simple_format_without_paragraph(text)
615 text.to_s.
615 text.to_s.
616 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
616 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
617 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
617 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
618 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
618 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
619 end
619 end
620
620
621 def lang_options_for_select(blank=true)
621 def lang_options_for_select(blank=true)
622 (blank ? [["(auto)", ""]] : []) +
622 (blank ? [["(auto)", ""]] : []) +
623 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
623 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
624 end
624 end
625
625
626 def label_tag_for(name, option_tags = nil, options = {})
626 def label_tag_for(name, option_tags = nil, options = {})
627 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
627 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
628 content_tag("label", label_text)
628 content_tag("label", label_text)
629 end
629 end
630
630
631 def labelled_tabular_form_for(name, object, options, &proc)
631 def labelled_tabular_form_for(name, object, options, &proc)
632 options[:html] ||= {}
632 options[:html] ||= {}
633 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
633 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
634 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
634 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
635 end
635 end
636
636
637 def back_url_hidden_field_tag
637 def back_url_hidden_field_tag
638 back_url = params[:back_url] || request.env['HTTP_REFERER']
638 back_url = params[:back_url] || request.env['HTTP_REFERER']
639 back_url = CGI.unescape(back_url.to_s)
639 back_url = CGI.unescape(back_url.to_s)
640 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
640 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
641 end
641 end
642
642
643 def check_all_links(form_name)
643 def check_all_links(form_name)
644 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
644 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
645 " | " +
645 " | " +
646 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
646 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
647 end
647 end
648
648
649 def progress_bar(pcts, options={})
649 def progress_bar(pcts, options={})
650 pcts = [pcts, pcts] unless pcts.is_a?(Array)
650 pcts = [pcts, pcts] unless pcts.is_a?(Array)
651 pcts = pcts.collect(&:round)
651 pcts = pcts.collect(&:round)
652 pcts[1] = pcts[1] - pcts[0]
652 pcts[1] = pcts[1] - pcts[0]
653 pcts << (100 - pcts[1] - pcts[0])
653 pcts << (100 - pcts[1] - pcts[0])
654 width = options[:width] || '100px;'
654 width = options[:width] || '100px;'
655 legend = options[:legend] || ''
655 legend = options[:legend] || ''
656 content_tag('table',
656 content_tag('table',
657 content_tag('tr',
657 content_tag('tr',
658 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
658 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
659 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
659 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
660 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
660 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
661 ), :class => 'progress', :style => "width: #{width};") +
661 ), :class => 'progress', :style => "width: #{width};") +
662 content_tag('p', legend, :class => 'pourcent')
662 content_tag('p', legend, :class => 'pourcent')
663 end
663 end
664
665 def context_menu(url)
666 unless @context_menu_included
667 content_for :header_tags do
668 javascript_include_tag('context_menu') +
669 stylesheet_link_tag('context_menu')
670 end
671 @context_menu_included = true
672 end
673 javascript_tag "new ContextMenu('#{ url_for(url) }')"
674 end
664
675
665 def context_menu_link(name, url, options={})
676 def context_menu_link(name, url, options={})
666 options[:class] ||= ''
677 options[:class] ||= ''
667 if options.delete(:selected)
678 if options.delete(:selected)
668 options[:class] << ' icon-checked disabled'
679 options[:class] << ' icon-checked disabled'
669 options[:disabled] = true
680 options[:disabled] = true
670 end
681 end
671 if options.delete(:disabled)
682 if options.delete(:disabled)
672 options.delete(:method)
683 options.delete(:method)
673 options.delete(:confirm)
684 options.delete(:confirm)
674 options.delete(:onclick)
685 options.delete(:onclick)
675 options[:class] << ' disabled'
686 options[:class] << ' disabled'
676 url = '#'
687 url = '#'
677 end
688 end
678 link_to name, url, options
689 link_to name, url, options
679 end
690 end
680
691
681 def calendar_for(field_id)
692 def calendar_for(field_id)
682 include_calendar_headers_tags
693 include_calendar_headers_tags
683 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
694 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
684 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
695 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
685 end
696 end
686
697
687 def include_calendar_headers_tags
698 def include_calendar_headers_tags
688 unless @calendar_headers_tags_included
699 unless @calendar_headers_tags_included
689 @calendar_headers_tags_included = true
700 @calendar_headers_tags_included = true
690 content_for :header_tags do
701 content_for :header_tags do
691 start_of_week = case Setting.start_of_week.to_i
702 start_of_week = case Setting.start_of_week.to_i
692 when 1
703 when 1
693 'Calendar._FD = 1;' # Monday
704 'Calendar._FD = 1;' # Monday
694 when 7
705 when 7
695 'Calendar._FD = 0;' # Sunday
706 'Calendar._FD = 0;' # Sunday
696 else
707 else
697 '' # use language
708 '' # use language
698 end
709 end
699
710
700 javascript_include_tag('calendar/calendar') +
711 javascript_include_tag('calendar/calendar') +
701 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
712 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
702 javascript_tag(start_of_week) +
713 javascript_tag(start_of_week) +
703 javascript_include_tag('calendar/calendar-setup') +
714 javascript_include_tag('calendar/calendar-setup') +
704 stylesheet_link_tag('calendar')
715 stylesheet_link_tag('calendar')
705 end
716 end
706 end
717 end
707 end
718 end
708
719
709 def content_for(name, content = nil, &block)
720 def content_for(name, content = nil, &block)
710 @has_content ||= {}
721 @has_content ||= {}
711 @has_content[name] = true
722 @has_content[name] = true
712 super(name, content, &block)
723 super(name, content, &block)
713 end
724 end
714
725
715 def has_content?(name)
726 def has_content?(name)
716 (@has_content && @has_content[name]) || false
727 (@has_content && @has_content[name]) || false
717 end
728 end
718
729
719 # Returns the avatar image tag for the given +user+ if avatars are enabled
730 # Returns the avatar image tag for the given +user+ if avatars are enabled
720 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
731 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
721 def avatar(user, options = { })
732 def avatar(user, options = { })
722 if Setting.gravatar_enabled?
733 if Setting.gravatar_enabled?
723 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
734 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
724 email = nil
735 email = nil
725 if user.respond_to?(:mail)
736 if user.respond_to?(:mail)
726 email = user.mail
737 email = user.mail
727 elsif user.to_s =~ %r{<(.+?)>}
738 elsif user.to_s =~ %r{<(.+?)>}
728 email = $1
739 email = $1
729 end
740 end
730 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
741 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
731 end
742 end
732 end
743 end
733
744
734 private
745 private
735
746
736 def wiki_helper
747 def wiki_helper
737 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
748 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
738 extend helper
749 extend helper
739 return self
750 return self
740 end
751 end
741
752
742 def link_to_remote_content_update(text, url_params)
753 def link_to_remote_content_update(text, url_params)
743 link_to_remote(text,
754 link_to_remote(text,
744 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
755 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
745 {:href => url_for(:params => url_params)}
756 {:href => url_for(:params => url_params)}
746 )
757 )
747 end
758 end
748
759
749 end
760 end
@@ -1,86 +1,83
1 <div class="contextual">
1 <div class="contextual">
2 <% if !@query.new_record? && @query.editable_by?(User.current) %>
2 <% if !@query.new_record? && @query.editable_by?(User.current) %>
3 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
3 <%= link_to l(:button_edit), {:controller => 'queries', :action => 'edit', :id => @query}, :class => 'icon icon-edit' %>
4 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
4 <%= link_to l(:button_delete), {:controller => 'queries', :action => 'destroy', :id => @query}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
5 <% end %>
5 <% end %>
6 </div>
6 </div>
7
7
8 <h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
8 <h2><%= @query.new_record? ? l(:label_issue_plural) : h(@query.name) %></h2>
9 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
9 <% html_title(@query.new_record? ? l(:label_issue_plural) : @query.name) %>
10
10
11 <% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
11 <% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
12 <%= hidden_field_tag('project_id', @project.to_param) if @project %>
12 <%= hidden_field_tag('project_id', @project.to_param) if @project %>
13 <div id="query_form_content">
13 <div id="query_form_content">
14 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
14 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
15 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
15 <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
16 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
16 <div style="<%= @query.new_record? ? "" : "display: none;" %>">
17 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
17 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
18 </div>
18 </div>
19 </fieldset>
19 </fieldset>
20 <fieldset class="collapsible collapsed">
20 <fieldset class="collapsible collapsed">
21 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
21 <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
22 <div style="display: none;">
22 <div style="display: none;">
23 <table>
23 <table>
24 <tr>
24 <tr>
25 <td><%= l(:field_column_names) %></td>
25 <td><%= l(:field_column_names) %></td>
26 <td><%= render :partial => 'queries/columns', :locals => {:query => @query} %></td>
26 <td><%= render :partial => 'queries/columns', :locals => {:query => @query} %></td>
27 </tr>
27 </tr>
28 <tr>
28 <tr>
29 <td><%= l(:field_group_by) %></td>
29 <td><%= l(:field_group_by) %></td>
30 <td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td>
30 <td><%= select_tag('group_by', options_for_select([[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, @query.group_by)) %></td>
31 </tr>
31 </tr>
32 </table>
32 </table>
33 </div>
33 </div>
34 </fieldset>
34 </fieldset>
35 </div>
35 </div>
36 <p class="buttons">
36 <p class="buttons">
37
37
38 <%= link_to_remote l(:button_apply),
38 <%= link_to_remote l(:button_apply),
39 { :url => { :set_filter => 1 },
39 { :url => { :set_filter => 1 },
40 :before => 'selectAllOptions("selected_columns");',
40 :before => 'selectAllOptions("selected_columns");',
41 :update => "content",
41 :update => "content",
42 :with => "Form.serialize('query_form')"
42 :with => "Form.serialize('query_form')"
43 }, :class => 'icon icon-checked' %>
43 }, :class => 'icon icon-checked' %>
44
44
45 <%= link_to_remote l(:button_clear),
45 <%= link_to_remote l(:button_clear),
46 { :url => { :set_filter => 1, :project_id => @project },
46 { :url => { :set_filter => 1, :project_id => @project },
47 :method => :get,
47 :method => :get,
48 :update => "content",
48 :update => "content",
49 }, :class => 'icon icon-reload' %>
49 }, :class => 'icon icon-reload' %>
50
50
51 <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
51 <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
52 <%= link_to l(:button_save), {}, :onclick => "selectAllOptions('selected_columns'); $('query_form').submit(); return false;", :class => 'icon icon-save' %>
52 <%= link_to l(:button_save), {}, :onclick => "selectAllOptions('selected_columns'); $('query_form').submit(); return false;", :class => 'icon icon-save' %>
53 <% end %>
53 <% end %>
54 </p>
54 </p>
55 <% end %>
55 <% end %>
56
56
57 <%= error_messages_for 'query' %>
57 <%= error_messages_for 'query' %>
58 <% if @query.valid? %>
58 <% if @query.valid? %>
59 <% if @issues.empty? %>
59 <% if @issues.empty? %>
60 <p class="nodata"><%= l(:label_no_data) %></p>
60 <p class="nodata"><%= l(:label_no_data) %></p>
61 <% else %>
61 <% else %>
62 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
62 <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %>
63 <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
63 <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p>
64 <% end %>
64 <% end %>
65
65
66 <% other_formats_links do |f| %>
66 <% other_formats_links do |f| %>
67 <%= f.link_to 'Atom', :url => { :project_id => @project, :query_id => (@query.new_record? ? nil : @query), :key => User.current.rss_key } %>
67 <%= f.link_to 'Atom', :url => { :project_id => @project, :query_id => (@query.new_record? ? nil : @query), :key => User.current.rss_key } %>
68 <%= f.link_to 'CSV', :url => { :project_id => @project } %>
68 <%= f.link_to 'CSV', :url => { :project_id => @project } %>
69 <%= f.link_to 'PDF', :url => { :project_id => @project } %>
69 <%= f.link_to 'PDF', :url => { :project_id => @project } %>
70 <% end %>
70 <% end %>
71
71
72 <% end %>
72 <% end %>
73
73
74 <% content_for :sidebar do %>
74 <% content_for :sidebar do %>
75 <%= render :partial => 'issues/sidebar' %>
75 <%= render :partial => 'issues/sidebar' %>
76 <% end %>
76 <% end %>
77
77
78 <% content_for :header_tags do %>
78 <% content_for :header_tags do %>
79 <%= auto_discovery_link_tag(:atom, {:query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_issue_plural)) %>
79 <%= auto_discovery_link_tag(:atom, {:query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_issue_plural)) %>
80 <%= auto_discovery_link_tag(:atom, {:action => 'changes', :query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_changes_details)) %>
80 <%= auto_discovery_link_tag(:atom, {:action => 'changes', :query_id => @query, :format => 'atom', :page => nil, :key => User.current.rss_key}, :title => l(:label_changes_details)) %>
81 <%= javascript_include_tag 'context_menu' %>
82 <%= stylesheet_link_tag 'context_menu' %>
83 <% end %>
81 <% end %>
84
82
85 <div id="context-menu" style="display: none;"></div>
83 <%= context_menu :controller => 'issues', :action => 'context_menu' %>
86 <%= javascript_tag "new ContextMenu('#{url_for(:controller => 'issues', :action => 'context_menu')}')" %>
@@ -1,42 +1,36
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to l(:label_personalize_page), :action => 'page_layout' %>
2 <%= link_to l(:label_personalize_page), :action => 'page_layout' %>
3 </div>
3 </div>
4
4
5 <h2><%=l(:label_my_page)%></h2>
5 <h2><%=l(:label_my_page)%></h2>
6
6
7 <div id="list-top">
7 <div id="list-top">
8 <% @blocks['top'].each do |b|
8 <% @blocks['top'].each do |b|
9 next unless MyController::BLOCKS.keys.include? b %>
9 next unless MyController::BLOCKS.keys.include? b %>
10 <div class="mypage-box">
10 <div class="mypage-box">
11 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
11 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
12 </div>
12 </div>
13 <% end if @blocks['top'] %>
13 <% end if @blocks['top'] %>
14 </div>
14 </div>
15
15
16 <div id="list-left" class="splitcontentleft">
16 <div id="list-left" class="splitcontentleft">
17 <% @blocks['left'].each do |b|
17 <% @blocks['left'].each do |b|
18 next unless MyController::BLOCKS.keys.include? b %>
18 next unless MyController::BLOCKS.keys.include? b %>
19 <div class="mypage-box">
19 <div class="mypage-box">
20 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
20 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
21 </div>
21 </div>
22 <% end if @blocks['left'] %>
22 <% end if @blocks['left'] %>
23 </div>
23 </div>
24
24
25 <div id="list-right" class="splitcontentright">
25 <div id="list-right" class="splitcontentright">
26 <% @blocks['right'].each do |b|
26 <% @blocks['right'].each do |b|
27 next unless MyController::BLOCKS.keys.include? b %>
27 next unless MyController::BLOCKS.keys.include? b %>
28 <div class="mypage-box">
28 <div class="mypage-box">
29 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
29 <%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
30 </div>
30 </div>
31 <% end if @blocks['right'] %>
31 <% end if @blocks['right'] %>
32 </div>
32 </div>
33
33
34 <% content_for :header_tags do %>
34 <%= context_menu :controller => 'issues', :action => 'context_menu' %>
35 <%= javascript_include_tag 'context_menu' %>
36 <%= stylesheet_link_tag 'context_menu' %>
37 <% end %>
38
39 <div id="context-menu" style="display: none;"></div>
40 <%= javascript_tag "new ContextMenu('#{url_for(:controller => 'issues', :action => 'context_menu')}')" %>
41
35
42 <% html_title(l(:label_my_page)) -%>
36 <% html_title(l(:label_my_page)) -%>
@@ -1,221 +1,231
1 /* redMine - project management software
1 /* redMine - project management software
2 Copyright (C) 2006-2008 Jean-Philippe Lang */
2 Copyright (C) 2006-2008 Jean-Philippe Lang */
3
3
4 var observingContextMenuClick;
4 var observingContextMenuClick;
5
5
6 ContextMenu = Class.create();
6 ContextMenu = Class.create();
7 ContextMenu.prototype = {
7 ContextMenu.prototype = {
8 initialize: function (url) {
8 initialize: function (url) {
9 this.url = url;
9 this.url = url;
10
10 this.createMenu();
11
11 // prevent text selection in the issue list
12 // prevent text selection in the issue list
12 var tables = $$('table.issues');
13 var tables = $$('table.issues');
13 for (i=0; i<tables.length; i++) {
14 for (i=0; i<tables.length; i++) {
14 tables[i].onselectstart = function () { return false; } // ie
15 tables[i].onselectstart = function () { return false; } // ie
15 tables[i].onmousedown = function () { return false; } // mozilla
16 tables[i].onmousedown = function () { return false; } // mozilla
16 }
17 }
17
18
18 if (!observingContextMenuClick) {
19 if (!observingContextMenuClick) {
19 Event.observe(document, 'click', this.Click.bindAsEventListener(this));
20 Event.observe(document, 'click', this.Click.bindAsEventListener(this));
20 Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this));
21 Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this));
21 observingContextMenuClick = true;
22 observingContextMenuClick = true;
22 }
23 }
23
24
24 this.unselectAll();
25 this.unselectAll();
25 this.lastSelected = null;
26 this.lastSelected = null;
26 },
27 },
27
28
28 RightClick: function(e) {
29 RightClick: function(e) {
29 this.hideMenu();
30 this.hideMenu();
30 // do not show the context menu on links
31 // do not show the context menu on links
31 if (Event.element(e).tagName == 'A') { return; }
32 if (Event.element(e).tagName == 'A') { return; }
32 // right-click simulated by Alt+Click with Opera
33 // right-click simulated by Alt+Click with Opera
33 if (window.opera && !e.altKey) { return; }
34 if (window.opera && !e.altKey) { return; }
34 var tr = Event.findElement(e, 'tr');
35 var tr = Event.findElement(e, 'tr');
35 if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; }
36 if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; }
36 Event.stop(e);
37 Event.stop(e);
37 if (!this.isSelected(tr)) {
38 if (!this.isSelected(tr)) {
38 this.unselectAll();
39 this.unselectAll();
39 this.addSelection(tr);
40 this.addSelection(tr);
40 this.lastSelected = tr;
41 this.lastSelected = tr;
41 }
42 }
42 this.showMenu(e);
43 this.showMenu(e);
43 },
44 },
44
45
45 Click: function(e) {
46 Click: function(e) {
46 this.hideMenu();
47 this.hideMenu();
47 if (Event.element(e).tagName == 'A') { return; }
48 if (Event.element(e).tagName == 'A') { return; }
48 if (window.opera && e.altKey) { return; }
49 if (window.opera && e.altKey) { return; }
49 if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
50 if (Event.isLeftClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) {
50 var tr = Event.findElement(e, 'tr');
51 var tr = Event.findElement(e, 'tr');
51 if (tr!=null && tr!=document && tr.hasClassName('hascontextmenu')) {
52 if (tr!=null && tr!=document && tr.hasClassName('hascontextmenu')) {
52 // a row was clicked, check if the click was on checkbox
53 // a row was clicked, check if the click was on checkbox
53 var box = Event.findElement(e, 'input');
54 var box = Event.findElement(e, 'input');
54 if (box!=document && box!=undefined) {
55 if (box!=document && box!=undefined) {
55 // a checkbox may be clicked
56 // a checkbox may be clicked
56 if (box.checked) {
57 if (box.checked) {
57 tr.addClassName('context-menu-selection');
58 tr.addClassName('context-menu-selection');
58 } else {
59 } else {
59 tr.removeClassName('context-menu-selection');
60 tr.removeClassName('context-menu-selection');
60 }
61 }
61 } else {
62 } else {
62 if (e.ctrlKey) {
63 if (e.ctrlKey) {
63 this.toggleSelection(tr);
64 this.toggleSelection(tr);
64 } else if (e.shiftKey) {
65 } else if (e.shiftKey) {
65 if (this.lastSelected != null) {
66 if (this.lastSelected != null) {
66 var toggling = false;
67 var toggling = false;
67 var rows = $$('.hascontextmenu');
68 var rows = $$('.hascontextmenu');
68 for (i=0; i<rows.length; i++) {
69 for (i=0; i<rows.length; i++) {
69 if (toggling || rows[i]==tr) {
70 if (toggling || rows[i]==tr) {
70 this.addSelection(rows[i]);
71 this.addSelection(rows[i]);
71 }
72 }
72 if (rows[i]==tr || rows[i]==this.lastSelected) {
73 if (rows[i]==tr || rows[i]==this.lastSelected) {
73 toggling = !toggling;
74 toggling = !toggling;
74 }
75 }
75 }
76 }
76 } else {
77 } else {
77 this.addSelection(tr);
78 this.addSelection(tr);
78 }
79 }
79 } else {
80 } else {
80 this.unselectAll();
81 this.unselectAll();
81 this.addSelection(tr);
82 this.addSelection(tr);
82 }
83 }
83 this.lastSelected = tr;
84 this.lastSelected = tr;
84 }
85 }
85 } else {
86 } else {
86 // click is outside the rows
87 // click is outside the rows
87 var t = Event.findElement(e, 'a');
88 var t = Event.findElement(e, 'a');
88 if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
89 if ((t != document) && (Element.hasClassName(t, 'disabled') || Element.hasClassName(t, 'submenu'))) {
89 Event.stop(e);
90 Event.stop(e);
90 }
91 }
91 }
92 }
92 }
93 }
93 else{
94 else{
94 this.RightClick(e);
95 this.RightClick(e);
95 }
96 }
96 },
97 },
97
98
99 createMenu: function() {
100 if (!$('context-menu')) {
101 var menu = document.createElement("div");
102 menu.setAttribute("id", "context-menu");
103 menu.setAttribute("style", "display:none;");
104 document.getElementById("content").appendChild(menu);
105 }
106 },
107
98 showMenu: function(e) {
108 showMenu: function(e) {
99 var mouse_x = Event.pointerX(e);
109 var mouse_x = Event.pointerX(e);
100 var mouse_y = Event.pointerY(e);
110 var mouse_y = Event.pointerY(e);
101 var render_x = mouse_x;
111 var render_x = mouse_x;
102 var render_y = mouse_y;
112 var render_y = mouse_y;
103 var dims;
113 var dims;
104 var menu_width;
114 var menu_width;
105 var menu_height;
115 var menu_height;
106 var window_width;
116 var window_width;
107 var window_height;
117 var window_height;
108 var max_width;
118 var max_width;
109 var max_height;
119 var max_height;
110
120
111 $('context-menu').style['left'] = (render_x + 'px');
121 $('context-menu').style['left'] = (render_x + 'px');
112 $('context-menu').style['top'] = (render_y + 'px');
122 $('context-menu').style['top'] = (render_y + 'px');
113 Element.update('context-menu', '');
123 Element.update('context-menu', '');
114
124
115 new Ajax.Updater({success:'context-menu'}, this.url,
125 new Ajax.Updater({success:'context-menu'}, this.url,
116 {asynchronous:true,
126 {asynchronous:true,
117 evalScripts:true,
127 evalScripts:true,
118 parameters:Form.serialize(Event.findElement(e, 'form')),
128 parameters:Form.serialize(Event.findElement(e, 'form')),
119 onComplete:function(request){
129 onComplete:function(request){
120 dims = $('context-menu').getDimensions();
130 dims = $('context-menu').getDimensions();
121 menu_width = dims.width;
131 menu_width = dims.width;
122 menu_height = dims.height;
132 menu_height = dims.height;
123 max_width = mouse_x + 2*menu_width;
133 max_width = mouse_x + 2*menu_width;
124 max_height = mouse_y + menu_height;
134 max_height = mouse_y + menu_height;
125
135
126 var ws = window_size();
136 var ws = window_size();
127 window_width = ws.width;
137 window_width = ws.width;
128 window_height = ws.height;
138 window_height = ws.height;
129
139
130 /* display the menu above and/or to the left of the click if needed */
140 /* display the menu above and/or to the left of the click if needed */
131 if (max_width > window_width) {
141 if (max_width > window_width) {
132 render_x -= menu_width;
142 render_x -= menu_width;
133 $('context-menu').addClassName('reverse-x');
143 $('context-menu').addClassName('reverse-x');
134 } else {
144 } else {
135 $('context-menu').removeClassName('reverse-x');
145 $('context-menu').removeClassName('reverse-x');
136 }
146 }
137 if (max_height > window_height) {
147 if (max_height > window_height) {
138 render_y -= menu_height;
148 render_y -= menu_height;
139 $('context-menu').addClassName('reverse-y');
149 $('context-menu').addClassName('reverse-y');
140 } else {
150 } else {
141 $('context-menu').removeClassName('reverse-y');
151 $('context-menu').removeClassName('reverse-y');
142 }
152 }
143 if (render_x <= 0) render_x = 1;
153 if (render_x <= 0) render_x = 1;
144 if (render_y <= 0) render_y = 1;
154 if (render_y <= 0) render_y = 1;
145 $('context-menu').style['left'] = (render_x + 'px');
155 $('context-menu').style['left'] = (render_x + 'px');
146 $('context-menu').style['top'] = (render_y + 'px');
156 $('context-menu').style['top'] = (render_y + 'px');
147
157
148 Effect.Appear('context-menu', {duration: 0.20});
158 Effect.Appear('context-menu', {duration: 0.20});
149 if (window.parseStylesheets) { window.parseStylesheets(); } // IE
159 if (window.parseStylesheets) { window.parseStylesheets(); } // IE
150 }})
160 }})
151 },
161 },
152
162
153 hideMenu: function() {
163 hideMenu: function() {
154 Element.hide('context-menu');
164 Element.hide('context-menu');
155 },
165 },
156
166
157 addSelection: function(tr) {
167 addSelection: function(tr) {
158 tr.addClassName('context-menu-selection');
168 tr.addClassName('context-menu-selection');
159 this.checkSelectionBox(tr, true);
169 this.checkSelectionBox(tr, true);
160 },
170 },
161
171
162 toggleSelection: function(tr) {
172 toggleSelection: function(tr) {
163 if (this.isSelected(tr)) {
173 if (this.isSelected(tr)) {
164 this.removeSelection(tr);
174 this.removeSelection(tr);
165 } else {
175 } else {
166 this.addSelection(tr);
176 this.addSelection(tr);
167 }
177 }
168 },
178 },
169
179
170 removeSelection: function(tr) {
180 removeSelection: function(tr) {
171 tr.removeClassName('context-menu-selection');
181 tr.removeClassName('context-menu-selection');
172 this.checkSelectionBox(tr, false);
182 this.checkSelectionBox(tr, false);
173 },
183 },
174
184
175 unselectAll: function() {
185 unselectAll: function() {
176 var rows = $$('.hascontextmenu');
186 var rows = $$('.hascontextmenu');
177 for (i=0; i<rows.length; i++) {
187 for (i=0; i<rows.length; i++) {
178 this.removeSelection(rows[i]);
188 this.removeSelection(rows[i]);
179 }
189 }
180 },
190 },
181
191
182 checkSelectionBox: function(tr, checked) {
192 checkSelectionBox: function(tr, checked) {
183 var inputs = Element.getElementsBySelector(tr, 'input');
193 var inputs = Element.getElementsBySelector(tr, 'input');
184 if (inputs.length > 0) { inputs[0].checked = checked; }
194 if (inputs.length > 0) { inputs[0].checked = checked; }
185 },
195 },
186
196
187 isSelected: function(tr) {
197 isSelected: function(tr) {
188 return Element.hasClassName(tr, 'context-menu-selection');
198 return Element.hasClassName(tr, 'context-menu-selection');
189 }
199 }
190 }
200 }
191
201
192 function toggleIssuesSelection(el) {
202 function toggleIssuesSelection(el) {
193 var boxes = el.getElementsBySelector('input[type=checkbox]');
203 var boxes = el.getElementsBySelector('input[type=checkbox]');
194 var all_checked = true;
204 var all_checked = true;
195 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
205 for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
196 for (i = 0; i < boxes.length; i++) {
206 for (i = 0; i < boxes.length; i++) {
197 if (all_checked) {
207 if (all_checked) {
198 boxes[i].checked = false;
208 boxes[i].checked = false;
199 boxes[i].up('tr').removeClassName('context-menu-selection');
209 boxes[i].up('tr').removeClassName('context-menu-selection');
200 } else if (boxes[i].checked == false) {
210 } else if (boxes[i].checked == false) {
201 boxes[i].checked = true;
211 boxes[i].checked = true;
202 boxes[i].up('tr').addClassName('context-menu-selection');
212 boxes[i].up('tr').addClassName('context-menu-selection');
203 }
213 }
204 }
214 }
205 }
215 }
206
216
207 function window_size() {
217 function window_size() {
208 var w;
218 var w;
209 var h;
219 var h;
210 if (window.innerWidth) {
220 if (window.innerWidth) {
211 w = window.innerWidth;
221 w = window.innerWidth;
212 h = window.innerHeight;
222 h = window.innerHeight;
213 } else if (document.documentElement) {
223 } else if (document.documentElement) {
214 w = document.documentElement.clientWidth;
224 w = document.documentElement.clientWidth;
215 h = document.documentElement.clientHeight;
225 h = document.documentElement.clientHeight;
216 } else {
226 } else {
217 w = document.body.clientWidth;
227 w = document.body.clientWidth;
218 h = document.body.clientHeight;
228 h = document.body.clientHeight;
219 }
229 }
220 return {width: w, height: h};
230 return {width: w, height: h};
221 }
231 }
General Comments 0
You need to be logged in to leave comments. Login now