##// END OF EJS Templates
Add RTL support to the context menu. #6012...
Eric Davis -
r3900:05527be943a5
parent child
Show More
@@ -0,0 +1,9
1 #context-menu li.folder ul { left:auto; right:168px; }
2 #context-menu li.folder>ul { left:auto; right:148px; }
3 #context-menu li a.submenu { background:url("../images/bullet_arrow_left.png") left no-repeat; }
4
5 #context-menu a {
6 background-position: 100% 40%;
7 padding-right: 20px;
8 padding-left: 0px;
9 }
@@ -1,833 +1,838
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 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
37 end
37 end
38
38
39 # Display a link to remote if user is authorized
39 # Display a link to remote if user is authorized
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
41 url = options[:url] || {}
41 url = options[:url] || {}
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
43 end
43 end
44
44
45 # Displays a link to user's account page if active
45 # Displays a link to user's account page if active
46 def link_to_user(user, options={})
46 def link_to_user(user, options={})
47 if user.is_a?(User)
47 if user.is_a?(User)
48 name = h(user.name(options[:format]))
48 name = h(user.name(options[:format]))
49 if user.active?
49 if user.active?
50 link_to name, :controller => 'users', :action => 'show', :id => user
50 link_to name, :controller => 'users', :action => 'show', :id => user
51 else
51 else
52 name
52 name
53 end
53 end
54 else
54 else
55 h(user.to_s)
55 h(user.to_s)
56 end
56 end
57 end
57 end
58
58
59 # Displays a link to +issue+ with its subject.
59 # Displays a link to +issue+ with its subject.
60 # Examples:
60 # Examples:
61 #
61 #
62 # link_to_issue(issue) # => Defect #6: This is the subject
62 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :subject => false) # => Defect #6
64 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 #
66 #
67 def link_to_issue(issue, options={})
67 def link_to_issue(issue, options={})
68 title = nil
68 title = nil
69 subject = nil
69 subject = nil
70 if options[:subject] == false
70 if options[:subject] == false
71 title = truncate(issue.subject, :length => 60)
71 title = truncate(issue.subject, :length => 60)
72 else
72 else
73 subject = issue.subject
73 subject = issue.subject
74 if options[:truncate]
74 if options[:truncate]
75 subject = truncate(subject, :length => options[:truncate])
75 subject = truncate(subject, :length => options[:truncate])
76 end
76 end
77 end
77 end
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
79 :class => issue.css_classes,
79 :class => issue.css_classes,
80 :title => title
80 :title => title
81 s << ": #{h subject}" if subject
81 s << ": #{h subject}" if subject
82 s = "#{h issue.project} - " + s if options[:project]
82 s = "#{h issue.project} - " + s if options[:project]
83 s
83 s
84 end
84 end
85
85
86 # Generates a link to an attachment.
86 # Generates a link to an attachment.
87 # Options:
87 # Options:
88 # * :text - Link text (default to attachment filename)
88 # * :text - Link text (default to attachment filename)
89 # * :download - Force download (default: false)
89 # * :download - Force download (default: false)
90 def link_to_attachment(attachment, options={})
90 def link_to_attachment(attachment, options={})
91 text = options.delete(:text) || attachment.filename
91 text = options.delete(:text) || attachment.filename
92 action = options.delete(:download) ? 'download' : 'show'
92 action = options.delete(:download) ? 'download' : 'show'
93
93
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
95 end
95 end
96
96
97 # Generates a link to a SCM revision
97 # Generates a link to a SCM revision
98 # Options:
98 # Options:
99 # * :text - Link text (default to the formatted revision)
99 # * :text - Link text (default to the formatted revision)
100 def link_to_revision(revision, project, options={})
100 def link_to_revision(revision, project, options={})
101 text = options.delete(:text) || format_revision(revision)
101 text = options.delete(:text) || format_revision(revision)
102
102
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
104 end
104 end
105
105
106 # Generates a link to a project if active
106 # Generates a link to a project if active
107 # Examples:
107 # Examples:
108 #
108 #
109 # link_to_project(project) # => link to the specified project overview
109 # link_to_project(project) # => link to the specified project overview
110 # link_to_project(project, :action=>'settings') # => link to project settings
110 # link_to_project(project, :action=>'settings') # => link to project settings
111 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
111 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
112 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
112 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
113 #
113 #
114 def link_to_project(project, options={}, html_options = nil)
114 def link_to_project(project, options={}, html_options = nil)
115 if project.active?
115 if project.active?
116 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
116 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
117 link_to(h(project), url, html_options)
117 link_to(h(project), url, html_options)
118 else
118 else
119 h(project)
119 h(project)
120 end
120 end
121 end
121 end
122
122
123 def toggle_link(name, id, options={})
123 def toggle_link(name, id, options={})
124 onclick = "Element.toggle('#{id}'); "
124 onclick = "Element.toggle('#{id}'); "
125 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
125 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
126 onclick << "return false;"
126 onclick << "return false;"
127 link_to(name, "#", :onclick => onclick)
127 link_to(name, "#", :onclick => onclick)
128 end
128 end
129
129
130 def image_to_function(name, function, html_options = {})
130 def image_to_function(name, function, html_options = {})
131 html_options.symbolize_keys!
131 html_options.symbolize_keys!
132 tag(:input, html_options.merge({
132 tag(:input, html_options.merge({
133 :type => "image", :src => image_path(name),
133 :type => "image", :src => image_path(name),
134 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
134 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
135 }))
135 }))
136 end
136 end
137
137
138 def prompt_to_remote(name, text, param, url, html_options = {})
138 def prompt_to_remote(name, text, param, url, html_options = {})
139 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
139 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
140 link_to name, {}, html_options
140 link_to name, {}, html_options
141 end
141 end
142
142
143 def format_activity_title(text)
143 def format_activity_title(text)
144 h(truncate_single_line(text, :length => 100))
144 h(truncate_single_line(text, :length => 100))
145 end
145 end
146
146
147 def format_activity_day(date)
147 def format_activity_day(date)
148 date == Date.today ? l(:label_today).titleize : format_date(date)
148 date == Date.today ? l(:label_today).titleize : format_date(date)
149 end
149 end
150
150
151 def format_activity_description(text)
151 def format_activity_description(text)
152 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
152 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
153 end
153 end
154
154
155 def format_version_name(version)
155 def format_version_name(version)
156 if version.project == @project
156 if version.project == @project
157 h(version)
157 h(version)
158 else
158 else
159 h("#{version.project} - #{version}")
159 h("#{version.project} - #{version}")
160 end
160 end
161 end
161 end
162
162
163 def due_date_distance_in_words(date)
163 def due_date_distance_in_words(date)
164 if date
164 if date
165 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
165 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
166 end
166 end
167 end
167 end
168
168
169 def render_page_hierarchy(pages, node=nil)
169 def render_page_hierarchy(pages, node=nil)
170 content = ''
170 content = ''
171 if pages[node]
171 if pages[node]
172 content << "<ul class=\"pages-hierarchy\">\n"
172 content << "<ul class=\"pages-hierarchy\">\n"
173 pages[node].each do |page|
173 pages[node].each do |page|
174 content << "<li>"
174 content << "<li>"
175 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
175 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
176 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
176 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
177 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
177 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
178 content << "</li>\n"
178 content << "</li>\n"
179 end
179 end
180 content << "</ul>\n"
180 content << "</ul>\n"
181 end
181 end
182 content
182 content
183 end
183 end
184
184
185 # Renders flash messages
185 # Renders flash messages
186 def render_flash_messages
186 def render_flash_messages
187 s = ''
187 s = ''
188 flash.each do |k,v|
188 flash.each do |k,v|
189 s << content_tag('div', v, :class => "flash #{k}")
189 s << content_tag('div', v, :class => "flash #{k}")
190 end
190 end
191 s
191 s
192 end
192 end
193
193
194 # Renders tabs and their content
194 # Renders tabs and their content
195 def render_tabs(tabs)
195 def render_tabs(tabs)
196 if tabs.any?
196 if tabs.any?
197 render :partial => 'common/tabs', :locals => {:tabs => tabs}
197 render :partial => 'common/tabs', :locals => {:tabs => tabs}
198 else
198 else
199 content_tag 'p', l(:label_no_data), :class => "nodata"
199 content_tag 'p', l(:label_no_data), :class => "nodata"
200 end
200 end
201 end
201 end
202
202
203 # Renders the project quick-jump box
203 # Renders the project quick-jump box
204 def render_project_jump_box
204 def render_project_jump_box
205 # Retrieve them now to avoid a COUNT query
205 # Retrieve them now to avoid a COUNT query
206 projects = User.current.projects.all
206 projects = User.current.projects.all
207 if projects.any?
207 if projects.any?
208 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
208 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
209 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
209 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
210 '<option value="" disabled="disabled">---</option>'
210 '<option value="" disabled="disabled">---</option>'
211 s << project_tree_options_for_select(projects, :selected => @project) do |p|
211 s << project_tree_options_for_select(projects, :selected => @project) do |p|
212 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
212 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
213 end
213 end
214 s << '</select>'
214 s << '</select>'
215 s
215 s
216 end
216 end
217 end
217 end
218
218
219 def project_tree_options_for_select(projects, options = {})
219 def project_tree_options_for_select(projects, options = {})
220 s = ''
220 s = ''
221 project_tree(projects) do |project, level|
221 project_tree(projects) do |project, level|
222 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
222 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
223 tag_options = {:value => project.id}
223 tag_options = {:value => project.id}
224 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
224 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
225 tag_options[:selected] = 'selected'
225 tag_options[:selected] = 'selected'
226 else
226 else
227 tag_options[:selected] = nil
227 tag_options[:selected] = nil
228 end
228 end
229 tag_options.merge!(yield(project)) if block_given?
229 tag_options.merge!(yield(project)) if block_given?
230 s << content_tag('option', name_prefix + h(project), tag_options)
230 s << content_tag('option', name_prefix + h(project), tag_options)
231 end
231 end
232 s
232 s
233 end
233 end
234
234
235 # Yields the given block for each project with its level in the tree
235 # Yields the given block for each project with its level in the tree
236 def project_tree(projects, &block)
236 def project_tree(projects, &block)
237 ancestors = []
237 ancestors = []
238 projects.sort_by(&:lft).each do |project|
238 projects.sort_by(&:lft).each do |project|
239 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
239 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
240 ancestors.pop
240 ancestors.pop
241 end
241 end
242 yield project, ancestors.size
242 yield project, ancestors.size
243 ancestors << project
243 ancestors << project
244 end
244 end
245 end
245 end
246
246
247 def project_nested_ul(projects, &block)
247 def project_nested_ul(projects, &block)
248 s = ''
248 s = ''
249 if projects.any?
249 if projects.any?
250 ancestors = []
250 ancestors = []
251 projects.sort_by(&:lft).each do |project|
251 projects.sort_by(&:lft).each do |project|
252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
253 s << "<ul>\n"
253 s << "<ul>\n"
254 else
254 else
255 ancestors.pop
255 ancestors.pop
256 s << "</li>"
256 s << "</li>"
257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 ancestors.pop
258 ancestors.pop
259 s << "</ul></li>\n"
259 s << "</ul></li>\n"
260 end
260 end
261 end
261 end
262 s << "<li>"
262 s << "<li>"
263 s << yield(project).to_s
263 s << yield(project).to_s
264 ancestors << project
264 ancestors << project
265 end
265 end
266 s << ("</li></ul>\n" * ancestors.size)
266 s << ("</li></ul>\n" * ancestors.size)
267 end
267 end
268 s
268 s
269 end
269 end
270
270
271 def principals_check_box_tags(name, principals)
271 def principals_check_box_tags(name, principals)
272 s = ''
272 s = ''
273 principals.sort.each do |principal|
273 principals.sort.each do |principal|
274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
275 end
275 end
276 s
276 s
277 end
277 end
278
278
279 # Truncates and returns the string as a single line
279 # Truncates and returns the string as a single line
280 def truncate_single_line(string, *args)
280 def truncate_single_line(string, *args)
281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
282 end
282 end
283
283
284 # Truncates at line break after 250 characters or options[:length]
284 # Truncates at line break after 250 characters or options[:length]
285 def truncate_lines(string, options={})
285 def truncate_lines(string, options={})
286 length = options[:length] || 250
286 length = options[:length] || 250
287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
288 "#{$1}..."
288 "#{$1}..."
289 else
289 else
290 string
290 string
291 end
291 end
292 end
292 end
293
293
294 def html_hours(text)
294 def html_hours(text)
295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
296 end
296 end
297
297
298 def authoring(created, author, options={})
298 def authoring(created, author, options={})
299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
300 end
300 end
301
301
302 def time_tag(time)
302 def time_tag(time)
303 text = distance_of_time_in_words(Time.now, time)
303 text = distance_of_time_in_words(Time.now, time)
304 if @project
304 if @project
305 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
305 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
306 else
306 else
307 content_tag('acronym', text, :title => format_time(time))
307 content_tag('acronym', text, :title => format_time(time))
308 end
308 end
309 end
309 end
310
310
311 def syntax_highlight(name, content)
311 def syntax_highlight(name, content)
312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
313 end
313 end
314
314
315 def to_path_param(path)
315 def to_path_param(path)
316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
317 end
317 end
318
318
319 def pagination_links_full(paginator, count=nil, options={})
319 def pagination_links_full(paginator, count=nil, options={})
320 page_param = options.delete(:page_param) || :page
320 page_param = options.delete(:page_param) || :page
321 per_page_links = options.delete(:per_page_links)
321 per_page_links = options.delete(:per_page_links)
322 url_param = params.dup
322 url_param = params.dup
323 # don't reuse query params if filters are present
323 # don't reuse query params if filters are present
324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
325
325
326 html = ''
326 html = ''
327 if paginator.current.previous
327 if paginator.current.previous
328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
329 end
329 end
330
330
331 html << (pagination_links_each(paginator, options) do |n|
331 html << (pagination_links_each(paginator, options) do |n|
332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
333 end || '')
333 end || '')
334
334
335 if paginator.current.next
335 if paginator.current.next
336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
337 end
337 end
338
338
339 unless count.nil?
339 unless count.nil?
340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
342 html << " | #{links}"
342 html << " | #{links}"
343 end
343 end
344 end
344 end
345
345
346 html
346 html
347 end
347 end
348
348
349 def per_page_links(selected=nil)
349 def per_page_links(selected=nil)
350 url_param = params.dup
350 url_param = params.dup
351 url_param.clear if url_param.has_key?(:set_filter)
351 url_param.clear if url_param.has_key?(:set_filter)
352
352
353 links = Setting.per_page_options_array.collect do |n|
353 links = Setting.per_page_options_array.collect do |n|
354 n == selected ? n : link_to_remote(n, {:update => "content",
354 n == selected ? n : link_to_remote(n, {:update => "content",
355 :url => params.dup.merge(:per_page => n),
355 :url => params.dup.merge(:per_page => n),
356 :method => :get},
356 :method => :get},
357 {:href => url_for(url_param.merge(:per_page => n))})
357 {:href => url_for(url_param.merge(:per_page => n))})
358 end
358 end
359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
360 end
360 end
361
361
362 def reorder_links(name, url)
362 def reorder_links(name, url)
363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
367 end
367 end
368
368
369 def breadcrumb(*args)
369 def breadcrumb(*args)
370 elements = args.flatten
370 elements = args.flatten
371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
372 end
372 end
373
373
374 def other_formats_links(&block)
374 def other_formats_links(&block)
375 concat('<p class="other-formats">' + l(:label_export_to))
375 concat('<p class="other-formats">' + l(:label_export_to))
376 yield Redmine::Views::OtherFormatsBuilder.new(self)
376 yield Redmine::Views::OtherFormatsBuilder.new(self)
377 concat('</p>')
377 concat('</p>')
378 end
378 end
379
379
380 def page_header_title
380 def page_header_title
381 if @project.nil? || @project.new_record?
381 if @project.nil? || @project.new_record?
382 h(Setting.app_title)
382 h(Setting.app_title)
383 else
383 else
384 b = []
384 b = []
385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
386 if ancestors.any?
386 if ancestors.any?
387 root = ancestors.shift
387 root = ancestors.shift
388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
389 if ancestors.size > 2
389 if ancestors.size > 2
390 b << '&#8230;'
390 b << '&#8230;'
391 ancestors = ancestors[-2, 2]
391 ancestors = ancestors[-2, 2]
392 end
392 end
393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
394 end
394 end
395 b << h(@project)
395 b << h(@project)
396 b.join(' &#187; ')
396 b.join(' &#187; ')
397 end
397 end
398 end
398 end
399
399
400 def html_title(*args)
400 def html_title(*args)
401 if args.empty?
401 if args.empty?
402 title = []
402 title = []
403 title << @project.name if @project
403 title << @project.name if @project
404 title += @html_title if @html_title
404 title += @html_title if @html_title
405 title << Setting.app_title
405 title << Setting.app_title
406 title.select {|t| !t.blank? }.join(' - ')
406 title.select {|t| !t.blank? }.join(' - ')
407 else
407 else
408 @html_title ||= []
408 @html_title ||= []
409 @html_title += args
409 @html_title += args
410 end
410 end
411 end
411 end
412
412
413 # Returns the theme, controller name, and action as css classes for the
413 # Returns the theme, controller name, and action as css classes for the
414 # HTML body.
414 # HTML body.
415 def body_css_classes
415 def body_css_classes
416 css = []
416 css = []
417 if theme = Redmine::Themes.theme(Setting.ui_theme)
417 if theme = Redmine::Themes.theme(Setting.ui_theme)
418 css << 'theme-' + theme.name
418 css << 'theme-' + theme.name
419 end
419 end
420
420
421 css << 'controller-' + params[:controller]
421 css << 'controller-' + params[:controller]
422 css << 'action-' + params[:action]
422 css << 'action-' + params[:action]
423 css.join(' ')
423 css.join(' ')
424 end
424 end
425
425
426 def accesskey(s)
426 def accesskey(s)
427 Redmine::AccessKeys.key_for s
427 Redmine::AccessKeys.key_for s
428 end
428 end
429
429
430 # Formats text according to system settings.
430 # Formats text according to system settings.
431 # 2 ways to call this method:
431 # 2 ways to call this method:
432 # * with a String: textilizable(text, options)
432 # * with a String: textilizable(text, options)
433 # * with an object and one of its attribute: textilizable(issue, :description, options)
433 # * with an object and one of its attribute: textilizable(issue, :description, options)
434 def textilizable(*args)
434 def textilizable(*args)
435 options = args.last.is_a?(Hash) ? args.pop : {}
435 options = args.last.is_a?(Hash) ? args.pop : {}
436 case args.size
436 case args.size
437 when 1
437 when 1
438 obj = options[:object]
438 obj = options[:object]
439 text = args.shift
439 text = args.shift
440 when 2
440 when 2
441 obj = args.shift
441 obj = args.shift
442 attr = args.shift
442 attr = args.shift
443 text = obj.send(attr).to_s
443 text = obj.send(attr).to_s
444 else
444 else
445 raise ArgumentError, 'invalid arguments to textilizable'
445 raise ArgumentError, 'invalid arguments to textilizable'
446 end
446 end
447 return '' if text.blank?
447 return '' if text.blank?
448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
449 only_path = options.delete(:only_path) == false ? false : true
449 only_path = options.delete(:only_path) == false ? false : true
450
450
451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
452
452
453 parse_non_pre_blocks(text) do |text|
453 parse_non_pre_blocks(text) do |text|
454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
455 send method_name, text, project, obj, attr, only_path, options
455 send method_name, text, project, obj, attr, only_path, options
456 end
456 end
457 end
457 end
458 end
458 end
459
459
460 def parse_non_pre_blocks(text)
460 def parse_non_pre_blocks(text)
461 s = StringScanner.new(text)
461 s = StringScanner.new(text)
462 tags = []
462 tags = []
463 parsed = ''
463 parsed = ''
464 while !s.eos?
464 while !s.eos?
465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
467 if tags.empty?
467 if tags.empty?
468 yield text
468 yield text
469 end
469 end
470 parsed << text
470 parsed << text
471 if tag
471 if tag
472 if closing
472 if closing
473 if tags.last == tag.downcase
473 if tags.last == tag.downcase
474 tags.pop
474 tags.pop
475 end
475 end
476 else
476 else
477 tags << tag.downcase
477 tags << tag.downcase
478 end
478 end
479 parsed << full_tag
479 parsed << full_tag
480 end
480 end
481 end
481 end
482 # Close any non closing tags
482 # Close any non closing tags
483 while tag = tags.pop
483 while tag = tags.pop
484 parsed << "</#{tag}>"
484 parsed << "</#{tag}>"
485 end
485 end
486 parsed
486 parsed
487 end
487 end
488
488
489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
490 # when using an image link, try to use an attachment, if possible
490 # when using an image link, try to use an attachment, if possible
491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
492 attachments = nil
492 attachments = nil
493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
496 # search for the picture in attachments
496 # search for the picture in attachments
497 if found = attachments.detect { |att| att.filename.downcase == filename }
497 if found = attachments.detect { |att| att.filename.downcase == filename }
498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
499 desc = found.description.to_s.gsub('"', '')
499 desc = found.description.to_s.gsub('"', '')
500 if !desc.blank? && alttext.blank?
500 if !desc.blank? && alttext.blank?
501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
502 end
502 end
503 "src=\"#{image_url}\"#{alt}"
503 "src=\"#{image_url}\"#{alt}"
504 else
504 else
505 m
505 m
506 end
506 end
507 end
507 end
508 end
508 end
509 end
509 end
510
510
511 # Wiki links
511 # Wiki links
512 #
512 #
513 # Examples:
513 # Examples:
514 # [[mypage]]
514 # [[mypage]]
515 # [[mypage|mytext]]
515 # [[mypage|mytext]]
516 # wiki links can refer other project wikis, using project name or identifier:
516 # wiki links can refer other project wikis, using project name or identifier:
517 # [[project:]] -> wiki starting page
517 # [[project:]] -> wiki starting page
518 # [[project:|mytext]]
518 # [[project:|mytext]]
519 # [[project:mypage]]
519 # [[project:mypage]]
520 # [[project:mypage|mytext]]
520 # [[project:mypage|mytext]]
521 def parse_wiki_links(text, project, obj, attr, only_path, options)
521 def parse_wiki_links(text, project, obj, attr, only_path, options)
522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
523 link_project = project
523 link_project = project
524 esc, all, page, title = $1, $2, $3, $5
524 esc, all, page, title = $1, $2, $3, $5
525 if esc.nil?
525 if esc.nil?
526 if page =~ /^([^\:]+)\:(.*)$/
526 if page =~ /^([^\:]+)\:(.*)$/
527 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
527 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
528 page = $2
528 page = $2
529 title ||= $1 if page.blank?
529 title ||= $1 if page.blank?
530 end
530 end
531
531
532 if link_project && link_project.wiki
532 if link_project && link_project.wiki
533 # extract anchor
533 # extract anchor
534 anchor = nil
534 anchor = nil
535 if page =~ /^(.+?)\#(.+)$/
535 if page =~ /^(.+?)\#(.+)$/
536 page, anchor = $1, $2
536 page, anchor = $1, $2
537 end
537 end
538 # check if page exists
538 # check if page exists
539 wiki_page = link_project.wiki.find_page(page)
539 wiki_page = link_project.wiki.find_page(page)
540 url = case options[:wiki_links]
540 url = case options[:wiki_links]
541 when :local; "#{title}.html"
541 when :local; "#{title}.html"
542 when :anchor; "##{title}" # used for single-file wiki export
542 when :anchor; "##{title}" # used for single-file wiki export
543 else
543 else
544 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
544 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
545 end
545 end
546 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
546 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
547 else
547 else
548 # project or wiki doesn't exist
548 # project or wiki doesn't exist
549 all
549 all
550 end
550 end
551 else
551 else
552 all
552 all
553 end
553 end
554 end
554 end
555 end
555 end
556
556
557 # Redmine links
557 # Redmine links
558 #
558 #
559 # Examples:
559 # Examples:
560 # Issues:
560 # Issues:
561 # #52 -> Link to issue #52
561 # #52 -> Link to issue #52
562 # Changesets:
562 # Changesets:
563 # r52 -> Link to revision 52
563 # r52 -> Link to revision 52
564 # commit:a85130f -> Link to scmid starting with a85130f
564 # commit:a85130f -> Link to scmid starting with a85130f
565 # Documents:
565 # Documents:
566 # document#17 -> Link to document with id 17
566 # document#17 -> Link to document with id 17
567 # document:Greetings -> Link to the document with title "Greetings"
567 # document:Greetings -> Link to the document with title "Greetings"
568 # document:"Some document" -> Link to the document with title "Some document"
568 # document:"Some document" -> Link to the document with title "Some document"
569 # Versions:
569 # Versions:
570 # version#3 -> Link to version with id 3
570 # version#3 -> Link to version with id 3
571 # version:1.0.0 -> Link to version named "1.0.0"
571 # version:1.0.0 -> Link to version named "1.0.0"
572 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
572 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
573 # Attachments:
573 # Attachments:
574 # attachment:file.zip -> Link to the attachment of the current object named file.zip
574 # attachment:file.zip -> Link to the attachment of the current object named file.zip
575 # Source files:
575 # Source files:
576 # source:some/file -> Link to the file located at /some/file in the project's repository
576 # source:some/file -> Link to the file located at /some/file in the project's repository
577 # source:some/file@52 -> Link to the file's revision 52
577 # source:some/file@52 -> Link to the file's revision 52
578 # source:some/file#L120 -> Link to line 120 of the file
578 # source:some/file#L120 -> Link to line 120 of the file
579 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
579 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
580 # export:some/file -> Force the download of the file
580 # export:some/file -> Force the download of the file
581 # Forum messages:
581 # Forum messages:
582 # message#1218 -> Link to message with id 1218
582 # message#1218 -> Link to message with id 1218
583 def parse_redmine_links(text, project, obj, attr, only_path, options)
583 def parse_redmine_links(text, project, obj, attr, only_path, options)
584 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
584 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
585 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
585 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
586 link = nil
586 link = nil
587 if esc.nil?
587 if esc.nil?
588 if prefix.nil? && sep == 'r'
588 if prefix.nil? && sep == 'r'
589 if project && (changeset = project.changesets.find_by_revision(identifier))
589 if project && (changeset = project.changesets.find_by_revision(identifier))
590 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
590 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
591 :class => 'changeset',
591 :class => 'changeset',
592 :title => truncate_single_line(changeset.comments, :length => 100))
592 :title => truncate_single_line(changeset.comments, :length => 100))
593 end
593 end
594 elsif sep == '#'
594 elsif sep == '#'
595 oid = identifier.to_i
595 oid = identifier.to_i
596 case prefix
596 case prefix
597 when nil
597 when nil
598 if issue = Issue.visible.find_by_id(oid, :include => :status)
598 if issue = Issue.visible.find_by_id(oid, :include => :status)
599 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
599 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
600 :class => issue.css_classes,
600 :class => issue.css_classes,
601 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
601 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
602 end
602 end
603 when 'document'
603 when 'document'
604 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
604 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
606 :class => 'document'
606 :class => 'document'
607 end
607 end
608 when 'version'
608 when 'version'
609 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
609 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
611 :class => 'version'
611 :class => 'version'
612 end
612 end
613 when 'message'
613 when 'message'
614 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
614 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
615 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
615 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
616 :controller => 'messages',
616 :controller => 'messages',
617 :action => 'show',
617 :action => 'show',
618 :board_id => message.board,
618 :board_id => message.board,
619 :id => message.root,
619 :id => message.root,
620 :anchor => (message.parent ? "message-#{message.id}" : nil)},
620 :anchor => (message.parent ? "message-#{message.id}" : nil)},
621 :class => 'message'
621 :class => 'message'
622 end
622 end
623 when 'project'
623 when 'project'
624 if p = Project.visible.find_by_id(oid)
624 if p = Project.visible.find_by_id(oid)
625 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
625 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
626 end
626 end
627 end
627 end
628 elsif sep == ':'
628 elsif sep == ':'
629 # removes the double quotes if any
629 # removes the double quotes if any
630 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
630 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
631 case prefix
631 case prefix
632 when 'document'
632 when 'document'
633 if project && document = project.documents.find_by_title(name)
633 if project && document = project.documents.find_by_title(name)
634 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
634 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
635 :class => 'document'
635 :class => 'document'
636 end
636 end
637 when 'version'
637 when 'version'
638 if project && version = project.versions.find_by_name(name)
638 if project && version = project.versions.find_by_name(name)
639 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
639 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
640 :class => 'version'
640 :class => 'version'
641 end
641 end
642 when 'commit'
642 when 'commit'
643 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
643 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
644 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
644 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
645 :class => 'changeset',
645 :class => 'changeset',
646 :title => truncate_single_line(changeset.comments, :length => 100)
646 :title => truncate_single_line(changeset.comments, :length => 100)
647 end
647 end
648 when 'source', 'export'
648 when 'source', 'export'
649 if project && project.repository
649 if project && project.repository
650 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
650 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
651 path, rev, anchor = $1, $3, $5
651 path, rev, anchor = $1, $3, $5
652 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
652 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
653 :path => to_path_param(path),
653 :path => to_path_param(path),
654 :rev => rev,
654 :rev => rev,
655 :anchor => anchor,
655 :anchor => anchor,
656 :format => (prefix == 'export' ? 'raw' : nil)},
656 :format => (prefix == 'export' ? 'raw' : nil)},
657 :class => (prefix == 'export' ? 'source download' : 'source')
657 :class => (prefix == 'export' ? 'source download' : 'source')
658 end
658 end
659 when 'attachment'
659 when 'attachment'
660 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
660 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
661 if attachments && attachment = attachments.detect {|a| a.filename == name }
661 if attachments && attachment = attachments.detect {|a| a.filename == name }
662 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
662 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
663 :class => 'attachment'
663 :class => 'attachment'
664 end
664 end
665 when 'project'
665 when 'project'
666 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
666 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
667 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
667 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
668 end
668 end
669 end
669 end
670 end
670 end
671 end
671 end
672 leading + (link || "#{prefix}#{sep}#{identifier}")
672 leading + (link || "#{prefix}#{sep}#{identifier}")
673 end
673 end
674 end
674 end
675
675
676 # Same as Rails' simple_format helper without using paragraphs
676 # Same as Rails' simple_format helper without using paragraphs
677 def simple_format_without_paragraph(text)
677 def simple_format_without_paragraph(text)
678 text.to_s.
678 text.to_s.
679 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
679 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
680 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
680 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
681 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
681 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
682 end
682 end
683
683
684 def lang_options_for_select(blank=true)
684 def lang_options_for_select(blank=true)
685 (blank ? [["(auto)", ""]] : []) +
685 (blank ? [["(auto)", ""]] : []) +
686 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
686 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
687 end
687 end
688
688
689 def label_tag_for(name, option_tags = nil, options = {})
689 def label_tag_for(name, option_tags = nil, options = {})
690 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
690 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
691 content_tag("label", label_text)
691 content_tag("label", label_text)
692 end
692 end
693
693
694 def labelled_tabular_form_for(name, object, options, &proc)
694 def labelled_tabular_form_for(name, object, options, &proc)
695 options[:html] ||= {}
695 options[:html] ||= {}
696 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
696 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
697 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
697 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
698 end
698 end
699
699
700 def back_url_hidden_field_tag
700 def back_url_hidden_field_tag
701 back_url = params[:back_url] || request.env['HTTP_REFERER']
701 back_url = params[:back_url] || request.env['HTTP_REFERER']
702 back_url = CGI.unescape(back_url.to_s)
702 back_url = CGI.unescape(back_url.to_s)
703 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
703 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
704 end
704 end
705
705
706 def check_all_links(form_name)
706 def check_all_links(form_name)
707 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
707 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
708 " | " +
708 " | " +
709 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
709 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
710 end
710 end
711
711
712 def progress_bar(pcts, options={})
712 def progress_bar(pcts, options={})
713 pcts = [pcts, pcts] unless pcts.is_a?(Array)
713 pcts = [pcts, pcts] unless pcts.is_a?(Array)
714 pcts = pcts.collect(&:round)
714 pcts = pcts.collect(&:round)
715 pcts[1] = pcts[1] - pcts[0]
715 pcts[1] = pcts[1] - pcts[0]
716 pcts << (100 - pcts[1] - pcts[0])
716 pcts << (100 - pcts[1] - pcts[0])
717 width = options[:width] || '100px;'
717 width = options[:width] || '100px;'
718 legend = options[:legend] || ''
718 legend = options[:legend] || ''
719 content_tag('table',
719 content_tag('table',
720 content_tag('tr',
720 content_tag('tr',
721 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
721 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
722 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
722 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
723 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
723 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
724 ), :class => 'progress', :style => "width: #{width};") +
724 ), :class => 'progress', :style => "width: #{width};") +
725 content_tag('p', legend, :class => 'pourcent')
725 content_tag('p', legend, :class => 'pourcent')
726 end
726 end
727
727
728 def checked_image(checked=true)
728 def checked_image(checked=true)
729 if checked
729 if checked
730 image_tag 'toggle_check.png'
730 image_tag 'toggle_check.png'
731 end
731 end
732 end
732 end
733
733
734 def context_menu(url)
734 def context_menu(url)
735 unless @context_menu_included
735 unless @context_menu_included
736 content_for :header_tags do
736 content_for :header_tags do
737 javascript_include_tag('context_menu') +
737 javascript_include_tag('context_menu') +
738 stylesheet_link_tag('context_menu')
738 stylesheet_link_tag('context_menu')
739 end
739 end
740 if l(:direction) == 'rtl'
741 content_for :header_tags do
742 stylesheet_link_tag('context_menu_rtl')
743 end
744 end
740 @context_menu_included = true
745 @context_menu_included = true
741 end
746 end
742 javascript_tag "new ContextMenu('#{ url_for(url) }')"
747 javascript_tag "new ContextMenu('#{ url_for(url) }')"
743 end
748 end
744
749
745 def context_menu_link(name, url, options={})
750 def context_menu_link(name, url, options={})
746 options[:class] ||= ''
751 options[:class] ||= ''
747 if options.delete(:selected)
752 if options.delete(:selected)
748 options[:class] << ' icon-checked disabled'
753 options[:class] << ' icon-checked disabled'
749 options[:disabled] = true
754 options[:disabled] = true
750 end
755 end
751 if options.delete(:disabled)
756 if options.delete(:disabled)
752 options.delete(:method)
757 options.delete(:method)
753 options.delete(:confirm)
758 options.delete(:confirm)
754 options.delete(:onclick)
759 options.delete(:onclick)
755 options[:class] << ' disabled'
760 options[:class] << ' disabled'
756 url = '#'
761 url = '#'
757 end
762 end
758 link_to name, url, options
763 link_to name, url, options
759 end
764 end
760
765
761 def calendar_for(field_id)
766 def calendar_for(field_id)
762 include_calendar_headers_tags
767 include_calendar_headers_tags
763 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
768 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
764 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
769 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
765 end
770 end
766
771
767 def include_calendar_headers_tags
772 def include_calendar_headers_tags
768 unless @calendar_headers_tags_included
773 unless @calendar_headers_tags_included
769 @calendar_headers_tags_included = true
774 @calendar_headers_tags_included = true
770 content_for :header_tags do
775 content_for :header_tags do
771 start_of_week = case Setting.start_of_week.to_i
776 start_of_week = case Setting.start_of_week.to_i
772 when 1
777 when 1
773 'Calendar._FD = 1;' # Monday
778 'Calendar._FD = 1;' # Monday
774 when 7
779 when 7
775 'Calendar._FD = 0;' # Sunday
780 'Calendar._FD = 0;' # Sunday
776 else
781 else
777 '' # use language
782 '' # use language
778 end
783 end
779
784
780 javascript_include_tag('calendar/calendar') +
785 javascript_include_tag('calendar/calendar') +
781 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
786 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
782 javascript_tag(start_of_week) +
787 javascript_tag(start_of_week) +
783 javascript_include_tag('calendar/calendar-setup') +
788 javascript_include_tag('calendar/calendar-setup') +
784 stylesheet_link_tag('calendar')
789 stylesheet_link_tag('calendar')
785 end
790 end
786 end
791 end
787 end
792 end
788
793
789 def content_for(name, content = nil, &block)
794 def content_for(name, content = nil, &block)
790 @has_content ||= {}
795 @has_content ||= {}
791 @has_content[name] = true
796 @has_content[name] = true
792 super(name, content, &block)
797 super(name, content, &block)
793 end
798 end
794
799
795 def has_content?(name)
800 def has_content?(name)
796 (@has_content && @has_content[name]) || false
801 (@has_content && @has_content[name]) || false
797 end
802 end
798
803
799 # Returns the avatar image tag for the given +user+ if avatars are enabled
804 # Returns the avatar image tag for the given +user+ if avatars are enabled
800 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
805 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
801 def avatar(user, options = { })
806 def avatar(user, options = { })
802 if Setting.gravatar_enabled?
807 if Setting.gravatar_enabled?
803 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
808 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
804 email = nil
809 email = nil
805 if user.respond_to?(:mail)
810 if user.respond_to?(:mail)
806 email = user.mail
811 email = user.mail
807 elsif user.to_s =~ %r{<(.+?)>}
812 elsif user.to_s =~ %r{<(.+?)>}
808 email = $1
813 email = $1
809 end
814 end
810 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
815 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
811 end
816 end
812 end
817 end
813
818
814 def favicon
819 def favicon
815 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
820 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
816 end
821 end
817
822
818 private
823 private
819
824
820 def wiki_helper
825 def wiki_helper
821 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
826 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
822 extend helper
827 extend helper
823 return self
828 return self
824 end
829 end
825
830
826 def link_to_remote_content_update(text, url_params)
831 def link_to_remote_content_update(text, url_params)
827 link_to_remote(text,
832 link_to_remote(text,
828 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
833 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
829 {:href => url_for(:params => url_params)}
834 {:href => url_for(:params => url_params)}
830 )
835 )
831 end
836 end
832
837
833 end
838 end
@@ -1,133 +1,134
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
3 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
4
4
5 <div class="<%= @issue.css_classes %> details">
5 <div class="<%= @issue.css_classes %> details">
6 <%= avatar(@issue.author, :size => "50") %>
6 <%= avatar(@issue.author, :size => "50") %>
7
7
8 <div class="subject">
8 <div class="subject">
9 <%= render_issue_subject_with_tree(@issue) %>
9 <%= render_issue_subject_with_tree(@issue) %>
10 </div>
10 </div>
11 <p class="author">
11 <p class="author">
12 <%= authoring @issue.created_on, @issue.author %>.
12 <%= authoring @issue.created_on, @issue.author %>.
13 <% if @issue.created_on != @issue.updated_on %>
13 <% if @issue.created_on != @issue.updated_on %>
14 <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
14 <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
15 <% end %>
15 <% end %>
16 </p>
16 </p>
17
17
18 <table class="attributes">
18 <table class="attributes">
19 <tr>
19 <tr>
20 <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
20 <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
21 <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
21 <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
22 </tr>
22 </tr>
23 <tr>
23 <tr>
24 <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
24 <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
25 <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
25 <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
26 </tr>
26 </tr>
27 <tr>
27 <tr>
28 <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
28 <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
29 <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
29 <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
30 </tr>
30 </tr>
31 <tr>
31 <tr>
32 <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
32 <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
33 <% if User.current.allowed_to?(:view_time_entries, @project) %>
33 <% if User.current.allowed_to?(:view_time_entries, @project) %>
34 <th class="spent-time"><%=l(:label_spent_time)%>:</th>
34 <th class="spent-time"><%=l(:label_spent_time)%>:</th>
35 <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
35 <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
36 <% end %>
36 <% end %>
37 </tr>
37 </tr>
38 <tr>
38 <tr>
39 <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
39 <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
40 <% if @issue.estimated_hours %>
40 <% if @issue.estimated_hours %>
41 <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
41 <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
42 <% end %>
42 <% end %>
43 </tr>
43 </tr>
44 <%= render_custom_fields_rows(@issue) %>
44 <%= render_custom_fields_rows(@issue) %>
45 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
45 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
46 </table>
46 </table>
47 <hr />
47 <hr />
48
48
49 <div class="contextual">
49 <div class="contextual">
50 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %>
50 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %>
51 </div>
51 </div>
52
52
53 <p><strong><%=l(:field_description)%></strong></p>
53 <p><strong><%=l(:field_description)%></strong></p>
54 <div class="wiki">
54 <div class="wiki">
55 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
55 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
56 </div>
56 </div>
57
57
58 <%= link_to_attachments @issue %>
58 <%= link_to_attachments @issue %>
59
59
60 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
60 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
61
61
62 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
62 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
63 <hr />
63 <hr />
64 <div id="issue_tree">
64 <div id="issue_tree">
65 <div class="contextual">
65 <div class="contextual">
66 <%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
66 <%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
67 </div>
67 </div>
68 <p><strong><%=l(:label_subtask_plural)%></strong></p>
68 <p><strong><%=l(:label_subtask_plural)%></strong></p>
69 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
69 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
70 </div>
70 </div>
71 <% end %>
71 <% end %>
72
72
73 <% if authorize_for('issue_relations', 'new') || @issue.relations.present? %>
73 <% if authorize_for('issue_relations', 'new') || @issue.relations.present? %>
74 <hr />
74 <hr />
75 <div id="relations">
75 <div id="relations">
76 <%= render :partial => 'relations' %>
76 <%= render :partial => 'relations' %>
77 </div>
77 </div>
78 <% end %>
78 <% end %>
79
79
80 </div>
80 </div>
81
81
82 <% if @changesets.present? %>
82 <% if @changesets.present? %>
83 <div id="issue-changesets">
83 <div id="issue-changesets">
84 <h3><%=l(:label_associated_revisions)%></h3>
84 <h3><%=l(:label_associated_revisions)%></h3>
85 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
85 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
86 </div>
86 </div>
87 <% end %>
87 <% end %>
88
88
89 <% if @journals.present? %>
89 <% if @journals.present? %>
90 <div id="history">
90 <div id="history">
91 <h3><%=l(:label_history)%></h3>
91 <h3><%=l(:label_history)%></h3>
92 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
92 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
93 </div>
93 </div>
94 <% end %>
94 <% end %>
95
95
96
96
97 <div style="clear: both;"></div>
97 <div style="clear: both;"></div>
98 <%= render :partial => 'action_menu', :locals => {:replace_watcher => 'watcher2' } %>
98 <%= render :partial => 'action_menu', :locals => {:replace_watcher => 'watcher2' } %>
99
99
100 <div style="clear: both;"></div>
100 <div style="clear: both;"></div>
101 <% if authorize_for('issues', 'edit') %>
101 <% if authorize_for('issues', 'edit') %>
102 <div id="update" style="display:none;">
102 <div id="update" style="display:none;">
103 <h3><%= l(:button_update) %></h3>
103 <h3><%= l(:button_update) %></h3>
104 <%= render :partial => 'edit' %>
104 <%= render :partial => 'edit' %>
105 </div>
105 </div>
106 <% end %>
106 <% end %>
107
107
108 <% other_formats_links do |f| %>
108 <% other_formats_links do |f| %>
109 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
109 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
110 <%= f.link_to 'PDF' %>
110 <%= f.link_to 'PDF' %>
111 <% end %>
111 <% end %>
112
112
113 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
113 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
114
114
115 <% content_for :sidebar do %>
115 <% content_for :sidebar do %>
116 <%= render :partial => 'issues/sidebar' %>
116 <%= render :partial => 'issues/sidebar' %>
117
117
118 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
118 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
119 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
119 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
120 <div id="watchers">
120 <div id="watchers">
121 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
121 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
122 </div>
122 </div>
123 <% end %>
123 <% end %>
124 <% end %>
124 <% end %>
125
125
126 <% content_for :header_tags do %>
126 <% content_for :header_tags do %>
127 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
127 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
128 <%= stylesheet_link_tag 'scm' %>
128 <%= stylesheet_link_tag 'scm' %>
129 <%= javascript_include_tag 'context_menu' %>
129 <%= javascript_include_tag 'context_menu' %>
130 <%= stylesheet_link_tag 'context_menu' %>
130 <%= stylesheet_link_tag 'context_menu' %>
131 <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
131 <% end %>
132 <% end %>
132 <div id="context-menu" style="display: none;"></div>
133 <div id="context-menu" style="display: none;"></div>
133 <%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>
134 <%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>
@@ -1,66 +1,62
1 body, #wrapper { direction: rtl;}
1 body, #wrapper { direction: rtl;}
2
2
3 #quick-search { float: left; }
3 #quick-search { float: left; }
4 #main-menu { margin-left: -500px; left: auto; right: 6px; margin-right: 0px;}
4 #main-menu { margin-left: -500px; left: auto; right: 6px; margin-right: 0px;}
5 #main-menu li { float: right; }
5 #main-menu li { float: right; }
6 #top-menu ul { float: right; }
6 #top-menu ul { float: right; }
7 #account { float: left; }
7 #account { float: left; }
8 #top-menu #loggedas { float: left; }
8 #top-menu #loggedas { float: left; }
9 #top-menu li { float: right; }
9 #top-menu li { float: right; }
10 .tabular label.floating
10 .tabular label.floating
11 {
11 {
12 margin-right: 0;
12 margin-right: 0;
13 margin-left: auto;
13 margin-left: auto;
14 text-align: right;
14 text-align: right;
15 }
15 }
16 .tabular label
16 .tabular label
17 {
17 {
18 float: right;
18 float: right;
19 margin-left: auto;
19 margin-left: auto;
20 }
20 }
21 .tabular p
21 .tabular p
22 {
22 {
23 clear: right;
23 clear: right;
24 }
24 }
25 .tabular label.block { text-align: right; }
25 .tabular label.block { text-align: right; }
26 .icon
26 .icon
27 {
27 {
28 background-position: 100% 40%;
28 background-position: 100% 40%;
29 padding-right: 20px;
29 padding-right: 20px;
30 padding-left: 0px;
30 padding-left: 0px;
31 }
31 }
32 div#activity dt, #search-results dt
32 div#activity dt, #search-results dt
33 {
33 {
34 background-position: 100% 50%;
34 background-position: 100% 50%;
35 padding-right: 20px;
35 padding-right: 20px;
36 padding-left: 0px;
36 padding-left: 0px;
37 }
37 }
38 #content .tabs ul li { float: right; }
38 #content .tabs ul li { float: right; }
39 #content .tabs ul { padding-left: auto; padding-right: 1em; }
39 #content .tabs ul { padding-left: auto; padding-right: 1em; }
40 table.progress { float: right; }
40 table.progress { float: right; }
41 .contextual { float: left; }
41 .contextual { float: left; }
42 .icon22 { background-position: 100% 40%; padding-right: 26px; padding-left: auto; }
42 .icon22 { background-position: 100% 40%; padding-right: 26px; padding-left: auto; }
43 h3, .wiki h2 { padding: 10px 2px 1px 0; }
43 h3, .wiki h2 { padding: 10px 2px 1px 0; }
44 .tooltip span.tip { text-align: right; }
44 .tooltip span.tip { text-align: right; }
45 tr.issue td.subject { text-align: right; }
45 tr.issue td.subject { text-align: right; }
46 tr.time-entry td.subject, tr.time-entry td.comments { text-align: right; }
46 tr.time-entry td.subject, tr.time-entry td.comments { text-align: right; }
47 #sidebar { float: left; }
47 #sidebar { float: left; }
48 #main.nosidebar #content { border-width: 1px; border-style: solid; border-color: #D7D7D7 #BBBBBB #BBBBBB #D7D7D7;}
48 #main.nosidebar #content { border-width: 1px; border-style: solid; border-color: #D7D7D7 #BBBBBB #BBBBBB #D7D7D7;}
49 .tabular.settings label { margin-left: auto; }
49 .tabular.settings label { margin-left: auto; }
50 .splitcontentleft { float: right; }
50 .splitcontentleft { float: right; }
51 .splitcontentright { float: left; }
51 .splitcontentright { float: left; }
52 p.progress-info { clear: right; }
52 p.progress-info { clear: right; }
53 table.list td.buttons a { padding-right: 20px; }
53 table.list td.buttons a { padding-right: 20px; }
54 .filecontent { direction: ltr; }
54 .filecontent { direction: ltr; }
55 .entries { direction: ltr; }
55 .entries { direction: ltr; }
56 .changeset-changes { direction: ltr; padding-left: 2em }
56 .changeset-changes { direction: ltr; padding-left: 2em }
57 .changesets { direction: ltr; }
57 .changesets { direction: ltr; }
58 div#issue-changesets { float: left; margin-right: 1em; margin-left: 0 }
58 div#issue-changesets { float: left; margin-right: 1em; margin-left: 0 }
59 #activity dt, .journal { clear: right; }
59 #activity dt, .journal { clear: right; }
60 .journal-link { float: left; }
60 .journal-link { float: left; }
61 div.wiki pre { direction: ltr; }
61 div.wiki pre { direction: ltr; }
62
62
63 #context-menu li.folder ul { left:auto; right:168px; }
64 #context-menu li.folder>ul { left:auto; right:148px; }
65
66 #context-menu li a.submenu { background:url("../images/bullet_arrow_left.png") left no-repeat; }
General Comments 0
You need to be logged in to leave comments. Login now