##// END OF EJS Templates
Improved responsiveness for versions and roadmap (#19097)....
Jean-Philippe Lang -
r14469:ecb1f660ac4d
parent child
Show More
@@ -1,1338 +1,1337
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
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 include Redmine::Pagination::Helper
27 include Redmine::Pagination::Helper
28 include Redmine::SudoMode::Helper
28 include Redmine::SudoMode::Helper
29 include Redmine::Themes::Helper
29 include Redmine::Themes::Helper
30 include Redmine::Hook::Helper
30 include Redmine::Hook::Helper
31
31
32 extend Forwardable
32 extend Forwardable
33 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
33 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
34
34
35 # Return true if user is authorized for controller/action, otherwise false
35 # Return true if user is authorized for controller/action, otherwise false
36 def authorize_for(controller, action)
36 def authorize_for(controller, action)
37 User.current.allowed_to?({:controller => controller, :action => action}, @project)
37 User.current.allowed_to?({:controller => controller, :action => action}, @project)
38 end
38 end
39
39
40 # Display a link if user is authorized
40 # Display a link if user is authorized
41 #
41 #
42 # @param [String] name Anchor text (passed to link_to)
42 # @param [String] name Anchor text (passed to link_to)
43 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
43 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
44 # @param [optional, Hash] html_options Options passed to link_to
44 # @param [optional, Hash] html_options Options passed to link_to
45 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
45 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
46 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
46 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
47 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
47 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
48 end
48 end
49
49
50 # Displays a link to user's account page if active
50 # Displays a link to user's account page if active
51 def link_to_user(user, options={})
51 def link_to_user(user, options={})
52 if user.is_a?(User)
52 if user.is_a?(User)
53 name = h(user.name(options[:format]))
53 name = h(user.name(options[:format]))
54 if user.active? || (User.current.admin? && user.logged?)
54 if user.active? || (User.current.admin? && user.logged?)
55 link_to name, user_path(user), :class => user.css_classes
55 link_to name, user_path(user), :class => user.css_classes
56 else
56 else
57 name
57 name
58 end
58 end
59 else
59 else
60 h(user.to_s)
60 h(user.to_s)
61 end
61 end
62 end
62 end
63
63
64 # Displays a link to +issue+ with its subject.
64 # Displays a link to +issue+ with its subject.
65 # Examples:
65 # Examples:
66 #
66 #
67 # link_to_issue(issue) # => Defect #6: This is the subject
67 # link_to_issue(issue) # => Defect #6: This is the subject
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
68 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
69 # link_to_issue(issue, :subject => false) # => Defect #6
69 # link_to_issue(issue, :subject => false) # => Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
70 # link_to_issue(issue, :project => true) # => Foo - Defect #6
71 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
71 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
72 #
72 #
73 def link_to_issue(issue, options={})
73 def link_to_issue(issue, options={})
74 title = nil
74 title = nil
75 subject = nil
75 subject = nil
76 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
76 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
77 if options[:subject] == false
77 if options[:subject] == false
78 title = issue.subject.truncate(60)
78 title = issue.subject.truncate(60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if truncate_length = options[:truncate]
81 if truncate_length = options[:truncate]
82 subject = subject.truncate(truncate_length)
82 subject = subject.truncate(truncate_length)
83 end
83 end
84 end
84 end
85 only_path = options[:only_path].nil? ? true : options[:only_path]
85 only_path = options[:only_path].nil? ? true : options[:only_path]
86 s = link_to(text, issue_url(issue, :only_path => only_path),
86 s = link_to(text, issue_url(issue, :only_path => only_path),
87 :class => issue.css_classes, :title => title)
87 :class => issue.css_classes, :title => title)
88 s << h(": #{subject}") if subject
88 s << h(": #{subject}") if subject
89 s = h("#{issue.project} - ") + s if options[:project]
89 s = h("#{issue.project} - ") + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 route_method = options.delete(:download) ? :download_named_attachment_url : :named_attachment_url
99 route_method = options.delete(:download) ? :download_named_attachment_url : :named_attachment_url
100 html_options = options.slice!(:only_path)
100 html_options = options.slice!(:only_path)
101 options[:only_path] = true unless options.key?(:only_path)
101 options[:only_path] = true unless options.key?(:only_path)
102 url = send(route_method, attachment, attachment.filename, options)
102 url = send(route_method, attachment, attachment.filename, options)
103 link_to text, url, html_options
103 link_to text, url, html_options
104 end
104 end
105
105
106 # Generates a link to a SCM revision
106 # Generates a link to a SCM revision
107 # Options:
107 # Options:
108 # * :text - Link text (default to the formatted revision)
108 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, repository, options={})
109 def link_to_revision(revision, repository, options={})
110 if repository.is_a?(Project)
110 if repository.is_a?(Project)
111 repository = repository.repository
111 repository = repository.repository
112 end
112 end
113 text = options.delete(:text) || format_revision(revision)
113 text = options.delete(:text) || format_revision(revision)
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
115 link_to(
115 link_to(
116 h(text),
116 h(text),
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
118 :title => l(:label_revision_id, format_revision(revision)),
118 :title => l(:label_revision_id, format_revision(revision)),
119 :accesskey => options[:accesskey]
119 :accesskey => options[:accesskey]
120 )
120 )
121 end
121 end
122
122
123 # Generates a link to a message
123 # Generates a link to a message
124 def link_to_message(message, options={}, html_options = nil)
124 def link_to_message(message, options={}, html_options = nil)
125 link_to(
125 link_to(
126 message.subject.truncate(60),
126 message.subject.truncate(60),
127 board_message_url(message.board_id, message.parent_id || message.id, {
127 board_message_url(message.board_id, message.parent_id || message.id, {
128 :r => (message.parent_id && message.id),
128 :r => (message.parent_id && message.id),
129 :anchor => (message.parent_id ? "message-#{message.id}" : nil),
129 :anchor => (message.parent_id ? "message-#{message.id}" : nil),
130 :only_path => true
130 :only_path => true
131 }.merge(options)),
131 }.merge(options)),
132 html_options
132 html_options
133 )
133 )
134 end
134 end
135
135
136 # Generates a link to a project if active
136 # Generates a link to a project if active
137 # Examples:
137 # Examples:
138 #
138 #
139 # link_to_project(project) # => link to the specified project overview
139 # link_to_project(project) # => link to the specified project overview
140 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
140 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
141 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
141 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
142 #
142 #
143 def link_to_project(project, options={}, html_options = nil)
143 def link_to_project(project, options={}, html_options = nil)
144 if project.archived?
144 if project.archived?
145 h(project.name)
145 h(project.name)
146 else
146 else
147 link_to project.name,
147 link_to project.name,
148 project_url(project, {:only_path => true}.merge(options)),
148 project_url(project, {:only_path => true}.merge(options)),
149 html_options
149 html_options
150 end
150 end
151 end
151 end
152
152
153 # Generates a link to a project settings if active
153 # Generates a link to a project settings if active
154 def link_to_project_settings(project, options={}, html_options=nil)
154 def link_to_project_settings(project, options={}, html_options=nil)
155 if project.active?
155 if project.active?
156 link_to project.name, settings_project_path(project, options), html_options
156 link_to project.name, settings_project_path(project, options), html_options
157 elsif project.archived?
157 elsif project.archived?
158 h(project.name)
158 h(project.name)
159 else
159 else
160 link_to project.name, project_path(project, options), html_options
160 link_to project.name, project_path(project, options), html_options
161 end
161 end
162 end
162 end
163
163
164 # Generates a link to a version
164 # Generates a link to a version
165 def link_to_version(version, options = {})
165 def link_to_version(version, options = {})
166 return '' unless version && version.is_a?(Version)
166 return '' unless version && version.is_a?(Version)
167 options = {:title => format_date(version.effective_date)}.merge(options)
167 options = {:title => format_date(version.effective_date)}.merge(options)
168 link_to_if version.visible?, format_version_name(version), version_path(version), options
168 link_to_if version.visible?, format_version_name(version), version_path(version), options
169 end
169 end
170
170
171 # Helper that formats object for html or text rendering
171 # Helper that formats object for html or text rendering
172 def format_object(object, html=true, &block)
172 def format_object(object, html=true, &block)
173 if block_given?
173 if block_given?
174 object = yield object
174 object = yield object
175 end
175 end
176 case object.class.name
176 case object.class.name
177 when 'Array'
177 when 'Array'
178 object.map {|o| format_object(o, html)}.join(', ').html_safe
178 object.map {|o| format_object(o, html)}.join(', ').html_safe
179 when 'Time'
179 when 'Time'
180 format_time(object)
180 format_time(object)
181 when 'Date'
181 when 'Date'
182 format_date(object)
182 format_date(object)
183 when 'Fixnum'
183 when 'Fixnum'
184 object.to_s
184 object.to_s
185 when 'Float'
185 when 'Float'
186 sprintf "%.2f", object
186 sprintf "%.2f", object
187 when 'User'
187 when 'User'
188 html ? link_to_user(object) : object.to_s
188 html ? link_to_user(object) : object.to_s
189 when 'Project'
189 when 'Project'
190 html ? link_to_project(object) : object.to_s
190 html ? link_to_project(object) : object.to_s
191 when 'Version'
191 when 'Version'
192 html ? link_to_version(object) : object.to_s
192 html ? link_to_version(object) : object.to_s
193 when 'TrueClass'
193 when 'TrueClass'
194 l(:general_text_Yes)
194 l(:general_text_Yes)
195 when 'FalseClass'
195 when 'FalseClass'
196 l(:general_text_No)
196 l(:general_text_No)
197 when 'Issue'
197 when 'Issue'
198 object.visible? && html ? link_to_issue(object) : "##{object.id}"
198 object.visible? && html ? link_to_issue(object) : "##{object.id}"
199 when 'CustomValue', 'CustomFieldValue'
199 when 'CustomValue', 'CustomFieldValue'
200 if object.custom_field
200 if object.custom_field
201 f = object.custom_field.format.formatted_custom_value(self, object, html)
201 f = object.custom_field.format.formatted_custom_value(self, object, html)
202 if f.nil? || f.is_a?(String)
202 if f.nil? || f.is_a?(String)
203 f
203 f
204 else
204 else
205 format_object(f, html, &block)
205 format_object(f, html, &block)
206 end
206 end
207 else
207 else
208 object.value.to_s
208 object.value.to_s
209 end
209 end
210 else
210 else
211 html ? h(object) : object.to_s
211 html ? h(object) : object.to_s
212 end
212 end
213 end
213 end
214
214
215 def wiki_page_path(page, options={})
215 def wiki_page_path(page, options={})
216 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
216 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
217 end
217 end
218
218
219 def thumbnail_tag(attachment)
219 def thumbnail_tag(attachment)
220 link_to image_tag(thumbnail_path(attachment)),
220 link_to image_tag(thumbnail_path(attachment)),
221 named_attachment_path(attachment, attachment.filename),
221 named_attachment_path(attachment, attachment.filename),
222 :title => attachment.filename
222 :title => attachment.filename
223 end
223 end
224
224
225 def toggle_link(name, id, options={})
225 def toggle_link(name, id, options={})
226 onclick = "$('##{id}').toggle(); "
226 onclick = "$('##{id}').toggle(); "
227 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
227 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
228 onclick << "return false;"
228 onclick << "return false;"
229 link_to(name, "#", :onclick => onclick)
229 link_to(name, "#", :onclick => onclick)
230 end
230 end
231
231
232 def format_activity_title(text)
232 def format_activity_title(text)
233 h(truncate_single_line_raw(text, 100))
233 h(truncate_single_line_raw(text, 100))
234 end
234 end
235
235
236 def format_activity_day(date)
236 def format_activity_day(date)
237 date == User.current.today ? l(:label_today).titleize : format_date(date)
237 date == User.current.today ? l(:label_today).titleize : format_date(date)
238 end
238 end
239
239
240 def format_activity_description(text)
240 def format_activity_description(text)
241 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
241 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
242 ).gsub(/[\r\n]+/, "<br />").html_safe
242 ).gsub(/[\r\n]+/, "<br />").html_safe
243 end
243 end
244
244
245 def format_version_name(version)
245 def format_version_name(version)
246 if version.project == @project
246 if version.project == @project
247 h(version)
247 h(version)
248 else
248 else
249 h("#{version.project} - #{version}")
249 h("#{version.project} - #{version}")
250 end
250 end
251 end
251 end
252
252
253 def due_date_distance_in_words(date)
253 def due_date_distance_in_words(date)
254 if date
254 if date
255 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
255 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
256 end
256 end
257 end
257 end
258
258
259 # Renders a tree of projects as a nested set of unordered lists
259 # Renders a tree of projects as a nested set of unordered lists
260 # The given collection may be a subset of the whole project tree
260 # The given collection may be a subset of the whole project tree
261 # (eg. some intermediate nodes are private and can not be seen)
261 # (eg. some intermediate nodes are private and can not be seen)
262 def render_project_nested_lists(projects, &block)
262 def render_project_nested_lists(projects, &block)
263 s = ''
263 s = ''
264 if projects.any?
264 if projects.any?
265 ancestors = []
265 ancestors = []
266 original_project = @project
266 original_project = @project
267 projects.sort_by(&:lft).each do |project|
267 projects.sort_by(&:lft).each do |project|
268 # set the project environment to please macros.
268 # set the project environment to please macros.
269 @project = project
269 @project = project
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
271 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
272 else
272 else
273 ancestors.pop
273 ancestors.pop
274 s << "</li>"
274 s << "</li>"
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 ancestors.pop
276 ancestors.pop
277 s << "</ul></li>\n"
277 s << "</ul></li>\n"
278 end
278 end
279 end
279 end
280 classes = (ancestors.empty? ? 'root' : 'child')
280 classes = (ancestors.empty? ? 'root' : 'child')
281 s << "<li class='#{classes}'><div class='#{classes}'>"
281 s << "<li class='#{classes}'><div class='#{classes}'>"
282 s << h(block_given? ? capture(project, &block) : project.name)
282 s << h(block_given? ? capture(project, &block) : project.name)
283 s << "</div>\n"
283 s << "</div>\n"
284 ancestors << project
284 ancestors << project
285 end
285 end
286 s << ("</li></ul>\n" * ancestors.size)
286 s << ("</li></ul>\n" * ancestors.size)
287 @project = original_project
287 @project = original_project
288 end
288 end
289 s.html_safe
289 s.html_safe
290 end
290 end
291
291
292 def render_page_hierarchy(pages, node=nil, options={})
292 def render_page_hierarchy(pages, node=nil, options={})
293 content = ''
293 content = ''
294 if pages[node]
294 if pages[node]
295 content << "<ul class=\"pages-hierarchy\">\n"
295 content << "<ul class=\"pages-hierarchy\">\n"
296 pages[node].each do |page|
296 pages[node].each do |page|
297 content << "<li>"
297 content << "<li>"
298 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
298 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
299 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
299 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
300 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
300 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
301 content << "</li>\n"
301 content << "</li>\n"
302 end
302 end
303 content << "</ul>\n"
303 content << "</ul>\n"
304 end
304 end
305 content.html_safe
305 content.html_safe
306 end
306 end
307
307
308 # Renders flash messages
308 # Renders flash messages
309 def render_flash_messages
309 def render_flash_messages
310 s = ''
310 s = ''
311 flash.each do |k,v|
311 flash.each do |k,v|
312 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
312 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
313 end
313 end
314 s.html_safe
314 s.html_safe
315 end
315 end
316
316
317 # Renders tabs and their content
317 # Renders tabs and their content
318 def render_tabs(tabs, selected=params[:tab])
318 def render_tabs(tabs, selected=params[:tab])
319 if tabs.any?
319 if tabs.any?
320 unless tabs.detect {|tab| tab[:name] == selected}
320 unless tabs.detect {|tab| tab[:name] == selected}
321 selected = nil
321 selected = nil
322 end
322 end
323 selected ||= tabs.first[:name]
323 selected ||= tabs.first[:name]
324 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
324 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
325 else
325 else
326 content_tag 'p', l(:label_no_data), :class => "nodata"
326 content_tag 'p', l(:label_no_data), :class => "nodata"
327 end
327 end
328 end
328 end
329
329
330 # Renders the project quick-jump box
330 # Renders the project quick-jump box
331 def render_project_jump_box
331 def render_project_jump_box
332 return unless User.current.logged?
332 return unless User.current.logged?
333 projects = User.current.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
333 projects = User.current.projects.active.select(:id, :name, :identifier, :lft, :rgt).to_a
334 if projects.any?
334 if projects.any?
335 options =
335 options =
336 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
336 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
337 '<option value="" disabled="disabled">---</option>').html_safe
337 '<option value="" disabled="disabled">---</option>').html_safe
338
338
339 options << project_tree_options_for_select(projects, :selected => @project) do |p|
339 options << project_tree_options_for_select(projects, :selected => @project) do |p|
340 { :value => project_path(:id => p, :jump => current_menu_item) }
340 { :value => project_path(:id => p, :jump => current_menu_item) }
341 end
341 end
342
342
343 content_tag( :span, nil, :class => 'jump-box-arrow') +
343 content_tag( :span, nil, :class => 'jump-box-arrow') +
344 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
344 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
345 end
345 end
346 end
346 end
347
347
348 def project_tree_options_for_select(projects, options = {})
348 def project_tree_options_for_select(projects, options = {})
349 s = ''.html_safe
349 s = ''.html_safe
350 if blank_text = options[:include_blank]
350 if blank_text = options[:include_blank]
351 if blank_text == true
351 if blank_text == true
352 blank_text = '&nbsp;'.html_safe
352 blank_text = '&nbsp;'.html_safe
353 end
353 end
354 s << content_tag('option', blank_text, :value => '')
354 s << content_tag('option', blank_text, :value => '')
355 end
355 end
356 project_tree(projects) do |project, level|
356 project_tree(projects) do |project, level|
357 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
357 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
358 tag_options = {:value => project.id}
358 tag_options = {:value => project.id}
359 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
359 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
360 tag_options[:selected] = 'selected'
360 tag_options[:selected] = 'selected'
361 else
361 else
362 tag_options[:selected] = nil
362 tag_options[:selected] = nil
363 end
363 end
364 tag_options.merge!(yield(project)) if block_given?
364 tag_options.merge!(yield(project)) if block_given?
365 s << content_tag('option', name_prefix + h(project), tag_options)
365 s << content_tag('option', name_prefix + h(project), tag_options)
366 end
366 end
367 s.html_safe
367 s.html_safe
368 end
368 end
369
369
370 # Yields the given block for each project with its level in the tree
370 # Yields the given block for each project with its level in the tree
371 #
371 #
372 # Wrapper for Project#project_tree
372 # Wrapper for Project#project_tree
373 def project_tree(projects, &block)
373 def project_tree(projects, &block)
374 Project.project_tree(projects, &block)
374 Project.project_tree(projects, &block)
375 end
375 end
376
376
377 def principals_check_box_tags(name, principals)
377 def principals_check_box_tags(name, principals)
378 s = ''
378 s = ''
379 principals.each do |principal|
379 principals.each do |principal|
380 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
380 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
381 end
381 end
382 s.html_safe
382 s.html_safe
383 end
383 end
384
384
385 # Returns a string for users/groups option tags
385 # Returns a string for users/groups option tags
386 def principals_options_for_select(collection, selected=nil)
386 def principals_options_for_select(collection, selected=nil)
387 s = ''
387 s = ''
388 if collection.include?(User.current)
388 if collection.include?(User.current)
389 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
389 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
390 end
390 end
391 groups = ''
391 groups = ''
392 collection.sort.each do |element|
392 collection.sort.each do |element|
393 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
393 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
394 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
394 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
395 end
395 end
396 unless groups.empty?
396 unless groups.empty?
397 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
397 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
398 end
398 end
399 s.html_safe
399 s.html_safe
400 end
400 end
401
401
402 def option_tag(name, text, value, selected=nil, options={})
402 def option_tag(name, text, value, selected=nil, options={})
403 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
403 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
404 end
404 end
405
405
406 def truncate_single_line_raw(string, length)
406 def truncate_single_line_raw(string, length)
407 string.to_s.truncate(length).gsub(%r{[\r\n]+}m, ' ')
407 string.to_s.truncate(length).gsub(%r{[\r\n]+}m, ' ')
408 end
408 end
409
409
410 # Truncates at line break after 250 characters or options[:length]
410 # Truncates at line break after 250 characters or options[:length]
411 def truncate_lines(string, options={})
411 def truncate_lines(string, options={})
412 length = options[:length] || 250
412 length = options[:length] || 250
413 if string.to_s =~ /\A(.{#{length}}.*?)$/m
413 if string.to_s =~ /\A(.{#{length}}.*?)$/m
414 "#{$1}..."
414 "#{$1}..."
415 else
415 else
416 string
416 string
417 end
417 end
418 end
418 end
419
419
420 def anchor(text)
420 def anchor(text)
421 text.to_s.gsub(' ', '_')
421 text.to_s.gsub(' ', '_')
422 end
422 end
423
423
424 def html_hours(text)
424 def html_hours(text)
425 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
425 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
426 end
426 end
427
427
428 def authoring(created, author, options={})
428 def authoring(created, author, options={})
429 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
429 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
430 end
430 end
431
431
432 def time_tag(time)
432 def time_tag(time)
433 text = distance_of_time_in_words(Time.now, time)
433 text = distance_of_time_in_words(Time.now, time)
434 if @project
434 if @project
435 link_to(text, project_activity_path(@project, :from => User.current.time_to_date(time)), :title => format_time(time))
435 link_to(text, project_activity_path(@project, :from => User.current.time_to_date(time)), :title => format_time(time))
436 else
436 else
437 content_tag('abbr', text, :title => format_time(time))
437 content_tag('abbr', text, :title => format_time(time))
438 end
438 end
439 end
439 end
440
440
441 def syntax_highlight_lines(name, content)
441 def syntax_highlight_lines(name, content)
442 lines = []
442 lines = []
443 syntax_highlight(name, content).each_line { |line| lines << line }
443 syntax_highlight(name, content).each_line { |line| lines << line }
444 lines
444 lines
445 end
445 end
446
446
447 def syntax_highlight(name, content)
447 def syntax_highlight(name, content)
448 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
448 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
449 end
449 end
450
450
451 def to_path_param(path)
451 def to_path_param(path)
452 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
452 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
453 str.blank? ? nil : str
453 str.blank? ? nil : str
454 end
454 end
455
455
456 def reorder_links(name, url, method = :post)
456 def reorder_links(name, url, method = :post)
457 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
457 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
458 url.merge({"#{name}[move_to]" => 'highest'}),
458 url.merge({"#{name}[move_to]" => 'highest'}),
459 :method => method, :title => l(:label_sort_highest)) +
459 :method => method, :title => l(:label_sort_highest)) +
460 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
460 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
461 url.merge({"#{name}[move_to]" => 'higher'}),
461 url.merge({"#{name}[move_to]" => 'higher'}),
462 :method => method, :title => l(:label_sort_higher)) +
462 :method => method, :title => l(:label_sort_higher)) +
463 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
463 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
464 url.merge({"#{name}[move_to]" => 'lower'}),
464 url.merge({"#{name}[move_to]" => 'lower'}),
465 :method => method, :title => l(:label_sort_lower)) +
465 :method => method, :title => l(:label_sort_lower)) +
466 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
466 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
467 url.merge({"#{name}[move_to]" => 'lowest'}),
467 url.merge({"#{name}[move_to]" => 'lowest'}),
468 :method => method, :title => l(:label_sort_lowest))
468 :method => method, :title => l(:label_sort_lowest))
469 end
469 end
470
470
471 def breadcrumb(*args)
471 def breadcrumb(*args)
472 elements = args.flatten
472 elements = args.flatten
473 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
473 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
474 end
474 end
475
475
476 def other_formats_links(&block)
476 def other_formats_links(&block)
477 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
477 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
478 yield Redmine::Views::OtherFormatsBuilder.new(self)
478 yield Redmine::Views::OtherFormatsBuilder.new(self)
479 concat('</p>'.html_safe)
479 concat('</p>'.html_safe)
480 end
480 end
481
481
482 def page_header_title
482 def page_header_title
483 if @project.nil? || @project.new_record?
483 if @project.nil? || @project.new_record?
484 h(Setting.app_title)
484 h(Setting.app_title)
485 else
485 else
486 b = []
486 b = []
487 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
487 ancestors = (@project.root? ? [] : @project.ancestors.visible.to_a)
488 if ancestors.any?
488 if ancestors.any?
489 root = ancestors.shift
489 root = ancestors.shift
490 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
490 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
491 if ancestors.size > 2
491 if ancestors.size > 2
492 b << "\xe2\x80\xa6"
492 b << "\xe2\x80\xa6"
493 ancestors = ancestors[-2, 2]
493 ancestors = ancestors[-2, 2]
494 end
494 end
495 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
495 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
496 end
496 end
497 b << h(@project)
497 b << h(@project)
498 b.join(" \xc2\xbb ").html_safe
498 b.join(" \xc2\xbb ").html_safe
499 end
499 end
500 end
500 end
501
501
502 # Returns a h2 tag and sets the html title with the given arguments
502 # Returns a h2 tag and sets the html title with the given arguments
503 def title(*args)
503 def title(*args)
504 strings = args.map do |arg|
504 strings = args.map do |arg|
505 if arg.is_a?(Array) && arg.size >= 2
505 if arg.is_a?(Array) && arg.size >= 2
506 link_to(*arg)
506 link_to(*arg)
507 else
507 else
508 h(arg.to_s)
508 h(arg.to_s)
509 end
509 end
510 end
510 end
511 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
511 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
512 content_tag('h2', strings.join(' &#187; ').html_safe)
512 content_tag('h2', strings.join(' &#187; ').html_safe)
513 end
513 end
514
514
515 # Sets the html title
515 # Sets the html title
516 # Returns the html title when called without arguments
516 # Returns the html title when called without arguments
517 # Current project name and app_title and automatically appended
517 # Current project name and app_title and automatically appended
518 # Exemples:
518 # Exemples:
519 # html_title 'Foo', 'Bar'
519 # html_title 'Foo', 'Bar'
520 # html_title # => 'Foo - Bar - My Project - Redmine'
520 # html_title # => 'Foo - Bar - My Project - Redmine'
521 def html_title(*args)
521 def html_title(*args)
522 if args.empty?
522 if args.empty?
523 title = @html_title || []
523 title = @html_title || []
524 title << @project.name if @project
524 title << @project.name if @project
525 title << Setting.app_title unless Setting.app_title == title.last
525 title << Setting.app_title unless Setting.app_title == title.last
526 title.reject(&:blank?).join(' - ')
526 title.reject(&:blank?).join(' - ')
527 else
527 else
528 @html_title ||= []
528 @html_title ||= []
529 @html_title += args
529 @html_title += args
530 end
530 end
531 end
531 end
532
532
533 # Returns the theme, controller name, and action as css classes for the
533 # Returns the theme, controller name, and action as css classes for the
534 # HTML body.
534 # HTML body.
535 def body_css_classes
535 def body_css_classes
536 css = []
536 css = []
537 if theme = Redmine::Themes.theme(Setting.ui_theme)
537 if theme = Redmine::Themes.theme(Setting.ui_theme)
538 css << 'theme-' + theme.name
538 css << 'theme-' + theme.name
539 end
539 end
540
540
541 css << 'project-' + @project.identifier if @project && @project.identifier.present?
541 css << 'project-' + @project.identifier if @project && @project.identifier.present?
542 css << 'controller-' + controller_name
542 css << 'controller-' + controller_name
543 css << 'action-' + action_name
543 css << 'action-' + action_name
544 css.join(' ')
544 css.join(' ')
545 end
545 end
546
546
547 def accesskey(s)
547 def accesskey(s)
548 @used_accesskeys ||= []
548 @used_accesskeys ||= []
549 key = Redmine::AccessKeys.key_for(s)
549 key = Redmine::AccessKeys.key_for(s)
550 return nil if @used_accesskeys.include?(key)
550 return nil if @used_accesskeys.include?(key)
551 @used_accesskeys << key
551 @used_accesskeys << key
552 key
552 key
553 end
553 end
554
554
555 # Formats text according to system settings.
555 # Formats text according to system settings.
556 # 2 ways to call this method:
556 # 2 ways to call this method:
557 # * with a String: textilizable(text, options)
557 # * with a String: textilizable(text, options)
558 # * with an object and one of its attribute: textilizable(issue, :description, options)
558 # * with an object and one of its attribute: textilizable(issue, :description, options)
559 def textilizable(*args)
559 def textilizable(*args)
560 options = args.last.is_a?(Hash) ? args.pop : {}
560 options = args.last.is_a?(Hash) ? args.pop : {}
561 case args.size
561 case args.size
562 when 1
562 when 1
563 obj = options[:object]
563 obj = options[:object]
564 text = args.shift
564 text = args.shift
565 when 2
565 when 2
566 obj = args.shift
566 obj = args.shift
567 attr = args.shift
567 attr = args.shift
568 text = obj.send(attr).to_s
568 text = obj.send(attr).to_s
569 else
569 else
570 raise ArgumentError, 'invalid arguments to textilizable'
570 raise ArgumentError, 'invalid arguments to textilizable'
571 end
571 end
572 return '' if text.blank?
572 return '' if text.blank?
573 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
573 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
574 @only_path = only_path = options.delete(:only_path) == false ? false : true
574 @only_path = only_path = options.delete(:only_path) == false ? false : true
575
575
576 text = text.dup
576 text = text.dup
577 macros = catch_macros(text)
577 macros = catch_macros(text)
578 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
578 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
579
579
580 @parsed_headings = []
580 @parsed_headings = []
581 @heading_anchors = {}
581 @heading_anchors = {}
582 @current_section = 0 if options[:edit_section_links]
582 @current_section = 0 if options[:edit_section_links]
583
583
584 parse_sections(text, project, obj, attr, only_path, options)
584 parse_sections(text, project, obj, attr, only_path, options)
585 text = parse_non_pre_blocks(text, obj, macros) do |text|
585 text = parse_non_pre_blocks(text, obj, macros) do |text|
586 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
586 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
587 send method_name, text, project, obj, attr, only_path, options
587 send method_name, text, project, obj, attr, only_path, options
588 end
588 end
589 end
589 end
590 parse_headings(text, project, obj, attr, only_path, options)
590 parse_headings(text, project, obj, attr, only_path, options)
591
591
592 if @parsed_headings.any?
592 if @parsed_headings.any?
593 replace_toc(text, @parsed_headings)
593 replace_toc(text, @parsed_headings)
594 end
594 end
595
595
596 text.html_safe
596 text.html_safe
597 end
597 end
598
598
599 def parse_non_pre_blocks(text, obj, macros)
599 def parse_non_pre_blocks(text, obj, macros)
600 s = StringScanner.new(text)
600 s = StringScanner.new(text)
601 tags = []
601 tags = []
602 parsed = ''
602 parsed = ''
603 while !s.eos?
603 while !s.eos?
604 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
604 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
605 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
605 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
606 if tags.empty?
606 if tags.empty?
607 yield text
607 yield text
608 inject_macros(text, obj, macros) if macros.any?
608 inject_macros(text, obj, macros) if macros.any?
609 else
609 else
610 inject_macros(text, obj, macros, false) if macros.any?
610 inject_macros(text, obj, macros, false) if macros.any?
611 end
611 end
612 parsed << text
612 parsed << text
613 if tag
613 if tag
614 if closing
614 if closing
615 if tags.last && tags.last.casecmp(tag) == 0
615 if tags.last && tags.last.casecmp(tag) == 0
616 tags.pop
616 tags.pop
617 end
617 end
618 else
618 else
619 tags << tag.downcase
619 tags << tag.downcase
620 end
620 end
621 parsed << full_tag
621 parsed << full_tag
622 end
622 end
623 end
623 end
624 # Close any non closing tags
624 # Close any non closing tags
625 while tag = tags.pop
625 while tag = tags.pop
626 parsed << "</#{tag}>"
626 parsed << "</#{tag}>"
627 end
627 end
628 parsed
628 parsed
629 end
629 end
630
630
631 def parse_inline_attachments(text, project, obj, attr, only_path, options)
631 def parse_inline_attachments(text, project, obj, attr, only_path, options)
632 return if options[:inline_attachments] == false
632 return if options[:inline_attachments] == false
633
633
634 # when using an image link, try to use an attachment, if possible
634 # when using an image link, try to use an attachment, if possible
635 attachments = options[:attachments] || []
635 attachments = options[:attachments] || []
636 attachments += obj.attachments if obj.respond_to?(:attachments)
636 attachments += obj.attachments if obj.respond_to?(:attachments)
637 if attachments.present?
637 if attachments.present?
638 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
638 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
639 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
639 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
640 # search for the picture in attachments
640 # search for the picture in attachments
641 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
641 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
642 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
642 image_url = download_named_attachment_url(found, found.filename, :only_path => only_path)
643 desc = found.description.to_s.gsub('"', '')
643 desc = found.description.to_s.gsub('"', '')
644 if !desc.blank? && alttext.blank?
644 if !desc.blank? && alttext.blank?
645 alt = " title=\"#{desc}\" alt=\"#{desc}\""
645 alt = " title=\"#{desc}\" alt=\"#{desc}\""
646 end
646 end
647 "src=\"#{image_url}\"#{alt}"
647 "src=\"#{image_url}\"#{alt}"
648 else
648 else
649 m
649 m
650 end
650 end
651 end
651 end
652 end
652 end
653 end
653 end
654
654
655 # Wiki links
655 # Wiki links
656 #
656 #
657 # Examples:
657 # Examples:
658 # [[mypage]]
658 # [[mypage]]
659 # [[mypage|mytext]]
659 # [[mypage|mytext]]
660 # wiki links can refer other project wikis, using project name or identifier:
660 # wiki links can refer other project wikis, using project name or identifier:
661 # [[project:]] -> wiki starting page
661 # [[project:]] -> wiki starting page
662 # [[project:|mytext]]
662 # [[project:|mytext]]
663 # [[project:mypage]]
663 # [[project:mypage]]
664 # [[project:mypage|mytext]]
664 # [[project:mypage|mytext]]
665 def parse_wiki_links(text, project, obj, attr, only_path, options)
665 def parse_wiki_links(text, project, obj, attr, only_path, options)
666 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
666 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
667 link_project = project
667 link_project = project
668 esc, all, page, title = $1, $2, $3, $5
668 esc, all, page, title = $1, $2, $3, $5
669 if esc.nil?
669 if esc.nil?
670 if page =~ /^([^\:]+)\:(.*)$/
670 if page =~ /^([^\:]+)\:(.*)$/
671 identifier, page = $1, $2
671 identifier, page = $1, $2
672 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
672 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
673 title ||= identifier if page.blank?
673 title ||= identifier if page.blank?
674 end
674 end
675
675
676 if link_project && link_project.wiki
676 if link_project && link_project.wiki
677 # extract anchor
677 # extract anchor
678 anchor = nil
678 anchor = nil
679 if page =~ /^(.+?)\#(.+)$/
679 if page =~ /^(.+?)\#(.+)$/
680 page, anchor = $1, $2
680 page, anchor = $1, $2
681 end
681 end
682 anchor = sanitize_anchor_name(anchor) if anchor.present?
682 anchor = sanitize_anchor_name(anchor) if anchor.present?
683 # check if page exists
683 # check if page exists
684 wiki_page = link_project.wiki.find_page(page)
684 wiki_page = link_project.wiki.find_page(page)
685 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
685 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
686 "##{anchor}"
686 "##{anchor}"
687 else
687 else
688 case options[:wiki_links]
688 case options[:wiki_links]
689 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
689 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
690 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
690 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
691 else
691 else
692 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
692 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
693 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
693 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
694 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
694 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
695 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
695 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
696 end
696 end
697 end
697 end
698 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
698 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
699 else
699 else
700 # project or wiki doesn't exist
700 # project or wiki doesn't exist
701 all
701 all
702 end
702 end
703 else
703 else
704 all
704 all
705 end
705 end
706 end
706 end
707 end
707 end
708
708
709 # Redmine links
709 # Redmine links
710 #
710 #
711 # Examples:
711 # Examples:
712 # Issues:
712 # Issues:
713 # #52 -> Link to issue #52
713 # #52 -> Link to issue #52
714 # Changesets:
714 # Changesets:
715 # r52 -> Link to revision 52
715 # r52 -> Link to revision 52
716 # commit:a85130f -> Link to scmid starting with a85130f
716 # commit:a85130f -> Link to scmid starting with a85130f
717 # Documents:
717 # Documents:
718 # document#17 -> Link to document with id 17
718 # document#17 -> Link to document with id 17
719 # document:Greetings -> Link to the document with title "Greetings"
719 # document:Greetings -> Link to the document with title "Greetings"
720 # document:"Some document" -> Link to the document with title "Some document"
720 # document:"Some document" -> Link to the document with title "Some document"
721 # Versions:
721 # Versions:
722 # version#3 -> Link to version with id 3
722 # version#3 -> Link to version with id 3
723 # version:1.0.0 -> Link to version named "1.0.0"
723 # version:1.0.0 -> Link to version named "1.0.0"
724 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
724 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
725 # Attachments:
725 # Attachments:
726 # attachment:file.zip -> Link to the attachment of the current object named file.zip
726 # attachment:file.zip -> Link to the attachment of the current object named file.zip
727 # Source files:
727 # Source files:
728 # source:some/file -> Link to the file located at /some/file in the project's repository
728 # source:some/file -> Link to the file located at /some/file in the project's repository
729 # source:some/file@52 -> Link to the file's revision 52
729 # source:some/file@52 -> Link to the file's revision 52
730 # source:some/file#L120 -> Link to line 120 of the file
730 # source:some/file#L120 -> Link to line 120 of the file
731 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
731 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
732 # export:some/file -> Force the download of the file
732 # export:some/file -> Force the download of the file
733 # Forum messages:
733 # Forum messages:
734 # message#1218 -> Link to message with id 1218
734 # message#1218 -> Link to message with id 1218
735 # Projects:
735 # Projects:
736 # project:someproject -> Link to project named "someproject"
736 # project:someproject -> Link to project named "someproject"
737 # project#3 -> Link to project with id 3
737 # project#3 -> Link to project with id 3
738 #
738 #
739 # Links can refer other objects from other projects, using project identifier:
739 # Links can refer other objects from other projects, using project identifier:
740 # identifier:r52
740 # identifier:r52
741 # identifier:document:"Some document"
741 # identifier:document:"Some document"
742 # identifier:version:1.0.0
742 # identifier:version:1.0.0
743 # identifier:source:some/file
743 # identifier:source:some/file
744 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
744 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
745 text.gsub!(%r{<a( [^>]+?)?>(.*?)</a>|([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
745 text.gsub!(%r{<a( [^>]+?)?>(.*?)</a>|([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
746 tag_content, leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $2, $3, $4, $5, $6, $7, $12, $13, $10 || $14 || $20, $16 || $21, $17, $19
746 tag_content, leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $2, $3, $4, $5, $6, $7, $12, $13, $10 || $14 || $20, $16 || $21, $17, $19
747 if tag_content
747 if tag_content
748 $&
748 $&
749 else
749 else
750 link = nil
750 link = nil
751 project = default_project
751 project = default_project
752 if project_identifier
752 if project_identifier
753 project = Project.visible.find_by_identifier(project_identifier)
753 project = Project.visible.find_by_identifier(project_identifier)
754 end
754 end
755 if esc.nil?
755 if esc.nil?
756 if prefix.nil? && sep == 'r'
756 if prefix.nil? && sep == 'r'
757 if project
757 if project
758 repository = nil
758 repository = nil
759 if repo_identifier
759 if repo_identifier
760 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
760 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
761 else
761 else
762 repository = project.repository
762 repository = project.repository
763 end
763 end
764 # project.changesets.visible raises an SQL error because of a double join on repositories
764 # project.changesets.visible raises an SQL error because of a double join on repositories
765 if repository &&
765 if repository &&
766 (changeset = Changeset.visible.
766 (changeset = Changeset.visible.
767 find_by_repository_id_and_revision(repository.id, identifier))
767 find_by_repository_id_and_revision(repository.id, identifier))
768 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
768 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
769 {:only_path => only_path, :controller => 'repositories',
769 {:only_path => only_path, :controller => 'repositories',
770 :action => 'revision', :id => project,
770 :action => 'revision', :id => project,
771 :repository_id => repository.identifier_param,
771 :repository_id => repository.identifier_param,
772 :rev => changeset.revision},
772 :rev => changeset.revision},
773 :class => 'changeset',
773 :class => 'changeset',
774 :title => truncate_single_line_raw(changeset.comments, 100))
774 :title => truncate_single_line_raw(changeset.comments, 100))
775 end
775 end
776 end
776 end
777 elsif sep == '#'
777 elsif sep == '#'
778 oid = identifier.to_i
778 oid = identifier.to_i
779 case prefix
779 case prefix
780 when nil
780 when nil
781 if oid.to_s == identifier &&
781 if oid.to_s == identifier &&
782 issue = Issue.visible.find_by_id(oid)
782 issue = Issue.visible.find_by_id(oid)
783 anchor = comment_id ? "note-#{comment_id}" : nil
783 anchor = comment_id ? "note-#{comment_id}" : nil
784 link = link_to("##{oid}#{comment_suffix}",
784 link = link_to("##{oid}#{comment_suffix}",
785 issue_url(issue, :only_path => only_path, :anchor => anchor),
785 issue_url(issue, :only_path => only_path, :anchor => anchor),
786 :class => issue.css_classes,
786 :class => issue.css_classes,
787 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
787 :title => "#{issue.tracker.name}: #{issue.subject.truncate(100)} (#{issue.status.name})")
788 end
788 end
789 when 'document'
789 when 'document'
790 if document = Document.visible.find_by_id(oid)
790 if document = Document.visible.find_by_id(oid)
791 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
791 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
792 end
792 end
793 when 'version'
793 when 'version'
794 if version = Version.visible.find_by_id(oid)
794 if version = Version.visible.find_by_id(oid)
795 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
795 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
796 end
796 end
797 when 'message'
797 when 'message'
798 if message = Message.visible.find_by_id(oid)
798 if message = Message.visible.find_by_id(oid)
799 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
799 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
800 end
800 end
801 when 'forum'
801 when 'forum'
802 if board = Board.visible.find_by_id(oid)
802 if board = Board.visible.find_by_id(oid)
803 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
803 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
804 end
804 end
805 when 'news'
805 when 'news'
806 if news = News.visible.find_by_id(oid)
806 if news = News.visible.find_by_id(oid)
807 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
807 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
808 end
808 end
809 when 'project'
809 when 'project'
810 if p = Project.visible.find_by_id(oid)
810 if p = Project.visible.find_by_id(oid)
811 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
811 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
812 end
812 end
813 end
813 end
814 elsif sep == ':'
814 elsif sep == ':'
815 # removes the double quotes if any
815 # removes the double quotes if any
816 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
816 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
817 name = CGI.unescapeHTML(name)
817 name = CGI.unescapeHTML(name)
818 case prefix
818 case prefix
819 when 'document'
819 when 'document'
820 if project && document = project.documents.visible.find_by_title(name)
820 if project && document = project.documents.visible.find_by_title(name)
821 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
821 link = link_to(document.title, document_url(document, :only_path => only_path), :class => 'document')
822 end
822 end
823 when 'version'
823 when 'version'
824 if project && version = project.versions.visible.find_by_name(name)
824 if project && version = project.versions.visible.find_by_name(name)
825 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
825 link = link_to(version.name, version_url(version, :only_path => only_path), :class => 'version')
826 end
826 end
827 when 'forum'
827 when 'forum'
828 if project && board = project.boards.visible.find_by_name(name)
828 if project && board = project.boards.visible.find_by_name(name)
829 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
829 link = link_to(board.name, project_board_url(board.project, board, :only_path => only_path), :class => 'board')
830 end
830 end
831 when 'news'
831 when 'news'
832 if project && news = project.news.visible.find_by_title(name)
832 if project && news = project.news.visible.find_by_title(name)
833 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
833 link = link_to(news.title, news_url(news, :only_path => only_path), :class => 'news')
834 end
834 end
835 when 'commit', 'source', 'export'
835 when 'commit', 'source', 'export'
836 if project
836 if project
837 repository = nil
837 repository = nil
838 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
838 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
839 repo_prefix, repo_identifier, name = $1, $2, $3
839 repo_prefix, repo_identifier, name = $1, $2, $3
840 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
840 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
841 else
841 else
842 repository = project.repository
842 repository = project.repository
843 end
843 end
844 if prefix == 'commit'
844 if prefix == 'commit'
845 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
845 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
846 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
846 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
847 :class => 'changeset',
847 :class => 'changeset',
848 :title => truncate_single_line_raw(changeset.comments, 100)
848 :title => truncate_single_line_raw(changeset.comments, 100)
849 end
849 end
850 else
850 else
851 if repository && User.current.allowed_to?(:browse_repository, project)
851 if repository && User.current.allowed_to?(:browse_repository, project)
852 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
852 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
853 path, rev, anchor = $1, $3, $5
853 path, rev, anchor = $1, $3, $5
854 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
854 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
855 :path => to_path_param(path),
855 :path => to_path_param(path),
856 :rev => rev,
856 :rev => rev,
857 :anchor => anchor},
857 :anchor => anchor},
858 :class => (prefix == 'export' ? 'source download' : 'source')
858 :class => (prefix == 'export' ? 'source download' : 'source')
859 end
859 end
860 end
860 end
861 repo_prefix = nil
861 repo_prefix = nil
862 end
862 end
863 when 'attachment'
863 when 'attachment'
864 attachments = options[:attachments] || []
864 attachments = options[:attachments] || []
865 attachments += obj.attachments if obj.respond_to?(:attachments)
865 attachments += obj.attachments if obj.respond_to?(:attachments)
866 if attachments && attachment = Attachment.latest_attach(attachments, name)
866 if attachments && attachment = Attachment.latest_attach(attachments, name)
867 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
867 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
868 end
868 end
869 when 'project'
869 when 'project'
870 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
870 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
871 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
871 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
872 end
872 end
873 end
873 end
874 end
874 end
875 end
875 end
876 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
876 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
877 end
877 end
878 end
878 end
879 end
879 end
880
880
881 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
881 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
882
882
883 def parse_sections(text, project, obj, attr, only_path, options)
883 def parse_sections(text, project, obj, attr, only_path, options)
884 return unless options[:edit_section_links]
884 return unless options[:edit_section_links]
885 text.gsub!(HEADING_RE) do
885 text.gsub!(HEADING_RE) do
886 heading = $1
886 heading = $1
887 @current_section += 1
887 @current_section += 1
888 if @current_section > 1
888 if @current_section > 1
889 content_tag('div',
889 content_tag('div',
890 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
890 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
891 :class => 'contextual',
891 :class => 'contextual',
892 :title => l(:button_edit_section),
892 :title => l(:button_edit_section),
893 :id => "section-#{@current_section}") + heading.html_safe
893 :id => "section-#{@current_section}") + heading.html_safe
894 else
894 else
895 heading
895 heading
896 end
896 end
897 end
897 end
898 end
898 end
899
899
900 # Headings and TOC
900 # Headings and TOC
901 # Adds ids and links to headings unless options[:headings] is set to false
901 # Adds ids and links to headings unless options[:headings] is set to false
902 def parse_headings(text, project, obj, attr, only_path, options)
902 def parse_headings(text, project, obj, attr, only_path, options)
903 return if options[:headings] == false
903 return if options[:headings] == false
904
904
905 text.gsub!(HEADING_RE) do
905 text.gsub!(HEADING_RE) do
906 level, attrs, content = $2.to_i, $3, $4
906 level, attrs, content = $2.to_i, $3, $4
907 item = strip_tags(content).strip
907 item = strip_tags(content).strip
908 anchor = sanitize_anchor_name(item)
908 anchor = sanitize_anchor_name(item)
909 # used for single-file wiki export
909 # used for single-file wiki export
910 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
910 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
911 @heading_anchors[anchor] ||= 0
911 @heading_anchors[anchor] ||= 0
912 idx = (@heading_anchors[anchor] += 1)
912 idx = (@heading_anchors[anchor] += 1)
913 if idx > 1
913 if idx > 1
914 anchor = "#{anchor}-#{idx}"
914 anchor = "#{anchor}-#{idx}"
915 end
915 end
916 @parsed_headings << [level, anchor, item]
916 @parsed_headings << [level, anchor, item]
917 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
917 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
918 end
918 end
919 end
919 end
920
920
921 MACROS_RE = /(
921 MACROS_RE = /(
922 (!)? # escaping
922 (!)? # escaping
923 (
923 (
924 \{\{ # opening tag
924 \{\{ # opening tag
925 ([\w]+) # macro name
925 ([\w]+) # macro name
926 (\(([^\n\r]*?)\))? # optional arguments
926 (\(([^\n\r]*?)\))? # optional arguments
927 ([\n\r].*?[\n\r])? # optional block of text
927 ([\n\r].*?[\n\r])? # optional block of text
928 \}\} # closing tag
928 \}\} # closing tag
929 )
929 )
930 )/mx unless const_defined?(:MACROS_RE)
930 )/mx unless const_defined?(:MACROS_RE)
931
931
932 MACRO_SUB_RE = /(
932 MACRO_SUB_RE = /(
933 \{\{
933 \{\{
934 macro\((\d+)\)
934 macro\((\d+)\)
935 \}\}
935 \}\}
936 )/x unless const_defined?(:MACRO_SUB_RE)
936 )/x unless const_defined?(:MACRO_SUB_RE)
937
937
938 # Extracts macros from text
938 # Extracts macros from text
939 def catch_macros(text)
939 def catch_macros(text)
940 macros = {}
940 macros = {}
941 text.gsub!(MACROS_RE) do
941 text.gsub!(MACROS_RE) do
942 all, macro = $1, $4.downcase
942 all, macro = $1, $4.downcase
943 if macro_exists?(macro) || all =~ MACRO_SUB_RE
943 if macro_exists?(macro) || all =~ MACRO_SUB_RE
944 index = macros.size
944 index = macros.size
945 macros[index] = all
945 macros[index] = all
946 "{{macro(#{index})}}"
946 "{{macro(#{index})}}"
947 else
947 else
948 all
948 all
949 end
949 end
950 end
950 end
951 macros
951 macros
952 end
952 end
953
953
954 # Executes and replaces macros in text
954 # Executes and replaces macros in text
955 def inject_macros(text, obj, macros, execute=true)
955 def inject_macros(text, obj, macros, execute=true)
956 text.gsub!(MACRO_SUB_RE) do
956 text.gsub!(MACRO_SUB_RE) do
957 all, index = $1, $2.to_i
957 all, index = $1, $2.to_i
958 orig = macros.delete(index)
958 orig = macros.delete(index)
959 if execute && orig && orig =~ MACROS_RE
959 if execute && orig && orig =~ MACROS_RE
960 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
960 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
961 if esc.nil?
961 if esc.nil?
962 h(exec_macro(macro, obj, args, block) || all)
962 h(exec_macro(macro, obj, args, block) || all)
963 else
963 else
964 h(all)
964 h(all)
965 end
965 end
966 elsif orig
966 elsif orig
967 h(orig)
967 h(orig)
968 else
968 else
969 h(all)
969 h(all)
970 end
970 end
971 end
971 end
972 end
972 end
973
973
974 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
974 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
975
975
976 # Renders the TOC with given headings
976 # Renders the TOC with given headings
977 def replace_toc(text, headings)
977 def replace_toc(text, headings)
978 text.gsub!(TOC_RE) do
978 text.gsub!(TOC_RE) do
979 left_align, right_align = $2, $3
979 left_align, right_align = $2, $3
980 # Keep only the 4 first levels
980 # Keep only the 4 first levels
981 headings = headings.select{|level, anchor, item| level <= 4}
981 headings = headings.select{|level, anchor, item| level <= 4}
982 if headings.empty?
982 if headings.empty?
983 ''
983 ''
984 else
984 else
985 div_class = 'toc'
985 div_class = 'toc'
986 div_class << ' right' if right_align
986 div_class << ' right' if right_align
987 div_class << ' left' if left_align
987 div_class << ' left' if left_align
988 out = "<ul class=\"#{div_class}\"><li>"
988 out = "<ul class=\"#{div_class}\"><li>"
989 root = headings.map(&:first).min
989 root = headings.map(&:first).min
990 current = root
990 current = root
991 started = false
991 started = false
992 headings.each do |level, anchor, item|
992 headings.each do |level, anchor, item|
993 if level > current
993 if level > current
994 out << '<ul><li>' * (level - current)
994 out << '<ul><li>' * (level - current)
995 elsif level < current
995 elsif level < current
996 out << "</li></ul>\n" * (current - level) + "</li><li>"
996 out << "</li></ul>\n" * (current - level) + "</li><li>"
997 elsif started
997 elsif started
998 out << '</li><li>'
998 out << '</li><li>'
999 end
999 end
1000 out << "<a href=\"##{anchor}\">#{item}</a>"
1000 out << "<a href=\"##{anchor}\">#{item}</a>"
1001 current = level
1001 current = level
1002 started = true
1002 started = true
1003 end
1003 end
1004 out << '</li></ul>' * (current - root)
1004 out << '</li></ul>' * (current - root)
1005 out << '</li></ul>'
1005 out << '</li></ul>'
1006 end
1006 end
1007 end
1007 end
1008 end
1008 end
1009
1009
1010 # Same as Rails' simple_format helper without using paragraphs
1010 # Same as Rails' simple_format helper without using paragraphs
1011 def simple_format_without_paragraph(text)
1011 def simple_format_without_paragraph(text)
1012 text.to_s.
1012 text.to_s.
1013 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1013 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1014 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1014 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1015 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1015 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1016 html_safe
1016 html_safe
1017 end
1017 end
1018
1018
1019 def lang_options_for_select(blank=true)
1019 def lang_options_for_select(blank=true)
1020 (blank ? [["(auto)", ""]] : []) + languages_options
1020 (blank ? [["(auto)", ""]] : []) + languages_options
1021 end
1021 end
1022
1022
1023 def labelled_form_for(*args, &proc)
1023 def labelled_form_for(*args, &proc)
1024 args << {} unless args.last.is_a?(Hash)
1024 args << {} unless args.last.is_a?(Hash)
1025 options = args.last
1025 options = args.last
1026 if args.first.is_a?(Symbol)
1026 if args.first.is_a?(Symbol)
1027 options.merge!(:as => args.shift)
1027 options.merge!(:as => args.shift)
1028 end
1028 end
1029 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1029 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1030 form_for(*args, &proc)
1030 form_for(*args, &proc)
1031 end
1031 end
1032
1032
1033 def labelled_fields_for(*args, &proc)
1033 def labelled_fields_for(*args, &proc)
1034 args << {} unless args.last.is_a?(Hash)
1034 args << {} unless args.last.is_a?(Hash)
1035 options = args.last
1035 options = args.last
1036 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1036 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1037 fields_for(*args, &proc)
1037 fields_for(*args, &proc)
1038 end
1038 end
1039
1039
1040 def error_messages_for(*objects)
1040 def error_messages_for(*objects)
1041 html = ""
1041 html = ""
1042 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1042 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1043 errors = objects.map {|o| o.errors.full_messages}.flatten
1043 errors = objects.map {|o| o.errors.full_messages}.flatten
1044 if errors.any?
1044 if errors.any?
1045 html << "<div id='errorExplanation'><ul>\n"
1045 html << "<div id='errorExplanation'><ul>\n"
1046 errors.each do |error|
1046 errors.each do |error|
1047 html << "<li>#{h error}</li>\n"
1047 html << "<li>#{h error}</li>\n"
1048 end
1048 end
1049 html << "</ul></div>\n"
1049 html << "</ul></div>\n"
1050 end
1050 end
1051 html.html_safe
1051 html.html_safe
1052 end
1052 end
1053
1053
1054 def delete_link(url, options={})
1054 def delete_link(url, options={})
1055 options = {
1055 options = {
1056 :method => :delete,
1056 :method => :delete,
1057 :data => {:confirm => l(:text_are_you_sure)},
1057 :data => {:confirm => l(:text_are_you_sure)},
1058 :class => 'icon icon-del'
1058 :class => 'icon icon-del'
1059 }.merge(options)
1059 }.merge(options)
1060
1060
1061 link_to l(:button_delete), url, options
1061 link_to l(:button_delete), url, options
1062 end
1062 end
1063
1063
1064 def preview_link(url, form, target='preview', options={})
1064 def preview_link(url, form, target='preview', options={})
1065 content_tag 'a', l(:label_preview), {
1065 content_tag 'a', l(:label_preview), {
1066 :href => "#",
1066 :href => "#",
1067 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1067 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1068 :accesskey => accesskey(:preview)
1068 :accesskey => accesskey(:preview)
1069 }.merge(options)
1069 }.merge(options)
1070 end
1070 end
1071
1071
1072 def link_to_function(name, function, html_options={})
1072 def link_to_function(name, function, html_options={})
1073 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1073 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1074 end
1074 end
1075
1075
1076 # Helper to render JSON in views
1076 # Helper to render JSON in views
1077 def raw_json(arg)
1077 def raw_json(arg)
1078 arg.to_json.to_s.gsub('/', '\/').html_safe
1078 arg.to_json.to_s.gsub('/', '\/').html_safe
1079 end
1079 end
1080
1080
1081 def back_url
1081 def back_url
1082 url = params[:back_url]
1082 url = params[:back_url]
1083 if url.nil? && referer = request.env['HTTP_REFERER']
1083 if url.nil? && referer = request.env['HTTP_REFERER']
1084 url = CGI.unescape(referer.to_s)
1084 url = CGI.unescape(referer.to_s)
1085 end
1085 end
1086 url
1086 url
1087 end
1087 end
1088
1088
1089 def back_url_hidden_field_tag
1089 def back_url_hidden_field_tag
1090 url = back_url
1090 url = back_url
1091 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1091 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1092 end
1092 end
1093
1093
1094 def check_all_links(form_name)
1094 def check_all_links(form_name)
1095 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1095 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1096 " | ".html_safe +
1096 " | ".html_safe +
1097 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1097 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1098 end
1098 end
1099
1099
1100 def toggle_checkboxes_link(selector)
1100 def toggle_checkboxes_link(selector)
1101 link_to_function image_tag('toggle_check.png'),
1101 link_to_function image_tag('toggle_check.png'),
1102 "toggleCheckboxesBySelector('#{selector}')",
1102 "toggleCheckboxesBySelector('#{selector}')",
1103 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}"
1103 :title => "#{l(:button_check_all)} / #{l(:button_uncheck_all)}"
1104 end
1104 end
1105
1105
1106 def progress_bar(pcts, options={})
1106 def progress_bar(pcts, options={})
1107 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1107 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1108 pcts = pcts.collect(&:round)
1108 pcts = pcts.collect(&:round)
1109 pcts[1] = pcts[1] - pcts[0]
1109 pcts[1] = pcts[1] - pcts[0]
1110 pcts << (100 - pcts[1] - pcts[0])
1110 pcts << (100 - pcts[1] - pcts[0])
1111 width = options[:width] || '100px;'
1112 legend = options[:legend] || ''
1111 legend = options[:legend] || ''
1113 content_tag('table',
1112 content_tag('table',
1114 content_tag('tr',
1113 content_tag('tr',
1115 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1114 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1116 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1115 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1117 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1116 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1118 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1117 ), :class => "progress progress-#{pcts[0]}").html_safe +
1119 content_tag('p', legend, :class => 'percent').html_safe
1118 content_tag('p', legend, :class => 'percent').html_safe
1120 end
1119 end
1121
1120
1122 def checked_image(checked=true)
1121 def checked_image(checked=true)
1123 if checked
1122 if checked
1124 @checked_image_tag ||= image_tag('toggle_check.png')
1123 @checked_image_tag ||= image_tag('toggle_check.png')
1125 end
1124 end
1126 end
1125 end
1127
1126
1128 def context_menu(url)
1127 def context_menu(url)
1129 unless @context_menu_included
1128 unless @context_menu_included
1130 content_for :header_tags do
1129 content_for :header_tags do
1131 javascript_include_tag('context_menu') +
1130 javascript_include_tag('context_menu') +
1132 stylesheet_link_tag('context_menu')
1131 stylesheet_link_tag('context_menu')
1133 end
1132 end
1134 if l(:direction) == 'rtl'
1133 if l(:direction) == 'rtl'
1135 content_for :header_tags do
1134 content_for :header_tags do
1136 stylesheet_link_tag('context_menu_rtl')
1135 stylesheet_link_tag('context_menu_rtl')
1137 end
1136 end
1138 end
1137 end
1139 @context_menu_included = true
1138 @context_menu_included = true
1140 end
1139 end
1141 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1140 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1142 end
1141 end
1143
1142
1144 def calendar_for(field_id)
1143 def calendar_for(field_id)
1145 include_calendar_headers_tags
1144 include_calendar_headers_tags
1146 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepicker(datepickerOptions); });")
1145 javascript_tag("$(function() { $('##{field_id}').addClass('date').datepicker(datepickerOptions); });")
1147 end
1146 end
1148
1147
1149 def include_calendar_headers_tags
1148 def include_calendar_headers_tags
1150 unless @calendar_headers_tags_included
1149 unless @calendar_headers_tags_included
1151 tags = ''.html_safe
1150 tags = ''.html_safe
1152 @calendar_headers_tags_included = true
1151 @calendar_headers_tags_included = true
1153 content_for :header_tags do
1152 content_for :header_tags do
1154 start_of_week = Setting.start_of_week
1153 start_of_week = Setting.start_of_week
1155 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1154 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1156 # Redmine uses 1..7 (monday..sunday) in settings and locales
1155 # Redmine uses 1..7 (monday..sunday) in settings and locales
1157 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1156 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1158 start_of_week = start_of_week.to_i % 7
1157 start_of_week = start_of_week.to_i % 7
1159 tags << javascript_tag(
1158 tags << javascript_tag(
1160 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1159 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1161 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1160 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1162 path_to_image('/images/calendar.png') +
1161 path_to_image('/images/calendar.png') +
1163 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1162 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1164 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1163 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1165 "beforeShow: beforeShowDatePicker};")
1164 "beforeShow: beforeShowDatePicker};")
1166 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1165 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1167 unless jquery_locale == 'en'
1166 unless jquery_locale == 'en'
1168 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1167 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1169 end
1168 end
1170 tags
1169 tags
1171 end
1170 end
1172 end
1171 end
1173 end
1172 end
1174
1173
1175 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1174 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1176 # Examples:
1175 # Examples:
1177 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1176 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1178 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1177 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1179 #
1178 #
1180 def stylesheet_link_tag(*sources)
1179 def stylesheet_link_tag(*sources)
1181 options = sources.last.is_a?(Hash) ? sources.pop : {}
1180 options = sources.last.is_a?(Hash) ? sources.pop : {}
1182 plugin = options.delete(:plugin)
1181 plugin = options.delete(:plugin)
1183 sources = sources.map do |source|
1182 sources = sources.map do |source|
1184 if plugin
1183 if plugin
1185 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1184 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1186 elsif current_theme && current_theme.stylesheets.include?(source)
1185 elsif current_theme && current_theme.stylesheets.include?(source)
1187 current_theme.stylesheet_path(source)
1186 current_theme.stylesheet_path(source)
1188 else
1187 else
1189 source
1188 source
1190 end
1189 end
1191 end
1190 end
1192 super *sources, options
1191 super *sources, options
1193 end
1192 end
1194
1193
1195 # Overrides Rails' image_tag with themes and plugins support.
1194 # Overrides Rails' image_tag with themes and plugins support.
1196 # Examples:
1195 # Examples:
1197 # image_tag('image.png') # => picks image.png from the current theme or defaults
1196 # image_tag('image.png') # => picks image.png from the current theme or defaults
1198 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1197 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1199 #
1198 #
1200 def image_tag(source, options={})
1199 def image_tag(source, options={})
1201 if plugin = options.delete(:plugin)
1200 if plugin = options.delete(:plugin)
1202 source = "/plugin_assets/#{plugin}/images/#{source}"
1201 source = "/plugin_assets/#{plugin}/images/#{source}"
1203 elsif current_theme && current_theme.images.include?(source)
1202 elsif current_theme && current_theme.images.include?(source)
1204 source = current_theme.image_path(source)
1203 source = current_theme.image_path(source)
1205 end
1204 end
1206 super source, options
1205 super source, options
1207 end
1206 end
1208
1207
1209 # Overrides Rails' javascript_include_tag with plugins support
1208 # Overrides Rails' javascript_include_tag with plugins support
1210 # Examples:
1209 # Examples:
1211 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1210 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1212 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1211 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1213 #
1212 #
1214 def javascript_include_tag(*sources)
1213 def javascript_include_tag(*sources)
1215 options = sources.last.is_a?(Hash) ? sources.pop : {}
1214 options = sources.last.is_a?(Hash) ? sources.pop : {}
1216 if plugin = options.delete(:plugin)
1215 if plugin = options.delete(:plugin)
1217 sources = sources.map do |source|
1216 sources = sources.map do |source|
1218 if plugin
1217 if plugin
1219 "/plugin_assets/#{plugin}/javascripts/#{source}"
1218 "/plugin_assets/#{plugin}/javascripts/#{source}"
1220 else
1219 else
1221 source
1220 source
1222 end
1221 end
1223 end
1222 end
1224 end
1223 end
1225 super *sources, options
1224 super *sources, options
1226 end
1225 end
1227
1226
1228 def sidebar_content?
1227 def sidebar_content?
1229 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1228 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1230 end
1229 end
1231
1230
1232 def view_layouts_base_sidebar_hook_response
1231 def view_layouts_base_sidebar_hook_response
1233 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1232 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1234 end
1233 end
1235
1234
1236 def email_delivery_enabled?
1235 def email_delivery_enabled?
1237 !!ActionMailer::Base.perform_deliveries
1236 !!ActionMailer::Base.perform_deliveries
1238 end
1237 end
1239
1238
1240 # Returns the avatar image tag for the given +user+ if avatars are enabled
1239 # Returns the avatar image tag for the given +user+ if avatars are enabled
1241 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1240 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1242 def avatar(user, options = { })
1241 def avatar(user, options = { })
1243 if Setting.gravatar_enabled?
1242 if Setting.gravatar_enabled?
1244 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1243 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1245 email = nil
1244 email = nil
1246 if user.respond_to?(:mail)
1245 if user.respond_to?(:mail)
1247 email = user.mail
1246 email = user.mail
1248 elsif user.to_s =~ %r{<(.+?)>}
1247 elsif user.to_s =~ %r{<(.+?)>}
1249 email = $1
1248 email = $1
1250 end
1249 end
1251 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1250 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1252 else
1251 else
1253 ''
1252 ''
1254 end
1253 end
1255 end
1254 end
1256
1255
1257 # Returns a link to edit user's avatar if avatars are enabled
1256 # Returns a link to edit user's avatar if avatars are enabled
1258 def avatar_edit_link(user, options={})
1257 def avatar_edit_link(user, options={})
1259 if Setting.gravatar_enabled?
1258 if Setting.gravatar_enabled?
1260 url = "https://gravatar.com"
1259 url = "https://gravatar.com"
1261 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1260 link_to avatar(user, {:title => l(:button_edit)}.merge(options)), url, :target => '_blank'
1262 end
1261 end
1263 end
1262 end
1264
1263
1265 def sanitize_anchor_name(anchor)
1264 def sanitize_anchor_name(anchor)
1266 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1265 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1267 end
1266 end
1268
1267
1269 # Returns the javascript tags that are included in the html layout head
1268 # Returns the javascript tags that are included in the html layout head
1270 def javascript_heads
1269 def javascript_heads
1271 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1270 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive')
1272 unless User.current.pref.warn_on_leaving_unsaved == '0'
1271 unless User.current.pref.warn_on_leaving_unsaved == '0'
1273 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1272 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1274 end
1273 end
1275 tags
1274 tags
1276 end
1275 end
1277
1276
1278 def favicon
1277 def favicon
1279 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1278 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1280 end
1279 end
1281
1280
1282 # Returns the path to the favicon
1281 # Returns the path to the favicon
1283 def favicon_path
1282 def favicon_path
1284 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1283 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1285 image_path(icon)
1284 image_path(icon)
1286 end
1285 end
1287
1286
1288 # Returns the full URL to the favicon
1287 # Returns the full URL to the favicon
1289 def favicon_url
1288 def favicon_url
1290 # TODO: use #image_url introduced in Rails4
1289 # TODO: use #image_url introduced in Rails4
1291 path = favicon_path
1290 path = favicon_path
1292 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1291 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1293 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1292 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1294 end
1293 end
1295
1294
1296 def robot_exclusion_tag
1295 def robot_exclusion_tag
1297 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1296 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1298 end
1297 end
1299
1298
1300 # Returns true if arg is expected in the API response
1299 # Returns true if arg is expected in the API response
1301 def include_in_api_response?(arg)
1300 def include_in_api_response?(arg)
1302 unless @included_in_api_response
1301 unless @included_in_api_response
1303 param = params[:include]
1302 param = params[:include]
1304 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1303 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1305 @included_in_api_response.collect!(&:strip)
1304 @included_in_api_response.collect!(&:strip)
1306 end
1305 end
1307 @included_in_api_response.include?(arg.to_s)
1306 @included_in_api_response.include?(arg.to_s)
1308 end
1307 end
1309
1308
1310 # Returns options or nil if nometa param or X-Redmine-Nometa header
1309 # Returns options or nil if nometa param or X-Redmine-Nometa header
1311 # was set in the request
1310 # was set in the request
1312 def api_meta(options)
1311 def api_meta(options)
1313 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1312 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1314 # compatibility mode for activeresource clients that raise
1313 # compatibility mode for activeresource clients that raise
1315 # an error when deserializing an array with attributes
1314 # an error when deserializing an array with attributes
1316 nil
1315 nil
1317 else
1316 else
1318 options
1317 options
1319 end
1318 end
1320 end
1319 end
1321
1320
1322 def generate_csv(&block)
1321 def generate_csv(&block)
1323 decimal_separator = l(:general_csv_decimal_separator)
1322 decimal_separator = l(:general_csv_decimal_separator)
1324 encoding = l(:general_csv_encoding)
1323 encoding = l(:general_csv_encoding)
1325 end
1324 end
1326
1325
1327 private
1326 private
1328
1327
1329 def wiki_helper
1328 def wiki_helper
1330 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1329 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1331 extend helper
1330 extend helper
1332 return self
1331 return self
1333 end
1332 end
1334
1333
1335 def link_to_content_update(text, url_params = {}, html_options = {})
1334 def link_to_content_update(text, url_params = {}, html_options = {})
1336 link_to(text, url_params, html_options)
1335 link_to(text, url_params, html_options)
1337 end
1336 end
1338 end
1337 end
@@ -1,516 +1,516
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module IssuesHelper
20 module IssuesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22 include Redmine::Export::PDF::IssuesPdfHelper
22 include Redmine::Export::PDF::IssuesPdfHelper
23
23
24 def issue_list(issues, &block)
24 def issue_list(issues, &block)
25 ancestors = []
25 ancestors = []
26 issues.each do |issue|
26 issues.each do |issue|
27 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
27 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
28 ancestors.pop
28 ancestors.pop
29 end
29 end
30 yield issue, ancestors.size
30 yield issue, ancestors.size
31 ancestors << issue unless issue.leaf?
31 ancestors << issue unless issue.leaf?
32 end
32 end
33 end
33 end
34
34
35 def grouped_issue_list(issues, query, issue_count_by_group, &block)
35 def grouped_issue_list(issues, query, issue_count_by_group, &block)
36 previous_group, first = false, true
36 previous_group, first = false, true
37 totals_by_group = query.totalable_columns.inject({}) do |h, column|
37 totals_by_group = query.totalable_columns.inject({}) do |h, column|
38 h[column] = query.total_by_group_for(column)
38 h[column] = query.total_by_group_for(column)
39 h
39 h
40 end
40 end
41 issue_list(issues) do |issue, level|
41 issue_list(issues) do |issue, level|
42 group_name = group_count = nil
42 group_name = group_count = nil
43 if query.grouped?
43 if query.grouped?
44 group = query.group_by_column.value(issue)
44 group = query.group_by_column.value(issue)
45 if first || group != previous_group
45 if first || group != previous_group
46 if group.blank? && group != false
46 if group.blank? && group != false
47 group_name = "(#{l(:label_blank_value)})"
47 group_name = "(#{l(:label_blank_value)})"
48 else
48 else
49 group_name = format_object(group)
49 group_name = format_object(group)
50 end
50 end
51 group_name ||= ""
51 group_name ||= ""
52 group_count = issue_count_by_group[group]
52 group_count = issue_count_by_group[group]
53 group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
53 group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
54 end
54 end
55 end
55 end
56 yield issue, level, group_name, group_count, group_totals
56 yield issue, level, group_name, group_count, group_totals
57 previous_group, first = group, false
57 previous_group, first = group, false
58 end
58 end
59 end
59 end
60
60
61 # Renders a HTML/CSS tooltip
61 # Renders a HTML/CSS tooltip
62 #
62 #
63 # To use, a trigger div is needed. This is a div with the class of "tooltip"
63 # To use, a trigger div is needed. This is a div with the class of "tooltip"
64 # that contains this method wrapped in a span with the class of "tip"
64 # that contains this method wrapped in a span with the class of "tip"
65 #
65 #
66 # <div class="tooltip"><%= link_to_issue(issue) %>
66 # <div class="tooltip"><%= link_to_issue(issue) %>
67 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
67 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
68 # </div>
68 # </div>
69 #
69 #
70 def render_issue_tooltip(issue)
70 def render_issue_tooltip(issue)
71 @cached_label_status ||= l(:field_status)
71 @cached_label_status ||= l(:field_status)
72 @cached_label_start_date ||= l(:field_start_date)
72 @cached_label_start_date ||= l(:field_start_date)
73 @cached_label_due_date ||= l(:field_due_date)
73 @cached_label_due_date ||= l(:field_due_date)
74 @cached_label_assigned_to ||= l(:field_assigned_to)
74 @cached_label_assigned_to ||= l(:field_assigned_to)
75 @cached_label_priority ||= l(:field_priority)
75 @cached_label_priority ||= l(:field_priority)
76 @cached_label_project ||= l(:field_project)
76 @cached_label_project ||= l(:field_project)
77
77
78 link_to_issue(issue) + "<br /><br />".html_safe +
78 link_to_issue(issue) + "<br /><br />".html_safe +
79 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
79 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
80 "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
80 "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
81 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
81 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
82 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
82 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
83 "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
83 "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
84 "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
84 "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
85 end
85 end
86
86
87 def issue_heading(issue)
87 def issue_heading(issue)
88 h("#{issue.tracker} ##{issue.id}")
88 h("#{issue.tracker} ##{issue.id}")
89 end
89 end
90
90
91 def render_issue_subject_with_tree(issue)
91 def render_issue_subject_with_tree(issue)
92 s = ''
92 s = ''
93 ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
93 ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
94 ancestors.each do |ancestor|
94 ancestors.each do |ancestor|
95 s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
95 s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
96 end
96 end
97 s << '<div>'
97 s << '<div>'
98 subject = h(issue.subject)
98 subject = h(issue.subject)
99 if issue.is_private?
99 if issue.is_private?
100 subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
100 subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
101 end
101 end
102 s << content_tag('h3', subject)
102 s << content_tag('h3', subject)
103 s << '</div>' * (ancestors.size + 1)
103 s << '</div>' * (ancestors.size + 1)
104 s.html_safe
104 s.html_safe
105 end
105 end
106
106
107 def render_descendants_tree(issue)
107 def render_descendants_tree(issue)
108 s = '<form><table class="list issues">'
108 s = '<form><table class="list issues">'
109 issue_list(issue.descendants.visible.preload(:status, :priority, :tracker).sort_by(&:lft)) do |child, level|
109 issue_list(issue.descendants.visible.preload(:status, :priority, :tracker).sort_by(&:lft)) do |child, level|
110 css = "issue issue-#{child.id} hascontextmenu"
110 css = "issue issue-#{child.id} hascontextmenu"
111 css << " idnt idnt-#{level}" if level > 0
111 css << " idnt idnt-#{level}" if level > 0
112 s << content_tag('tr',
112 s << content_tag('tr',
113 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
113 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
114 content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
114 content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
115 content_tag('td', h(child.status)) +
115 content_tag('td', h(child.status)) +
116 content_tag('td', link_to_user(child.assigned_to)) +
116 content_tag('td', link_to_user(child.assigned_to)) +
117 content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
117 content_tag('td', progress_bar(child.done_ratio)),
118 :class => css)
118 :class => css)
119 end
119 end
120 s << '</table></form>'
120 s << '</table></form>'
121 s.html_safe
121 s.html_safe
122 end
122 end
123
123
124 def issue_estimated_hours_details(issue)
124 def issue_estimated_hours_details(issue)
125 if issue.total_estimated_hours.present?
125 if issue.total_estimated_hours.present?
126 if issue.total_estimated_hours == issue.estimated_hours
126 if issue.total_estimated_hours == issue.estimated_hours
127 l_hours_short(issue.estimated_hours)
127 l_hours_short(issue.estimated_hours)
128 else
128 else
129 s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
129 s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
130 s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
130 s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
131 s.html_safe
131 s.html_safe
132 end
132 end
133 end
133 end
134 end
134 end
135
135
136 def issue_spent_hours_details(issue)
136 def issue_spent_hours_details(issue)
137 if issue.total_spent_hours > 0
137 if issue.total_spent_hours > 0
138 if issue.total_spent_hours == issue.spent_hours
138 if issue.total_spent_hours == issue.spent_hours
139 link_to(l_hours_short(issue.spent_hours), issue_time_entries_path(issue))
139 link_to(l_hours_short(issue.spent_hours), issue_time_entries_path(issue))
140 else
140 else
141 s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
141 s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
142 s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), issue_time_entries_path(issue)})"
142 s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), issue_time_entries_path(issue)})"
143 s.html_safe
143 s.html_safe
144 end
144 end
145 end
145 end
146 end
146 end
147
147
148 # Returns an array of error messages for bulk edited issues
148 # Returns an array of error messages for bulk edited issues
149 def bulk_edit_error_messages(issues)
149 def bulk_edit_error_messages(issues)
150 messages = {}
150 messages = {}
151 issues.each do |issue|
151 issues.each do |issue|
152 issue.errors.full_messages.each do |message|
152 issue.errors.full_messages.each do |message|
153 messages[message] ||= []
153 messages[message] ||= []
154 messages[message] << issue
154 messages[message] << issue
155 end
155 end
156 end
156 end
157 messages.map { |message, issues|
157 messages.map { |message, issues|
158 "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
158 "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
159 }
159 }
160 end
160 end
161
161
162 # Returns a link for adding a new subtask to the given issue
162 # Returns a link for adding a new subtask to the given issue
163 def link_to_new_subtask(issue)
163 def link_to_new_subtask(issue)
164 attrs = {
164 attrs = {
165 :tracker_id => issue.tracker,
165 :tracker_id => issue.tracker,
166 :parent_issue_id => issue
166 :parent_issue_id => issue
167 }
167 }
168 link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
168 link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
169 end
169 end
170
170
171 class IssueFieldsRows
171 class IssueFieldsRows
172 include ActionView::Helpers::TagHelper
172 include ActionView::Helpers::TagHelper
173
173
174 def initialize
174 def initialize
175 @left = []
175 @left = []
176 @right = []
176 @right = []
177 end
177 end
178
178
179 def left(*args)
179 def left(*args)
180 args.any? ? @left << cells(*args) : @left
180 args.any? ? @left << cells(*args) : @left
181 end
181 end
182
182
183 def right(*args)
183 def right(*args)
184 args.any? ? @right << cells(*args) : @right
184 args.any? ? @right << cells(*args) : @right
185 end
185 end
186
186
187 def size
187 def size
188 @left.size > @right.size ? @left.size : @right.size
188 @left.size > @right.size ? @left.size : @right.size
189 end
189 end
190
190
191 def to_html
191 def to_html
192 content =
192 content =
193 content_tag('div', @left.reduce(&:+), :class => 'splitcontentleft') +
193 content_tag('div', @left.reduce(&:+), :class => 'splitcontentleft') +
194 content_tag('div', @right.reduce(&:+), :class => 'splitcontentleft')
194 content_tag('div', @right.reduce(&:+), :class => 'splitcontentleft')
195
195
196 content_tag('div', content, :class => 'splitcontent')
196 content_tag('div', content, :class => 'splitcontent')
197 end
197 end
198
198
199 def cells(label, text, options={})
199 def cells(label, text, options={})
200 options[:class] = [options[:class] || "", 'attribute'].join(' ')
200 options[:class] = [options[:class] || "", 'attribute'].join(' ')
201 content_tag 'div',
201 content_tag 'div',
202 content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
202 content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
203 options
203 options
204 end
204 end
205 end
205 end
206
206
207 def issue_fields_rows
207 def issue_fields_rows
208 r = IssueFieldsRows.new
208 r = IssueFieldsRows.new
209 yield r
209 yield r
210 r.to_html
210 r.to_html
211 end
211 end
212
212
213 def render_custom_fields_rows(issue)
213 def render_custom_fields_rows(issue)
214 values = issue.visible_custom_field_values
214 values = issue.visible_custom_field_values
215 return if values.empty?
215 return if values.empty?
216 half = (values.size / 2.0).ceil
216 half = (values.size / 2.0).ceil
217 issue_fields_rows do |rows|
217 issue_fields_rows do |rows|
218 values.each_with_index do |value, i|
218 values.each_with_index do |value, i|
219 css = "cf_#{value.custom_field.id}"
219 css = "cf_#{value.custom_field.id}"
220 m = (i < half ? :left : :right)
220 m = (i < half ? :left : :right)
221 rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
221 rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
222 end
222 end
223 end
223 end
224 end
224 end
225
225
226 # Returns the path for updating the issue form
226 # Returns the path for updating the issue form
227 # with project as the current project
227 # with project as the current project
228 def update_issue_form_path(project, issue)
228 def update_issue_form_path(project, issue)
229 options = {:format => 'js'}
229 options = {:format => 'js'}
230 if issue.new_record?
230 if issue.new_record?
231 if project
231 if project
232 new_project_issue_path(project, options)
232 new_project_issue_path(project, options)
233 else
233 else
234 new_issue_path(options)
234 new_issue_path(options)
235 end
235 end
236 else
236 else
237 edit_issue_path(issue, options)
237 edit_issue_path(issue, options)
238 end
238 end
239 end
239 end
240
240
241 # Returns the number of descendants for an array of issues
241 # Returns the number of descendants for an array of issues
242 def issues_descendant_count(issues)
242 def issues_descendant_count(issues)
243 ids = issues.reject(&:leaf?).map {|issue| issue.descendants.ids}.flatten.uniq
243 ids = issues.reject(&:leaf?).map {|issue| issue.descendants.ids}.flatten.uniq
244 ids -= issues.map(&:id)
244 ids -= issues.map(&:id)
245 ids.size
245 ids.size
246 end
246 end
247
247
248 def issues_destroy_confirmation_message(issues)
248 def issues_destroy_confirmation_message(issues)
249 issues = [issues] unless issues.is_a?(Array)
249 issues = [issues] unless issues.is_a?(Array)
250 message = l(:text_issues_destroy_confirmation)
250 message = l(:text_issues_destroy_confirmation)
251
251
252 descendant_count = issues_descendant_count(issues)
252 descendant_count = issues_descendant_count(issues)
253 if descendant_count > 0
253 if descendant_count > 0
254 message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
254 message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
255 end
255 end
256 message
256 message
257 end
257 end
258
258
259 # Returns an array of users that are proposed as watchers
259 # Returns an array of users that are proposed as watchers
260 # on the new issue form
260 # on the new issue form
261 def users_for_new_issue_watchers(issue)
261 def users_for_new_issue_watchers(issue)
262 users = issue.watcher_users
262 users = issue.watcher_users
263 if issue.project.users.count <= 20
263 if issue.project.users.count <= 20
264 users = (users + issue.project.users.sort).uniq
264 users = (users + issue.project.users.sort).uniq
265 end
265 end
266 users
266 users
267 end
267 end
268
268
269 def sidebar_queries
269 def sidebar_queries
270 unless @sidebar_queries
270 unless @sidebar_queries
271 @sidebar_queries = IssueQuery.visible.
271 @sidebar_queries = IssueQuery.visible.
272 order("#{Query.table_name}.name ASC").
272 order("#{Query.table_name}.name ASC").
273 # Project specific queries and global queries
273 # Project specific queries and global queries
274 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
274 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
275 to_a
275 to_a
276 end
276 end
277 @sidebar_queries
277 @sidebar_queries
278 end
278 end
279
279
280 def query_links(title, queries)
280 def query_links(title, queries)
281 return '' if queries.empty?
281 return '' if queries.empty?
282 # links to #index on issues/show
282 # links to #index on issues/show
283 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
283 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
284
284
285 content_tag('h3', title) + "\n" +
285 content_tag('h3', title) + "\n" +
286 content_tag('ul',
286 content_tag('ul',
287 queries.collect {|query|
287 queries.collect {|query|
288 css = 'query'
288 css = 'query'
289 css << ' selected' if query == @query
289 css << ' selected' if query == @query
290 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
290 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
291 }.join("\n").html_safe,
291 }.join("\n").html_safe,
292 :class => 'queries'
292 :class => 'queries'
293 ) + "\n"
293 ) + "\n"
294 end
294 end
295
295
296 def render_sidebar_queries
296 def render_sidebar_queries
297 out = ''.html_safe
297 out = ''.html_safe
298 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
298 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
299 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
299 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
300 out
300 out
301 end
301 end
302
302
303 def email_issue_attributes(issue, user)
303 def email_issue_attributes(issue, user)
304 items = []
304 items = []
305 %w(author status priority assigned_to category fixed_version).each do |attribute|
305 %w(author status priority assigned_to category fixed_version).each do |attribute|
306 unless issue.disabled_core_fields.include?(attribute+"_id")
306 unless issue.disabled_core_fields.include?(attribute+"_id")
307 items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
307 items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
308 end
308 end
309 end
309 end
310 issue.visible_custom_field_values(user).each do |value|
310 issue.visible_custom_field_values(user).each do |value|
311 items << "#{value.custom_field.name}: #{show_value(value, false)}"
311 items << "#{value.custom_field.name}: #{show_value(value, false)}"
312 end
312 end
313 items
313 items
314 end
314 end
315
315
316 def render_email_issue_attributes(issue, user, html=false)
316 def render_email_issue_attributes(issue, user, html=false)
317 items = email_issue_attributes(issue, user)
317 items = email_issue_attributes(issue, user)
318 if html
318 if html
319 content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
319 content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
320 else
320 else
321 items.map{|s| "* #{s}"}.join("\n")
321 items.map{|s| "* #{s}"}.join("\n")
322 end
322 end
323 end
323 end
324
324
325 # Returns the textual representation of a journal details
325 # Returns the textual representation of a journal details
326 # as an array of strings
326 # as an array of strings
327 def details_to_strings(details, no_html=false, options={})
327 def details_to_strings(details, no_html=false, options={})
328 options[:only_path] = (options[:only_path] == false ? false : true)
328 options[:only_path] = (options[:only_path] == false ? false : true)
329 strings = []
329 strings = []
330 values_by_field = {}
330 values_by_field = {}
331 details.each do |detail|
331 details.each do |detail|
332 if detail.property == 'cf'
332 if detail.property == 'cf'
333 field = detail.custom_field
333 field = detail.custom_field
334 if field && field.multiple?
334 if field && field.multiple?
335 values_by_field[field] ||= {:added => [], :deleted => []}
335 values_by_field[field] ||= {:added => [], :deleted => []}
336 if detail.old_value
336 if detail.old_value
337 values_by_field[field][:deleted] << detail.old_value
337 values_by_field[field][:deleted] << detail.old_value
338 end
338 end
339 if detail.value
339 if detail.value
340 values_by_field[field][:added] << detail.value
340 values_by_field[field][:added] << detail.value
341 end
341 end
342 next
342 next
343 end
343 end
344 end
344 end
345 strings << show_detail(detail, no_html, options)
345 strings << show_detail(detail, no_html, options)
346 end
346 end
347 if values_by_field.present?
347 if values_by_field.present?
348 multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
348 multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
349 values_by_field.each do |field, changes|
349 values_by_field.each do |field, changes|
350 if changes[:added].any?
350 if changes[:added].any?
351 detail = multiple_values_detail.new('cf', field.id.to_s, field)
351 detail = multiple_values_detail.new('cf', field.id.to_s, field)
352 detail.value = changes[:added]
352 detail.value = changes[:added]
353 strings << show_detail(detail, no_html, options)
353 strings << show_detail(detail, no_html, options)
354 end
354 end
355 if changes[:deleted].any?
355 if changes[:deleted].any?
356 detail = multiple_values_detail.new('cf', field.id.to_s, field)
356 detail = multiple_values_detail.new('cf', field.id.to_s, field)
357 detail.old_value = changes[:deleted]
357 detail.old_value = changes[:deleted]
358 strings << show_detail(detail, no_html, options)
358 strings << show_detail(detail, no_html, options)
359 end
359 end
360 end
360 end
361 end
361 end
362 strings
362 strings
363 end
363 end
364
364
365 # Returns the textual representation of a single journal detail
365 # Returns the textual representation of a single journal detail
366 def show_detail(detail, no_html=false, options={})
366 def show_detail(detail, no_html=false, options={})
367 multiple = false
367 multiple = false
368 show_diff = false
368 show_diff = false
369
369
370 case detail.property
370 case detail.property
371 when 'attr'
371 when 'attr'
372 field = detail.prop_key.to_s.gsub(/\_id$/, "")
372 field = detail.prop_key.to_s.gsub(/\_id$/, "")
373 label = l(("field_" + field).to_sym)
373 label = l(("field_" + field).to_sym)
374 case detail.prop_key
374 case detail.prop_key
375 when 'due_date', 'start_date'
375 when 'due_date', 'start_date'
376 value = format_date(detail.value.to_date) if detail.value
376 value = format_date(detail.value.to_date) if detail.value
377 old_value = format_date(detail.old_value.to_date) if detail.old_value
377 old_value = format_date(detail.old_value.to_date) if detail.old_value
378
378
379 when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
379 when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
380 'priority_id', 'category_id', 'fixed_version_id'
380 'priority_id', 'category_id', 'fixed_version_id'
381 value = find_name_by_reflection(field, detail.value)
381 value = find_name_by_reflection(field, detail.value)
382 old_value = find_name_by_reflection(field, detail.old_value)
382 old_value = find_name_by_reflection(field, detail.old_value)
383
383
384 when 'estimated_hours'
384 when 'estimated_hours'
385 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
385 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
386 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
386 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
387
387
388 when 'parent_id'
388 when 'parent_id'
389 label = l(:field_parent_issue)
389 label = l(:field_parent_issue)
390 value = "##{detail.value}" unless detail.value.blank?
390 value = "##{detail.value}" unless detail.value.blank?
391 old_value = "##{detail.old_value}" unless detail.old_value.blank?
391 old_value = "##{detail.old_value}" unless detail.old_value.blank?
392
392
393 when 'is_private'
393 when 'is_private'
394 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
394 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
395 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
395 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
396
396
397 when 'description'
397 when 'description'
398 show_diff = true
398 show_diff = true
399 end
399 end
400 when 'cf'
400 when 'cf'
401 custom_field = detail.custom_field
401 custom_field = detail.custom_field
402 if custom_field
402 if custom_field
403 label = custom_field.name
403 label = custom_field.name
404 if custom_field.format.class.change_as_diff
404 if custom_field.format.class.change_as_diff
405 show_diff = true
405 show_diff = true
406 else
406 else
407 multiple = custom_field.multiple?
407 multiple = custom_field.multiple?
408 value = format_value(detail.value, custom_field) if detail.value
408 value = format_value(detail.value, custom_field) if detail.value
409 old_value = format_value(detail.old_value, custom_field) if detail.old_value
409 old_value = format_value(detail.old_value, custom_field) if detail.old_value
410 end
410 end
411 end
411 end
412 when 'attachment'
412 when 'attachment'
413 label = l(:label_attachment)
413 label = l(:label_attachment)
414 when 'relation'
414 when 'relation'
415 if detail.value && !detail.old_value
415 if detail.value && !detail.old_value
416 rel_issue = Issue.visible.find_by_id(detail.value)
416 rel_issue = Issue.visible.find_by_id(detail.value)
417 value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
417 value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
418 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
418 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
419 elsif detail.old_value && !detail.value
419 elsif detail.old_value && !detail.value
420 rel_issue = Issue.visible.find_by_id(detail.old_value)
420 rel_issue = Issue.visible.find_by_id(detail.old_value)
421 old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
421 old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
422 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
422 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
423 end
423 end
424 relation_type = IssueRelation::TYPES[detail.prop_key]
424 relation_type = IssueRelation::TYPES[detail.prop_key]
425 label = l(relation_type[:name]) if relation_type
425 label = l(relation_type[:name]) if relation_type
426 end
426 end
427 call_hook(:helper_issues_show_detail_after_setting,
427 call_hook(:helper_issues_show_detail_after_setting,
428 {:detail => detail, :label => label, :value => value, :old_value => old_value })
428 {:detail => detail, :label => label, :value => value, :old_value => old_value })
429
429
430 label ||= detail.prop_key
430 label ||= detail.prop_key
431 value ||= detail.value
431 value ||= detail.value
432 old_value ||= detail.old_value
432 old_value ||= detail.old_value
433
433
434 unless no_html
434 unless no_html
435 label = content_tag('strong', label)
435 label = content_tag('strong', label)
436 old_value = content_tag("i", h(old_value)) if detail.old_value
436 old_value = content_tag("i", h(old_value)) if detail.old_value
437 if detail.old_value && detail.value.blank? && detail.property != 'relation'
437 if detail.old_value && detail.value.blank? && detail.property != 'relation'
438 old_value = content_tag("del", old_value)
438 old_value = content_tag("del", old_value)
439 end
439 end
440 if detail.property == 'attachment' && value.present? &&
440 if detail.property == 'attachment' && value.present? &&
441 atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
441 atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
442 # Link to the attachment if it has not been removed
442 # Link to the attachment if it has not been removed
443 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
443 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
444 if options[:only_path] != false && atta.is_text?
444 if options[:only_path] != false && atta.is_text?
445 value += link_to(
445 value += link_to(
446 image_tag('magnifier.png'),
446 image_tag('magnifier.png'),
447 :controller => 'attachments', :action => 'show',
447 :controller => 'attachments', :action => 'show',
448 :id => atta, :filename => atta.filename
448 :id => atta, :filename => atta.filename
449 )
449 )
450 end
450 end
451 else
451 else
452 value = content_tag("i", h(value)) if value
452 value = content_tag("i", h(value)) if value
453 end
453 end
454 end
454 end
455
455
456 if show_diff
456 if show_diff
457 s = l(:text_journal_changed_no_detail, :label => label)
457 s = l(:text_journal_changed_no_detail, :label => label)
458 unless no_html
458 unless no_html
459 diff_link = link_to 'diff',
459 diff_link = link_to 'diff',
460 {:controller => 'journals', :action => 'diff', :id => detail.journal_id,
460 {:controller => 'journals', :action => 'diff', :id => detail.journal_id,
461 :detail_id => detail.id, :only_path => options[:only_path]},
461 :detail_id => detail.id, :only_path => options[:only_path]},
462 :title => l(:label_view_diff)
462 :title => l(:label_view_diff)
463 s << " (#{ diff_link })"
463 s << " (#{ diff_link })"
464 end
464 end
465 s.html_safe
465 s.html_safe
466 elsif detail.value.present?
466 elsif detail.value.present?
467 case detail.property
467 case detail.property
468 when 'attr', 'cf'
468 when 'attr', 'cf'
469 if detail.old_value.present?
469 if detail.old_value.present?
470 l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
470 l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
471 elsif multiple
471 elsif multiple
472 l(:text_journal_added, :label => label, :value => value).html_safe
472 l(:text_journal_added, :label => label, :value => value).html_safe
473 else
473 else
474 l(:text_journal_set_to, :label => label, :value => value).html_safe
474 l(:text_journal_set_to, :label => label, :value => value).html_safe
475 end
475 end
476 when 'attachment', 'relation'
476 when 'attachment', 'relation'
477 l(:text_journal_added, :label => label, :value => value).html_safe
477 l(:text_journal_added, :label => label, :value => value).html_safe
478 end
478 end
479 else
479 else
480 l(:text_journal_deleted, :label => label, :old => old_value).html_safe
480 l(:text_journal_deleted, :label => label, :old => old_value).html_safe
481 end
481 end
482 end
482 end
483
483
484 # Find the name of an associated record stored in the field attribute
484 # Find the name of an associated record stored in the field attribute
485 def find_name_by_reflection(field, id)
485 def find_name_by_reflection(field, id)
486 unless id.present?
486 unless id.present?
487 return nil
487 return nil
488 end
488 end
489 @detail_value_name_by_reflection ||= Hash.new do |hash, key|
489 @detail_value_name_by_reflection ||= Hash.new do |hash, key|
490 association = Issue.reflect_on_association(key.first.to_sym)
490 association = Issue.reflect_on_association(key.first.to_sym)
491 name = nil
491 name = nil
492 if association
492 if association
493 record = association.klass.find_by_id(key.last)
493 record = association.klass.find_by_id(key.last)
494 if record
494 if record
495 name = record.name.force_encoding('UTF-8')
495 name = record.name.force_encoding('UTF-8')
496 end
496 end
497 end
497 end
498 hash[key] = name
498 hash[key] = name
499 end
499 end
500 @detail_value_name_by_reflection[[field, id]]
500 @detail_value_name_by_reflection[[field, id]]
501 end
501 end
502
502
503 # Renders issue children recursively
503 # Renders issue children recursively
504 def render_api_issue_children(issue, api)
504 def render_api_issue_children(issue, api)
505 return if issue.leaf?
505 return if issue.leaf?
506 api.array :children do
506 api.array :children do
507 issue.children.each do |child|
507 issue.children.each do |child|
508 api.issue(:id => child.id) do
508 api.issue(:id => child.id) do
509 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
509 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
510 api.subject child.subject
510 api.subject child.subject
511 render_api_issue_children(child, api)
511 render_api_issue_children(child, api)
512 end
512 end
513 end
513 end
514 end
514 end
515 end
515 end
516 end
516 end
@@ -1,246 +1,246
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module QueriesHelper
20 module QueriesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def filters_options_for_select(query)
23 def filters_options_for_select(query)
24 ungrouped = []
24 ungrouped = []
25 grouped = {}
25 grouped = {}
26 query.available_filters.map do |field, field_options|
26 query.available_filters.map do |field, field_options|
27 if [:tree, :relation].include?(field_options[:type])
27 if [:tree, :relation].include?(field_options[:type])
28 group = :label_related_issues
28 group = :label_related_issues
29 elsif field =~ /^(.+)\./
29 elsif field =~ /^(.+)\./
30 # association filters
30 # association filters
31 group = "field_#{$1}"
31 group = "field_#{$1}"
32 elsif %w(member_of_group assigned_to_role).include?(field)
32 elsif %w(member_of_group assigned_to_role).include?(field)
33 group = :field_assigned_to
33 group = :field_assigned_to
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
35 group = :label_date
35 group = :label_date
36 end
36 end
37 if group
37 if group
38 (grouped[group] ||= []) << [field_options[:name], field]
38 (grouped[group] ||= []) << [field_options[:name], field]
39 else
39 else
40 ungrouped << [field_options[:name], field]
40 ungrouped << [field_options[:name], field]
41 end
41 end
42 end
42 end
43 # Don't group dates if there's only one (eg. time entries filters)
43 # Don't group dates if there's only one (eg. time entries filters)
44 if grouped[:label_date].try(:size) == 1
44 if grouped[:label_date].try(:size) == 1
45 ungrouped << grouped.delete(:label_date).first
45 ungrouped << grouped.delete(:label_date).first
46 end
46 end
47 s = options_for_select([[]] + ungrouped)
47 s = options_for_select([[]] + ungrouped)
48 if grouped.present?
48 if grouped.present?
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
50 s << grouped_options_for_select(localized_grouped)
50 s << grouped_options_for_select(localized_grouped)
51 end
51 end
52 s
52 s
53 end
53 end
54
54
55 def query_filters_hidden_tags(query)
55 def query_filters_hidden_tags(query)
56 tags = ''.html_safe
56 tags = ''.html_safe
57 query.filters.each do |field, options|
57 query.filters.each do |field, options|
58 tags << hidden_field_tag("f[]", field, :id => nil)
58 tags << hidden_field_tag("f[]", field, :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
60 options[:values].each do |value|
60 options[:values].each do |value|
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
62 end
62 end
63 end
63 end
64 tags
64 tags
65 end
65 end
66
66
67 def query_columns_hidden_tags(query)
67 def query_columns_hidden_tags(query)
68 tags = ''.html_safe
68 tags = ''.html_safe
69 query.columns.each do |column|
69 query.columns.each do |column|
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
71 end
71 end
72 tags
72 tags
73 end
73 end
74
74
75 def query_hidden_tags(query)
75 def query_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
77 end
77 end
78
78
79 def available_block_columns_tags(query)
79 def available_block_columns_tags(query)
80 tags = ''.html_safe
80 tags = ''.html_safe
81 query.available_block_columns.each do |column|
81 query.available_block_columns.each do |column|
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
83 end
83 end
84 tags
84 tags
85 end
85 end
86
86
87 def available_totalable_columns_tags(query)
87 def available_totalable_columns_tags(query)
88 tags = ''.html_safe
88 tags = ''.html_safe
89 query.available_totalable_columns.each do |column|
89 query.available_totalable_columns.each do |column|
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
91 end
91 end
92 tags
92 tags
93 end
93 end
94
94
95 def query_available_inline_columns_options(query)
95 def query_available_inline_columns_options(query)
96 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
96 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
97 end
97 end
98
98
99 def query_selected_inline_columns_options(query)
99 def query_selected_inline_columns_options(query)
100 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
100 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
101 end
101 end
102
102
103 def render_query_columns_selection(query, options={})
103 def render_query_columns_selection(query, options={})
104 tag_name = (options[:name] || 'c') + '[]'
104 tag_name = (options[:name] || 'c') + '[]'
105 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
105 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
106 end
106 end
107
107
108 def render_query_totals(query)
108 def render_query_totals(query)
109 return unless query.totalable_columns.present?
109 return unless query.totalable_columns.present?
110 totals = query.totalable_columns.map do |column|
110 totals = query.totalable_columns.map do |column|
111 total_tag(column, query.total_for(column))
111 total_tag(column, query.total_for(column))
112 end
112 end
113 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
113 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
114 end
114 end
115
115
116 def total_tag(column, value)
116 def total_tag(column, value)
117 label = content_tag('span', "#{column.caption}:")
117 label = content_tag('span', "#{column.caption}:")
118 value = content_tag('span', format_object(value), :class => 'value')
118 value = content_tag('span', format_object(value), :class => 'value')
119 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
119 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
120 end
120 end
121
121
122 def column_header(column)
122 def column_header(column)
123 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
123 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
124 :default_order => column.default_order) :
124 :default_order => column.default_order) :
125 content_tag('th', h(column.caption))
125 content_tag('th', h(column.caption))
126 end
126 end
127
127
128 def column_content(column, issue)
128 def column_content(column, issue)
129 value = column.value_object(issue)
129 value = column.value_object(issue)
130 if value.is_a?(Array)
130 if value.is_a?(Array)
131 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
131 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
132 else
132 else
133 column_value(column, issue, value)
133 column_value(column, issue, value)
134 end
134 end
135 end
135 end
136
136
137 def column_value(column, issue, value)
137 def column_value(column, issue, value)
138 case column.name
138 case column.name
139 when :id
139 when :id
140 link_to value, issue_path(issue)
140 link_to value, issue_path(issue)
141 when :subject
141 when :subject
142 link_to value, issue_path(issue)
142 link_to value, issue_path(issue)
143 when :parent
143 when :parent
144 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
144 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
145 when :description
145 when :description
146 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
146 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
147 when :done_ratio
147 when :done_ratio
148 progress_bar(value, :width => '80px')
148 progress_bar(value)
149 when :relations
149 when :relations
150 content_tag('span',
150 content_tag('span',
151 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
151 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
152 :class => value.css_classes_for(issue))
152 :class => value.css_classes_for(issue))
153 else
153 else
154 format_object(value)
154 format_object(value)
155 end
155 end
156 end
156 end
157
157
158 def csv_content(column, issue)
158 def csv_content(column, issue)
159 value = column.value_object(issue)
159 value = column.value_object(issue)
160 if value.is_a?(Array)
160 if value.is_a?(Array)
161 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
161 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
162 else
162 else
163 csv_value(column, issue, value)
163 csv_value(column, issue, value)
164 end
164 end
165 end
165 end
166
166
167 def csv_value(column, object, value)
167 def csv_value(column, object, value)
168 format_object(value, false) do |value|
168 format_object(value, false) do |value|
169 case value.class.name
169 case value.class.name
170 when 'Float'
170 when 'Float'
171 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
171 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
172 when 'IssueRelation'
172 when 'IssueRelation'
173 value.to_s(object)
173 value.to_s(object)
174 when 'Issue'
174 when 'Issue'
175 if object.is_a?(TimeEntry)
175 if object.is_a?(TimeEntry)
176 "#{value.tracker} ##{value.id}: #{value.subject}"
176 "#{value.tracker} ##{value.id}: #{value.subject}"
177 else
177 else
178 value.id
178 value.id
179 end
179 end
180 else
180 else
181 value
181 value
182 end
182 end
183 end
183 end
184 end
184 end
185
185
186 def query_to_csv(items, query, options={})
186 def query_to_csv(items, query, options={})
187 options ||= {}
187 options ||= {}
188 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
188 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
189 query.available_block_columns.each do |column|
189 query.available_block_columns.each do |column|
190 if options[column.name].present?
190 if options[column.name].present?
191 columns << column
191 columns << column
192 end
192 end
193 end
193 end
194
194
195 Redmine::Export::CSV.generate do |csv|
195 Redmine::Export::CSV.generate do |csv|
196 # csv header fields
196 # csv header fields
197 csv << columns.map {|c| c.caption.to_s}
197 csv << columns.map {|c| c.caption.to_s}
198 # csv lines
198 # csv lines
199 items.each do |item|
199 items.each do |item|
200 csv << columns.map {|c| csv_content(c, item)}
200 csv << columns.map {|c| csv_content(c, item)}
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 # Retrieve query from session or build a new query
205 # Retrieve query from session or build a new query
206 def retrieve_query
206 def retrieve_query
207 if !params[:query_id].blank?
207 if !params[:query_id].blank?
208 cond = "project_id IS NULL"
208 cond = "project_id IS NULL"
209 cond << " OR project_id = #{@project.id}" if @project
209 cond << " OR project_id = #{@project.id}" if @project
210 @query = IssueQuery.where(cond).find(params[:query_id])
210 @query = IssueQuery.where(cond).find(params[:query_id])
211 raise ::Unauthorized unless @query.visible?
211 raise ::Unauthorized unless @query.visible?
212 @query.project = @project
212 @query.project = @project
213 session[:query] = {:id => @query.id, :project_id => @query.project_id}
213 session[:query] = {:id => @query.id, :project_id => @query.project_id}
214 sort_clear
214 sort_clear
215 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
215 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
216 # Give it a name, required to be valid
216 # Give it a name, required to be valid
217 @query = IssueQuery.new(:name => "_")
217 @query = IssueQuery.new(:name => "_")
218 @query.project = @project
218 @query.project = @project
219 @query.build_from_params(params)
219 @query.build_from_params(params)
220 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
220 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
221 else
221 else
222 # retrieve from session
222 # retrieve from session
223 @query = nil
223 @query = nil
224 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
224 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
225 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
225 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
226 @query.project = @project
226 @query.project = @project
227 end
227 end
228 end
228 end
229
229
230 def retrieve_query_from_session
230 def retrieve_query_from_session
231 if session[:query]
231 if session[:query]
232 if session[:query][:id]
232 if session[:query][:id]
233 @query = IssueQuery.find_by_id(session[:query][:id])
233 @query = IssueQuery.find_by_id(session[:query][:id])
234 return unless @query
234 return unless @query
235 else
235 else
236 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
236 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
237 end
237 end
238 if session[:query].has_key?(:project_id)
238 if session[:query].has_key?(:project_id)
239 @query.project_id = session[:query][:project_id]
239 @query.project_id = session[:query][:project_id]
240 else
240 else
241 @query.project = @project
241 @query.project = @project
242 end
242 end
243 @query
243 @query
244 end
244 end
245 end
245 end
246 end
246 end
@@ -1,162 +1,162
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <h2><%= issue_heading(@issue) %></h2>
3 <h2><%= issue_heading(@issue) %></h2>
4
4
5 <div class="<%= @issue.css_classes %> details">
5 <div class="<%= @issue.css_classes %> details">
6 <% if @prev_issue_id || @next_issue_id %>
6 <% if @prev_issue_id || @next_issue_id %>
7 <div class="next-prev-links contextual">
7 <div class="next-prev-links contextual">
8 <%= link_to_if @prev_issue_id,
8 <%= link_to_if @prev_issue_id,
9 "\xc2\xab #{l(:label_previous)}",
9 "\xc2\xab #{l(:label_previous)}",
10 (@prev_issue_id ? issue_path(@prev_issue_id) : nil),
10 (@prev_issue_id ? issue_path(@prev_issue_id) : nil),
11 :title => "##{@prev_issue_id}",
11 :title => "##{@prev_issue_id}",
12 :accesskey => accesskey(:previous) %> |
12 :accesskey => accesskey(:previous) %> |
13 <% if @issue_position && @issue_count %>
13 <% if @issue_position && @issue_count %>
14 <span class="position"><%= l(:label_item_position, :position => @issue_position, :count => @issue_count) %></span> |
14 <span class="position"><%= l(:label_item_position, :position => @issue_position, :count => @issue_count) %></span> |
15 <% end %>
15 <% end %>
16 <%= link_to_if @next_issue_id,
16 <%= link_to_if @next_issue_id,
17 "#{l(:label_next)} \xc2\xbb",
17 "#{l(:label_next)} \xc2\xbb",
18 (@next_issue_id ? issue_path(@next_issue_id) : nil),
18 (@next_issue_id ? issue_path(@next_issue_id) : nil),
19 :title => "##{@next_issue_id}",
19 :title => "##{@next_issue_id}",
20 :accesskey => accesskey(:next) %>
20 :accesskey => accesskey(:next) %>
21 </div>
21 </div>
22 <% end %>
22 <% end %>
23
23
24 <%= avatar(@issue.author, :size => "50") %>
24 <%= avatar(@issue.author, :size => "50") %>
25
25
26 <div class="subject">
26 <div class="subject">
27 <%= render_issue_subject_with_tree(@issue) %>
27 <%= render_issue_subject_with_tree(@issue) %>
28 </div>
28 </div>
29 <p class="author">
29 <p class="author">
30 <%= authoring @issue.created_on, @issue.author %>.
30 <%= authoring @issue.created_on, @issue.author %>.
31 <% if @issue.created_on != @issue.updated_on %>
31 <% if @issue.created_on != @issue.updated_on %>
32 <%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
32 <%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
33 <% end %>
33 <% end %>
34 </p>
34 </p>
35
35
36 <div class="attributes">
36 <div class="attributes">
37 <%= issue_fields_rows do |rows|
37 <%= issue_fields_rows do |rows|
38 rows.left l(:field_status), @issue.status.name, :class => 'status'
38 rows.left l(:field_status), @issue.status.name, :class => 'status'
39 rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
39 rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
40
40
41 unless @issue.disabled_core_fields.include?('assigned_to_id')
41 unless @issue.disabled_core_fields.include?('assigned_to_id')
42 rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
42 rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
43 end
43 end
44 unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
44 unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
45 rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
45 rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
46 end
46 end
47 unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
47 unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
48 rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
48 rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
49 end
49 end
50
50
51 unless @issue.disabled_core_fields.include?('start_date')
51 unless @issue.disabled_core_fields.include?('start_date')
52 rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
52 rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
53 end
53 end
54 unless @issue.disabled_core_fields.include?('due_date')
54 unless @issue.disabled_core_fields.include?('due_date')
55 rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
55 rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
56 end
56 end
57 unless @issue.disabled_core_fields.include?('done_ratio')
57 unless @issue.disabled_core_fields.include?('done_ratio')
58 rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
58 rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :legend => "#{@issue.done_ratio}%"), :class => 'progress'
59 end
59 end
60 unless @issue.disabled_core_fields.include?('estimated_hours')
60 unless @issue.disabled_core_fields.include?('estimated_hours')
61 if @issue.estimated_hours.present? || @issue.total_estimated_hours.to_f > 0
61 if @issue.estimated_hours.present? || @issue.total_estimated_hours.to_f > 0
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
62 rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours'
63 end
63 end
64 end
64 end
65 if User.current.allowed_to_view_all_time_entries?(@project)
65 if User.current.allowed_to_view_all_time_entries?(@project)
66 if @issue.total_spent_hours > 0
66 if @issue.total_spent_hours > 0
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
67 rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time'
68 end
68 end
69 end
69 end
70 end %>
70 end %>
71 <%= render_custom_fields_rows(@issue) %>
71 <%= render_custom_fields_rows(@issue) %>
72 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
72 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
73 </div>
73 </div>
74
74
75 <% if @issue.description? || @issue.attachments.any? -%>
75 <% if @issue.description? || @issue.attachments.any? -%>
76 <hr />
76 <hr />
77 <% if @issue.description? %>
77 <% if @issue.description? %>
78 <div class="description">
78 <div class="description">
79 <div class="contextual">
79 <div class="contextual">
80 <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if authorize_for('issues', 'edit') %>
80 <%= link_to l(:button_quote), quoted_issue_path(@issue), :remote => true, :method => 'post', :class => 'icon icon-comment' if authorize_for('issues', 'edit') %>
81 </div>
81 </div>
82
82
83 <p><strong><%=l(:field_description)%></strong></p>
83 <p><strong><%=l(:field_description)%></strong></p>
84 <div class="wiki">
84 <div class="wiki">
85 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
85 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
86 </div>
86 </div>
87 </div>
87 </div>
88 <% end %>
88 <% end %>
89 <%= link_to_attachments @issue, :thumbnails => true %>
89 <%= link_to_attachments @issue, :thumbnails => true %>
90 <% end -%>
90 <% end -%>
91
91
92 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
92 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
93
93
94 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
94 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
95 <hr />
95 <hr />
96 <div id="issue_tree">
96 <div id="issue_tree">
97 <div class="contextual">
97 <div class="contextual">
98 <%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
98 <%= link_to_new_subtask(@issue) if User.current.allowed_to?(:manage_subtasks, @project) %>
99 </div>
99 </div>
100 <p><strong><%=l(:label_subtask_plural)%></strong></p>
100 <p><strong><%=l(:label_subtask_plural)%></strong></p>
101 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
101 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
102 </div>
102 </div>
103 <% end %>
103 <% end %>
104
104
105 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
105 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
106 <hr />
106 <hr />
107 <div id="relations">
107 <div id="relations">
108 <%= render :partial => 'relations' %>
108 <%= render :partial => 'relations' %>
109 </div>
109 </div>
110 <% end %>
110 <% end %>
111
111
112 </div>
112 </div>
113
113
114 <% if @changesets.present? %>
114 <% if @changesets.present? %>
115 <div id="issue-changesets">
115 <div id="issue-changesets">
116 <h3><%=l(:label_associated_revisions)%></h3>
116 <h3><%=l(:label_associated_revisions)%></h3>
117 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
117 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
118 </div>
118 </div>
119 <% end %>
119 <% end %>
120
120
121 <% if @journals.present? %>
121 <% if @journals.present? %>
122 <div id="history">
122 <div id="history">
123 <h3><%=l(:label_history)%></h3>
123 <h3><%=l(:label_history)%></h3>
124 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
124 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
125 </div>
125 </div>
126 <% end %>
126 <% end %>
127
127
128
128
129 <div style="clear: both;"></div>
129 <div style="clear: both;"></div>
130 <%= render :partial => 'action_menu' %>
130 <%= render :partial => 'action_menu' %>
131
131
132 <div style="clear: both;"></div>
132 <div style="clear: both;"></div>
133 <% if @issue.editable? %>
133 <% if @issue.editable? %>
134 <div id="update" style="display:none;">
134 <div id="update" style="display:none;">
135 <h3><%= l(:button_edit) %></h3>
135 <h3><%= l(:button_edit) %></h3>
136 <%= render :partial => 'edit' %>
136 <%= render :partial => 'edit' %>
137 </div>
137 </div>
138 <% end %>
138 <% end %>
139
139
140 <% other_formats_links do |f| %>
140 <% other_formats_links do |f| %>
141 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
141 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
142 <%= f.link_to 'PDF' %>
142 <%= f.link_to 'PDF' %>
143 <% end %>
143 <% end %>
144
144
145 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
145 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
146
146
147 <% content_for :sidebar do %>
147 <% content_for :sidebar do %>
148 <%= render :partial => 'issues/sidebar' %>
148 <%= render :partial => 'issues/sidebar' %>
149
149
150 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
150 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
151 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
151 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
152 <div id="watchers">
152 <div id="watchers">
153 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
153 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
154 </div>
154 </div>
155 <% end %>
155 <% end %>
156 <% end %>
156 <% end %>
157
157
158 <% content_for :header_tags do %>
158 <% content_for :header_tags do %>
159 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
159 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
160 <% end %>
160 <% end %>
161
161
162 <%= context_menu issues_context_menu_path %>
162 <%= context_menu issues_context_menu_path %>
@@ -1,33 +1,32
1 <%= form_tag({}, :id => "status_by_form") do -%>
1 <%= form_tag({}, :id => "status_by_form") do -%>
2 <fieldset>
2 <fieldset>
3 <legend>
3 <legend>
4 <%= l(:label_issues_by,
4 <%= l(:label_issues_by,
5 select_tag('status_by',
5 select_tag('status_by',
6 status_by_options_for_select(criteria),
6 status_by_options_for_select(criteria),
7 :id => 'status_by_select',
7 :id => 'status_by_select',
8 :data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %>
8 :data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %>
9 </legend>
9 </legend>
10 <% if counts.empty? %>
10 <% if counts.empty? %>
11 <p><em><%= l(:label_no_data) %></em></p>
11 <p><em><%= l(:label_no_data) %></em></p>
12 <% else %>
12 <% else %>
13 <table>
13 <table>
14 <% counts.each do |count| %>
14 <% counts.each do |count| %>
15 <tr>
15 <tr>
16 <td style="width:130px; text-align:right;">
16 <td style="width:130px; text-align:right;">
17 <% if count[:group] -%>
17 <% if count[:group] -%>
18 <%= link_to(count[:group], project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %>
18 <%= link_to(count[:group], project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %>
19 <% else -%>
19 <% else -%>
20 <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %>
20 <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %>
21 <% end %>
21 <% end %>
22 </td>
22 </td>
23 <td style="width:240px;">
23 <td style="width:240px;">
24 <%= progress_bar((count[:closed].to_f / count[:total])*100,
24 <%= progress_bar((count[:closed].to_f / count[:total])*100,
25 :legend => "#{count[:closed]}/#{count[:total]}",
25 :legend => "#{count[:closed]}/#{count[:total]}") %>
26 :width => "#{(count[:total].to_f / max * 200).floor}px;") %>
27 </td>
26 </td>
28 </tr>
27 </tr>
29 <% end %>
28 <% end %>
30 </table>
29 </table>
31 <% end %>
30 <% end %>
32 </fieldset>
31 </fieldset>
33 <% end %>
32 <% end %>
@@ -1,33 +1,35
1 <div class="version-overview">
1 <% if version.completed? %>
2 <% if version.completed? %>
2 <p><%= format_date(version.effective_date) %></p>
3 <p><%= format_date(version.effective_date) %></p>
3 <% elsif version.effective_date %>
4 <% elsif version.effective_date %>
4 <p><strong><%= due_date_distance_in_words(version.effective_date) %></strong> (<%= format_date(version.effective_date) %>)</p>
5 <p><strong><%= due_date_distance_in_words(version.effective_date) %></strong> (<%= format_date(version.effective_date) %>)</p>
5 <% end %>
6 <% end %>
6
7
7 <p><%=h version.description %></p>
8 <p><%=h version.description %></p>
8 <% if version.custom_field_values.any? %>
9 <% if version.custom_field_values.any? %>
9 <ul>
10 <ul>
10 <% render_custom_field_values(version) do |custom_field, formatted| %>
11 <% render_custom_field_values(version) do |custom_field, formatted| %>
11 <li><span class="label"><%= custom_field.name %>:</span> <%= formatted %></li>
12 <li><span class="label"><%= custom_field.name %>:</span> <%= formatted %></li>
12 <% end %>
13 <% end %>
13 </ul>
14 </ul>
14 <% end %>
15 <% end %>
15
16
16 <% if version.issues_count > 0 %>
17 <% if version.issues_count > 0 %>
17 <%= progress_bar([version.closed_percent, version.completed_percent],
18 <%= progress_bar([version.closed_percent, version.completed_percent],
18 :width => '40em', :legend => ('%0.0f%' % version.completed_percent)) %>
19 :legend => ('%0.0f%' % version.completed_percent)) %>
19 <p class="progress-info">
20 <p class="progress-info">
20 <%= link_to(l(:label_x_issues, :count => version.issues_count),
21 <%= link_to(l(:label_x_issues, :count => version.issues_count),
21 version_filtered_issues_path(version, :status_id => '*')) %>
22 version_filtered_issues_path(version, :status_id => '*')) %>
22 &nbsp;
23 &nbsp;
23 (<%= link_to_if(version.closed_issues_count > 0,
24 (<%= link_to_if(version.closed_issues_count > 0,
24 l(:label_x_closed_issues_abbr, :count => version.closed_issues_count),
25 l(:label_x_closed_issues_abbr, :count => version.closed_issues_count),
25 version_filtered_issues_path(version, :status_id => 'c')) %>
26 version_filtered_issues_path(version, :status_id => 'c')) %>
26 &#8212;
27 &#8212;
27 <%= link_to_if(version.open_issues_count > 0,
28 <%= link_to_if(version.open_issues_count > 0,
28 l(:label_x_open_issues_abbr, :count => version.open_issues_count),
29 l(:label_x_open_issues_abbr, :count => version.open_issues_count),
29 version_filtered_issues_path(version, :status_id => 'o')) %>)
30 version_filtered_issues_path(version, :status_id => 'o')) %>)
30 </p>
31 </p>
31 <% else %>
32 <% else %>
32 <p class="progress-info"><%= l(:label_roadmap_no_issues) %></p>
33 <p class="progress-info"><%= l(:label_roadmap_no_issues) %></p>
33 <% end %>
34 <% end %>
35 </div>
@@ -1,1262 +1,1265
1 html {overflow-y:scroll;}
1 html {overflow-y:scroll;}
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#333; margin: 0; padding: 0; min-width: 900px; }
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#333; margin: 0; padding: 0; min-width: 900px; }
3
3
4 h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;}
4 h1, h2, h3, h4 {font-family: "Trebuchet MS", Verdana, sans-serif;padding: 2px 10px 1px 0px;margin: 0 0 10px 0;}
5 #content h1, h2, h3, h4 {color: #555;}
5 #content h1, h2, h3, h4 {color: #555;}
6 h2, .wiki h1 {font-size: 20px;}
6 h2, .wiki h1 {font-size: 20px;}
7 h3, .wiki h2 {font-size: 16px;}
7 h3, .wiki h2 {font-size: 16px;}
8 h4, .wiki h3 {font-size: 13px;}
8 h4, .wiki h3 {font-size: 13px;}
9 h4 {border-bottom: 1px dotted #bbb;}
9 h4 {border-bottom: 1px dotted #bbb;}
10 pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
10 pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
11
11
12 /***** Layout *****/
12 /***** Layout *****/
13 #wrapper {background: white;}
13 #wrapper {background: white;}
14
14
15 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
15 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
16 #top-menu ul {margin: 0; padding: 0;}
16 #top-menu ul {margin: 0; padding: 0;}
17 #top-menu li {
17 #top-menu li {
18 float:left;
18 float:left;
19 list-style-type:none;
19 list-style-type:none;
20 margin: 0px 0px 0px 0px;
20 margin: 0px 0px 0px 0px;
21 padding: 0px 0px 0px 0px;
21 padding: 0px 0px 0px 0px;
22 white-space:nowrap;
22 white-space:nowrap;
23 }
23 }
24 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
24 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
25 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
25 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
26
26
27 #account {float:right;}
27 #account {float:right;}
28
28
29 #header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;}
29 #header {min-height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 20px 6px; position:relative;}
30 #header a {color:#f8f8f8;}
30 #header a {color:#f8f8f8;}
31 #header h1 a.ancestor { font-size: 80%; }
31 #header h1 a.ancestor { font-size: 80%; }
32 #quick-search {float:right;}
32 #quick-search {float:right;}
33
33
34 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
34 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
35 #main-menu ul {margin: 0; padding: 0;}
35 #main-menu ul {margin: 0; padding: 0;}
36 #main-menu li {
36 #main-menu li {
37 float:left;
37 float:left;
38 list-style-type:none;
38 list-style-type:none;
39 margin: 0px 2px 0px 0px;
39 margin: 0px 2px 0px 0px;
40 padding: 0px 0px 0px 0px;
40 padding: 0px 0px 0px 0px;
41 white-space:nowrap;
41 white-space:nowrap;
42 }
42 }
43 #main-menu li a {
43 #main-menu li a {
44 display: block;
44 display: block;
45 color: #fff;
45 color: #fff;
46 text-decoration: none;
46 text-decoration: none;
47 font-weight: bold;
47 font-weight: bold;
48 margin: 0;
48 margin: 0;
49 padding: 4px 10px 4px 10px;
49 padding: 4px 10px 4px 10px;
50 }
50 }
51 #main-menu li a:hover {background:#759FCF; color:#fff;}
51 #main-menu li a:hover {background:#759FCF; color:#fff;}
52 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
52 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
53
53
54 #admin-menu ul {margin: 0; padding: 0;}
54 #admin-menu ul {margin: 0; padding: 0;}
55 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
55 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
56
56
57 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
57 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
58 #admin-menu a.projects { background-image: url(../images/projects.png); }
58 #admin-menu a.projects { background-image: url(../images/projects.png); }
59 #admin-menu a.users { background-image: url(../images/user.png); }
59 #admin-menu a.users { background-image: url(../images/user.png); }
60 #admin-menu a.groups { background-image: url(../images/group.png); }
60 #admin-menu a.groups { background-image: url(../images/group.png); }
61 #admin-menu a.roles { background-image: url(../images/database_key.png); }
61 #admin-menu a.roles { background-image: url(../images/database_key.png); }
62 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
62 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
63 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
63 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
64 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
64 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
65 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
65 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
66 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
66 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
67 #admin-menu a.settings { background-image: url(../images/changeset.png); }
67 #admin-menu a.settings { background-image: url(../images/changeset.png); }
68 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
68 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
69 #admin-menu a.info { background-image: url(../images/help.png); }
69 #admin-menu a.info { background-image: url(../images/help.png); }
70 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
70 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
71
71
72 #main {background-color:#EEEEEE;}
72 #main {background-color:#EEEEEE;}
73
73
74 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
74 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
75 * html #sidebar{ width: 22%; }
75 * html #sidebar{ width: 22%; }
76 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
76 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
77 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
77 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
78 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
78 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
79 #sidebar .contextual { margin-right: 1em; }
79 #sidebar .contextual { margin-right: 1em; }
80 #sidebar ul, ul.flat {margin: 0; padding: 0;}
80 #sidebar ul, ul.flat {margin: 0; padding: 0;}
81 #sidebar ul li, ul.flat li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
81 #sidebar ul li, ul.flat li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
82
82
83 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
83 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
84 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
84 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
85 html>body #content { min-height: 600px; }
85 html>body #content { min-height: 600px; }
86 * html body #content { height: 600px; } /* IE */
86 * html body #content { height: 600px; } /* IE */
87
87
88 #main.nosidebar #sidebar{ display: none; }
88 #main.nosidebar #sidebar{ display: none; }
89 #main.nosidebar #content{ width: auto; border-right: 0; }
89 #main.nosidebar #content{ width: auto; border-right: 0; }
90
90
91 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
91 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
92
92
93 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
93 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
94 #login-form table td {padding: 6px;}
94 #login-form table td {padding: 6px;}
95 #login-form label {font-weight: bold;}
95 #login-form label {font-weight: bold;}
96 #login-form input#username, #login-form input#password { width: 300px; }
96 #login-form input#username, #login-form input#password { width: 300px; }
97
97
98 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
98 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
99 div.modal h3.title {display:none;}
99 div.modal h3.title {display:none;}
100 div.modal p.buttons {text-align:right; margin-bottom:0;}
100 div.modal p.buttons {text-align:right; margin-bottom:0;}
101 div.modal .box p {margin: 0.3em 0;}
101 div.modal .box p {margin: 0.3em 0;}
102
102
103 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
103 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
104
104
105 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
105 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
106
106
107 /***** Links *****/
107 /***** Links *****/
108 a, a:link, a:visited{ color: #169; text-decoration: none; }
108 a, a:link, a:visited{ color: #169; text-decoration: none; }
109 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
109 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
110 a img{ border: 0; }
110 a img{ border: 0; }
111
111
112 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
112 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
113 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
113 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
114 a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;}
114 a.user.locked, a.user.locked:link, a.user.locked:visited {color: #999;}
115
115
116 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
116 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
117 #sidebar a.selected:hover {text-decoration:none;}
117 #sidebar a.selected:hover {text-decoration:none;}
118 #admin-menu a {line-height:1.7em;}
118 #admin-menu a {line-height:1.7em;}
119 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
119 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
120
120
121 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
121 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
122 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
122 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
123
123
124 a#toggle-completed-versions {color:#999;}
124 a#toggle-completed-versions {color:#999;}
125 /***** Tables *****/
125 /***** Tables *****/
126 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
126 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
127 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
127 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
128 table.list td {text-align:center; vertical-align:top; padding-right:10px;}
128 table.list td {text-align:center; vertical-align:top; padding-right:10px;}
129 table.list td.id { width: 2%; text-align: center;}
129 table.list td.id { width: 2%; text-align: center;}
130 table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles {text-align: left;}
130 table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles {text-align: left;}
131 table.list td.tick {width:15%}
131 table.list td.tick {width:15%}
132 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
132 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
133 table.list td.checkbox input {padding:0px;}
133 table.list td.checkbox input {padding:0px;}
134 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
134 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
135 table.list td.buttons a { padding-right: 0.6em; }
135 table.list td.buttons a { padding-right: 0.6em; }
136 table.list td.buttons img {vertical-align:middle;}
136 table.list td.buttons img {vertical-align:middle;}
137 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
137 table.list td.reorder {width:15%; white-space:nowrap; text-align:center; }
138 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
138 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
139
139
140 tr.project td.name a { white-space:nowrap; }
140 tr.project td.name a { white-space:nowrap; }
141 tr.project.closed, tr.project.archived { color: #aaa; }
141 tr.project.closed, tr.project.archived { color: #aaa; }
142 tr.project.closed a, tr.project.archived a { color: #aaa; }
142 tr.project.closed a, tr.project.archived a { color: #aaa; }
143
143
144 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
144 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
145 tr.project.idnt-1 td.name {padding-left: 0.5em;}
145 tr.project.idnt-1 td.name {padding-left: 0.5em;}
146 tr.project.idnt-2 td.name {padding-left: 2em;}
146 tr.project.idnt-2 td.name {padding-left: 2em;}
147 tr.project.idnt-3 td.name {padding-left: 3.5em;}
147 tr.project.idnt-3 td.name {padding-left: 3.5em;}
148 tr.project.idnt-4 td.name {padding-left: 5em;}
148 tr.project.idnt-4 td.name {padding-left: 5em;}
149 tr.project.idnt-5 td.name {padding-left: 6.5em;}
149 tr.project.idnt-5 td.name {padding-left: 6.5em;}
150 tr.project.idnt-6 td.name {padding-left: 8em;}
150 tr.project.idnt-6 td.name {padding-left: 8em;}
151 tr.project.idnt-7 td.name {padding-left: 9.5em;}
151 tr.project.idnt-7 td.name {padding-left: 9.5em;}
152 tr.project.idnt-8 td.name {padding-left: 11em;}
152 tr.project.idnt-8 td.name {padding-left: 11em;}
153 tr.project.idnt-9 td.name {padding-left: 12.5em;}
153 tr.project.idnt-9 td.name {padding-left: 12.5em;}
154
154
155 tr.issue { text-align: center; white-space: nowrap; }
155 tr.issue { text-align: center; white-space: nowrap; }
156 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations, tr.issue td.parent { white-space: normal; }
156 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text, tr.issue td.relations, tr.issue td.parent { white-space: normal; }
157 tr.issue td.relations { text-align: left; }
157 tr.issue td.relations { text-align: left; }
158 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
158 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
159 tr.issue td.relations span {white-space: nowrap;}
159 tr.issue td.relations span {white-space: nowrap;}
160 table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
160 table.issues td.description {color:#777; font-size:90%; padding:4px 4px 4px 24px; text-align:left; white-space:normal;}
161 table.issues td.description pre {white-space:normal;}
161 table.issues td.description pre {white-space:normal;}
162
162
163 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
163 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
164 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
164 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
165 tr.issue.idnt-2 td.subject {padding-left: 2em;}
165 tr.issue.idnt-2 td.subject {padding-left: 2em;}
166 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
166 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
167 tr.issue.idnt-4 td.subject {padding-left: 5em;}
167 tr.issue.idnt-4 td.subject {padding-left: 5em;}
168 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
168 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
169 tr.issue.idnt-6 td.subject {padding-left: 8em;}
169 tr.issue.idnt-6 td.subject {padding-left: 8em;}
170 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
170 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
171 tr.issue.idnt-8 td.subject {padding-left: 11em;}
171 tr.issue.idnt-8 td.subject {padding-left: 11em;}
172 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
172 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
173
173
174 table.issue-report {table-layout:fixed;}
174 table.issue-report {table-layout:fixed;}
175
175
176 tr.entry { border: 1px solid #f8f8f8; }
176 tr.entry { border: 1px solid #f8f8f8; }
177 tr.entry td { white-space: nowrap; }
177 tr.entry td { white-space: nowrap; }
178 tr.entry td.filename {width:30%; text-align:left;}
178 tr.entry td.filename {width:30%; text-align:left;}
179 tr.entry td.filename_no_report {width:70%; text-align:left;}
179 tr.entry td.filename_no_report {width:70%; text-align:left;}
180 tr.entry td.size { text-align: right; font-size: 90%; }
180 tr.entry td.size { text-align: right; font-size: 90%; }
181 tr.entry td.revision, tr.entry td.author { text-align: center; }
181 tr.entry td.revision, tr.entry td.author { text-align: center; }
182 tr.entry td.age { text-align: right; }
182 tr.entry td.age { text-align: right; }
183 tr.entry.file td.filename a { margin-left: 16px; }
183 tr.entry.file td.filename a { margin-left: 16px; }
184 tr.entry.file td.filename_no_report a { margin-left: 16px; }
184 tr.entry.file td.filename_no_report a { margin-left: 16px; }
185
185
186 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
186 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
187 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
187 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
188
188
189 tr.changeset { height: 20px }
189 tr.changeset { height: 20px }
190 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
190 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
191 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
191 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
192 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
192 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
193 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
193 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
194
194
195 table.files tbody th {text-align:left;}
195 table.files tbody th {text-align:left;}
196 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
196 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
197 table.files tr.file td.digest { font-size: 80%; }
197 table.files tr.file td.digest { font-size: 80%; }
198
198
199 table.members td.roles, table.memberships td.roles { width: 45%; }
199 table.members td.roles, table.memberships td.roles { width: 45%; }
200
200
201 tr.message { height: 2.6em; }
201 tr.message { height: 2.6em; }
202 tr.message td.subject { padding-left: 20px; }
202 tr.message td.subject { padding-left: 20px; }
203 tr.message td.created_on { white-space: nowrap; }
203 tr.message td.created_on { white-space: nowrap; }
204 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
204 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
205 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
205 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
206 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
206 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
207
207
208 tr.version.closed, tr.version.closed a { color: #999; }
208 tr.version.closed, tr.version.closed a { color: #999; }
209 tr.version td.name { padding-left: 20px; }
209 tr.version td.name { padding-left: 20px; }
210 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
210 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
211 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
211 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
212
212
213 tr.user td {width:13%;white-space: nowrap;}
213 tr.user td {width:13%;white-space: nowrap;}
214 td.username, td.firstname, td.lastname, td.email {text-align:left !important;}
214 td.username, td.firstname, td.lastname, td.email {text-align:left !important;}
215 tr.user td.email { width:18%; }
215 tr.user td.email { width:18%; }
216 tr.user.locked, tr.user.registered { color: #aaa; }
216 tr.user.locked, tr.user.registered { color: #aaa; }
217 tr.user.locked a, tr.user.registered a { color: #aaa; }
217 tr.user.locked a, tr.user.registered a { color: #aaa; }
218
218
219 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
219 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
220
220
221 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
221 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
222
222
223 tr.time-entry { text-align: center; white-space: nowrap; }
223 tr.time-entry { text-align: center; white-space: nowrap; }
224 tr.time-entry td.issue, tr.time-entry td.comments, tr.time-entry td.subject, tr.time-entry td.activity { text-align: left; white-space: normal; }
224 tr.time-entry td.issue, tr.time-entry td.comments, tr.time-entry td.subject, tr.time-entry td.activity { text-align: left; white-space: normal; }
225 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
225 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
226 td.hours .hours-dec { font-size: 0.9em; }
226 td.hours .hours-dec { font-size: 0.9em; }
227
227
228 table.plugins td { vertical-align: middle; }
228 table.plugins td { vertical-align: middle; }
229 table.plugins td.configure { text-align: right; padding-right: 1em; }
229 table.plugins td.configure { text-align: right; padding-right: 1em; }
230 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
230 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
231 table.plugins span.description { display: block; font-size: 0.9em; }
231 table.plugins span.description { display: block; font-size: 0.9em; }
232 table.plugins span.url { display: block; font-size: 0.9em; }
232 table.plugins span.url { display: block; font-size: 0.9em; }
233
233
234 tr.group td { padding: 0.8em 0 0.5em 0.3em; border-bottom: 1px solid #ccc; text-align:left; }
234 tr.group td { padding: 0.8em 0 0.5em 0.3em; border-bottom: 1px solid #ccc; text-align:left; }
235 tr.group span.name {font-weight:bold;}
235 tr.group span.name {font-weight:bold;}
236 tr.group span.count {font-weight:bold; position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;}
236 tr.group span.count {font-weight:bold; position:relative; top:-1px; color:#fff; font-size:10px; background:#9DB9D5; padding:0px 6px 1px 6px; border-radius:3px; margin-left:4px;}
237 tr.group span.totals {color: #aaa; font-size: 80%;}
237 tr.group span.totals {color: #aaa; font-size: 80%;}
238 tr.group span.totals .value {font-weight:bold; color:#777;}
238 tr.group span.totals .value {font-weight:bold; color:#777;}
239 tr.group a.toggle-all { color: #aaa; font-size: 80%; display:none; float:right; margin-right:4px;}
239 tr.group a.toggle-all { color: #aaa; font-size: 80%; display:none; float:right; margin-right:4px;}
240 tr.group:hover a.toggle-all { display:inline;}
240 tr.group:hover a.toggle-all { display:inline;}
241 a.toggle-all:hover {text-decoration:none;}
241 a.toggle-all:hover {text-decoration:none;}
242
242
243 table.list tbody tr:hover { background-color:#ffffdd; }
243 table.list tbody tr:hover { background-color:#ffffdd; }
244 table.list tbody tr.group:hover { background-color:inherit; }
244 table.list tbody tr.group:hover { background-color:inherit; }
245 table td {padding:2px;}
245 table td {padding:2px;}
246 table p {margin:0;}
246 table p {margin:0;}
247 .odd {background-color:#f6f7f8;}
247 .odd {background-color:#f6f7f8;}
248 .even {background-color: #fff;}
248 .even {background-color: #fff;}
249
249
250 tr.builtin td.name {font-style:italic;}
250 tr.builtin td.name {font-style:italic;}
251
251
252 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
252 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
253 a.sort.asc { background-image: url(../images/sort_asc.png); }
253 a.sort.asc { background-image: url(../images/sort_asc.png); }
254 a.sort.desc { background-image: url(../images/sort_desc.png); }
254 a.sort.desc { background-image: url(../images/sort_desc.png); }
255
255
256 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
256 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
257 table.boards td.last-message {text-align:left;font-size:80%;}
257 table.boards td.last-message {text-align:left;font-size:80%;}
258
258
259 table.messages td.last_message {text-align:left;}
259 table.messages td.last_message {text-align:left;}
260
260
261 #query_form_content {font-size:90%;}
261 #query_form_content {font-size:90%;}
262
262
263 .query_sort_criteria_count {
263 .query_sort_criteria_count {
264 display: inline-block;
264 display: inline-block;
265 min-width: 1em;
265 min-width: 1em;
266 }
266 }
267
267
268 table.query-columns {
268 table.query-columns {
269 border-collapse: collapse;
269 border-collapse: collapse;
270 border: 0;
270 border: 0;
271 }
271 }
272
272
273 table.query-columns td.buttons {
273 table.query-columns td.buttons {
274 vertical-align: middle;
274 vertical-align: middle;
275 text-align: center;
275 text-align: center;
276 }
276 }
277 table.query-columns td.buttons input[type=button] {width:35px;}
277 table.query-columns td.buttons input[type=button] {width:35px;}
278 .query-totals {text-align:right; margin-top:-2.3em;}
278 .query-totals {text-align:right; margin-top:-2.3em;}
279 .query-totals>span {margin-left:0.6em;}
279 .query-totals>span {margin-left:0.6em;}
280 .query-totals .value {font-weight:bold;}
280 .query-totals .value {font-weight:bold;}
281
281
282 td.center {text-align:center;}
282 td.center {text-align:center;}
283
283
284 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
284 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
285
285
286 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
286 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
287 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
287 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
288 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
288 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
289 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
289 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
290
290
291 #watchers select {width: 95%; display: block;}
291 #watchers select {width: 95%; display: block;}
292 #watchers a.delete {opacity: 0.4; vertical-align: middle;}
292 #watchers a.delete {opacity: 0.4; vertical-align: middle;}
293 #watchers a.delete:hover {opacity: 1;}
293 #watchers a.delete:hover {opacity: 1;}
294 #watchers img.gravatar {margin: 0 4px 2px 0;}
294 #watchers img.gravatar {margin: 0 4px 2px 0;}
295
295
296 span#watchers_inputs {overflow:auto; display:block;}
296 span#watchers_inputs {overflow:auto; display:block;}
297 span.search_for_watchers {display:block;}
297 span.search_for_watchers {display:block;}
298 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
298 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
299 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
299 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
300
300
301
301
302 .highlight { background-color: #FCFD8D;}
302 .highlight { background-color: #FCFD8D;}
303 .highlight.token-1 { background-color: #faa;}
303 .highlight.token-1 { background-color: #faa;}
304 .highlight.token-2 { background-color: #afa;}
304 .highlight.token-2 { background-color: #afa;}
305 .highlight.token-3 { background-color: #aaf;}
305 .highlight.token-3 { background-color: #aaf;}
306
306
307 .box{
307 .box{
308 padding:6px;
308 padding:6px;
309 margin-bottom: 10px;
309 margin-bottom: 10px;
310 background-color:#f6f6f6;
310 background-color:#f6f6f6;
311 color:#505050;
311 color:#505050;
312 line-height:1.5em;
312 line-height:1.5em;
313 border: 1px solid #e4e4e4;
313 border: 1px solid #e4e4e4;
314 word-wrap: break-word;
314 word-wrap: break-word;
315 border-radius: 3px;
315 border-radius: 3px;
316 }
316 }
317
317
318 div.square {
318 div.square {
319 border: 1px solid #999;
319 border: 1px solid #999;
320 float: left;
320 float: left;
321 margin: .3em .4em 0 .4em;
321 margin: .3em .4em 0 .4em;
322 overflow: hidden;
322 overflow: hidden;
323 width: .6em; height: .6em;
323 width: .6em; height: .6em;
324 }
324 }
325 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
325 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
326 .contextual input, .contextual select {font-size:0.9em;}
326 .contextual input, .contextual select {font-size:0.9em;}
327 .message .contextual { margin-top: 0; }
327 .message .contextual { margin-top: 0; }
328
328
329 .splitcontent {overflow:auto;}
329 .splitcontent {overflow:auto;}
330 .splitcontentleft{float:left; width:49%;}
330 .splitcontentleft{float:left; width:49%;}
331 .splitcontentright{float:right; width:49%;}
331 .splitcontentright{float:right; width:49%;}
332 form {display: inline;}
332 form {display: inline;}
333 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
333 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
334 fieldset {border: 1px solid #e4e4e4; margin:0;}
334 fieldset {border: 1px solid #e4e4e4; margin:0;}
335 legend {color: #333;}
335 legend {color: #333;}
336 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
336 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
337 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
337 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
338 blockquote blockquote { margin-left: 0;}
338 blockquote blockquote { margin-left: 0;}
339 abbr, span.field-description[title] { border-bottom: 1px dotted #aaa; cursor: help; }
339 abbr, span.field-description[title] { border-bottom: 1px dotted #aaa; cursor: help; }
340 textarea.wiki-edit {width:99%; resize:vertical;}
340 textarea.wiki-edit {width:99%; resize:vertical;}
341 li p {margin-top: 0;}
341 li p {margin-top: 0;}
342 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px; border: 1px solid #d7d7d7; border-radius:3px;}
342 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px; border: 1px solid #d7d7d7; border-radius:3px;}
343 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
343 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
344 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
344 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
345 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
345 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
346 .ltr {direction:ltr !important; unicode-bidi:bidi-override;}
346 .ltr {direction:ltr !important; unicode-bidi:bidi-override;}
347 .rtl {direction:rtl !important; unicode-bidi:bidi-override;}
347 .rtl {direction:rtl !important; unicode-bidi:bidi-override;}
348
348
349 div.issue div.subject div div { padding-left: 16px; }
349 div.issue div.subject div div { padding-left: 16px; }
350 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
350 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
351 div.issue div.subject>div>p { margin-top: 0.5em; }
351 div.issue div.subject>div>p { margin-top: 0.5em; }
352 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
352 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
353 div.issue span.private, div.journal span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
353 div.issue span.private, div.journal span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px;}
354 div.issue .next-prev-links {color:#999;}
354 div.issue .next-prev-links {color:#999;}
355 div.issue .attributes {margin-top: 2em;}
355 div.issue .attributes {margin-top: 2em;}
356 div.issue .attribute {padding-left:180px; clear:left; min-height: 1.8em;}
356 div.issue .attribute {padding-left:180px; clear:left; min-height: 1.8em;}
357 div.issue .attribute .label {width: 170px; margin-left:-180px; font-weight:bold; float:left;}
357 div.issue .attribute .label {width: 170px; margin-left:-180px; font-weight:bold; float:left;}
358 div.issue.overdue .due-date .value { color: #c22; }
358 div.issue.overdue .due-date .value { color: #c22; }
359
359
360 #issue_tree table.issues, #relations table.issues { border: 0; }
360 #issue_tree table.issues, #relations table.issues { border: 0; }
361 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
361 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
362 #relations td.buttons {padding:0;}
362 #relations td.buttons {padding:0;}
363
363
364 fieldset.collapsible {border-width: 1px 0 0 0;}
364 fieldset.collapsible {border-width: 1px 0 0 0;}
365 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
365 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
366 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
366 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
367
367
368 fieldset#date-range p { margin: 2px 0 2px 0; }
368 fieldset#date-range p { margin: 2px 0 2px 0; }
369 fieldset#filters table { border-collapse: collapse; }
369 fieldset#filters table { border-collapse: collapse; }
370 fieldset#filters table td { padding: 0; vertical-align: middle; }
370 fieldset#filters table td { padding: 0; vertical-align: middle; }
371 fieldset#filters tr.filter { height: 2.1em; }
371 fieldset#filters tr.filter { height: 2.1em; }
372 fieldset#filters td.field { width:230px; }
372 fieldset#filters td.field { width:230px; }
373 fieldset#filters td.operator { width:180px; }
373 fieldset#filters td.operator { width:180px; }
374 fieldset#filters td.operator select {max-width:170px;}
374 fieldset#filters td.operator select {max-width:170px;}
375 fieldset#filters td.values { white-space:nowrap; }
375 fieldset#filters td.values { white-space:nowrap; }
376 fieldset#filters td.values select {min-width:130px;}
376 fieldset#filters td.values select {min-width:130px;}
377 fieldset#filters td.values input {height:1em;}
377 fieldset#filters td.values input {height:1em;}
378
378
379 #filters-table {width:60%; float:left;}
379 #filters-table {width:60%; float:left;}
380 .add-filter {width:35%; float:right; text-align: right; vertical-align: top;}
380 .add-filter {width:35%; float:right; text-align: right; vertical-align: top;}
381
381
382 #issue_is_private_wrap {float:right; margin-right:1em;}
382 #issue_is_private_wrap {float:right; margin-right:1em;}
383 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
383 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
384 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
384 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
385
385
386 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
386 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
387 div#issue-changesets div.changeset { padding: 4px;}
387 div#issue-changesets div.changeset { padding: 4px;}
388 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
388 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
389 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
389 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
390
390
391 .journal ul.details img {margin:0 0 -3px 4px;}
391 .journal ul.details img {margin:0 0 -3px 4px;}
392 div.journal {overflow:auto;}
392 div.journal {overflow:auto;}
393 div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
393 div.journal.private-notes {border-left:2px solid #d22; padding-left:4px; margin-left:-6px;}
394 div.journal ul.details {color:#959595; margin-bottom: 1.5em;}
394 div.journal ul.details {color:#959595; margin-bottom: 1.5em;}
395 div.journal ul.details a {color:#70A7CD;}
395 div.journal ul.details a {color:#70A7CD;}
396 div.journal ul.details a:hover {color:#D14848;}
396 div.journal ul.details a:hover {color:#D14848;}
397
397
398 div#activity dl, #search-results { margin-left: 2em; }
398 div#activity dl, #search-results { margin-left: 2em; }
399 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
399 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
400 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
400 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
401 div#activity dt.me .time { border-bottom: 1px solid #999; }
401 div#activity dt.me .time { border-bottom: 1px solid #999; }
402 div#activity dt .time { color: #777; font-size: 80%; }
402 div#activity dt .time { color: #777; font-size: 80%; }
403 div#activity dd .description, #search-results dd .description { font-style: italic; }
403 div#activity dd .description, #search-results dd .description { font-style: italic; }
404 div#activity span.project:after, #search-results span.project:after { content: " -"; }
404 div#activity span.project:after, #search-results span.project:after { content: " -"; }
405 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
405 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
406 div#activity dt.grouped {margin-left:5em;}
406 div#activity dt.grouped {margin-left:5em;}
407 div#activity dd.grouped {margin-left:9em;}
407 div#activity dd.grouped {margin-left:9em;}
408
408
409 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
409 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
410
410
411 div#search-results-counts {float:right;}
411 div#search-results-counts {float:right;}
412 div#search-results-counts ul { margin-top: 0.5em; }
412 div#search-results-counts ul { margin-top: 0.5em; }
413 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
413 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
414
414
415 dt.issue { background-image: url(../images/ticket.png); }
415 dt.issue { background-image: url(../images/ticket.png); }
416 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
416 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
417 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
417 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
418 dt.issue-note { background-image: url(../images/ticket_note.png); }
418 dt.issue-note { background-image: url(../images/ticket_note.png); }
419 dt.changeset { background-image: url(../images/changeset.png); }
419 dt.changeset { background-image: url(../images/changeset.png); }
420 dt.news { background-image: url(../images/news.png); }
420 dt.news { background-image: url(../images/news.png); }
421 dt.message { background-image: url(../images/message.png); }
421 dt.message { background-image: url(../images/message.png); }
422 dt.reply { background-image: url(../images/comments.png); }
422 dt.reply { background-image: url(../images/comments.png); }
423 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
423 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
424 dt.attachment { background-image: url(../images/attachment.png); }
424 dt.attachment { background-image: url(../images/attachment.png); }
425 dt.document { background-image: url(../images/document.png); }
425 dt.document { background-image: url(../images/document.png); }
426 dt.project { background-image: url(../images/projects.png); }
426 dt.project { background-image: url(../images/projects.png); }
427 dt.time-entry { background-image: url(../images/time.png); }
427 dt.time-entry { background-image: url(../images/time.png); }
428
428
429 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
429 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
430
430
431 div#roadmap .related-issues { margin-bottom: 1em; }
431 div#roadmap .related-issues { margin-bottom: 1em; }
432 div#roadmap .related-issues td.checkbox { display: none; }
432 div#roadmap .related-issues td.checkbox { display: none; }
433 div#roadmap .wiki h1:first-child { display: none; }
433 div#roadmap .wiki h1:first-child { display: none; }
434 div#roadmap .wiki h1 { font-size: 120%; }
434 div#roadmap .wiki h1 { font-size: 120%; }
435 div#roadmap .wiki h2 { font-size: 110%; }
435 div#roadmap .wiki h2 { font-size: 110%; }
436 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
436 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
437
437
438 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
438 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
439 div#version-summary fieldset { margin-bottom: 1em; }
439 div#version-summary fieldset { margin-bottom: 1em; }
440 div#version-summary fieldset.time-tracking table { width:100%; }
440 div#version-summary fieldset.time-tracking table { width:100%; }
441 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
441 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
442
442
443 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
443 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
444 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
444 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
445 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
445 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
446 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
446 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
447 table#time-report .hours-dec { font-size: 0.9em; }
447 table#time-report .hours-dec { font-size: 0.9em; }
448
448
449 div.wiki-page .contextual a {opacity: 0.4}
449 div.wiki-page .contextual a {opacity: 0.4}
450 div.wiki-page .contextual a:hover {opacity: 1}
450 div.wiki-page .contextual a:hover {opacity: 1}
451
451
452 form .attributes select { width: 60%; }
452 form .attributes select { width: 60%; }
453 input#issue_subject, input#document_title { width: 99%; }
453 input#issue_subject, input#document_title { width: 99%; }
454 select#issue_done_ratio { width: 95px; }
454 select#issue_done_ratio { width: 95px; }
455
455
456 ul.projects {margin:0; padding-left:1em;}
456 ul.projects {margin:0; padding-left:1em;}
457 ul.projects ul {padding-left:1.6em;}
457 ul.projects ul {padding-left:1.6em;}
458 ul.projects.root {margin:0; padding:0;}
458 ul.projects.root {margin:0; padding:0;}
459 ul.projects li {list-style-type:none;}
459 ul.projects li {list-style-type:none;}
460
460
461 #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;}
461 #projects-index ul.projects ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;}
462 #projects-index ul.projects li.root {margin-bottom: 1em;}
462 #projects-index ul.projects li.root {margin-bottom: 1em;}
463 #projects-index ul.projects li.child {margin-top: 1em;}
463 #projects-index ul.projects li.child {margin-top: 1em;}
464 #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
464 #projects-index ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
465 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
465 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
466
466
467 #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;}
467 #notified-projects>ul, #tracker_project_ids>ul, #custom_field_project_ids>ul {max-height:250px; overflow-y:auto;}
468
468
469 #related-issues li img {vertical-align:middle;}
469 #related-issues li img {vertical-align:middle;}
470
470
471 ul.properties {padding:0; font-size: 0.9em; color: #777;}
471 ul.properties {padding:0; font-size: 0.9em; color: #777;}
472 ul.properties li {list-style-type:none;}
472 ul.properties li {list-style-type:none;}
473 ul.properties li span {font-style:italic;}
473 ul.properties li span {font-style:italic;}
474
474
475 .total-hours { font-size: 110%; font-weight: bold; }
475 .total-hours { font-size: 110%; font-weight: bold; }
476 .total-hours span.hours-int { font-size: 120%; }
476 .total-hours span.hours-int { font-size: 120%; }
477
477
478 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
478 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
479 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
479 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
480
480
481 #workflow_copy_form select { width: 200px; }
481 #workflow_copy_form select { width: 200px; }
482 table.transitions td.enabled {background: #bfb;}
482 table.transitions td.enabled {background: #bfb;}
483 #workflow_form table select {font-size:90%; max-width:100px;}
483 #workflow_form table select {font-size:90%; max-width:100px;}
484 table.fields_permissions td.readonly {background:#ddd;}
484 table.fields_permissions td.readonly {background:#ddd;}
485 table.fields_permissions td.required {background:#d88;}
485 table.fields_permissions td.required {background:#d88;}
486
486
487 select.expandable {vertical-align:top;}
487 select.expandable {vertical-align:top;}
488
488
489 textarea#custom_field_possible_values {width: 95%; resize:vertical}
489 textarea#custom_field_possible_values {width: 95%; resize:vertical}
490 textarea#custom_field_default_value {width: 95%; resize:vertical}
490 textarea#custom_field_default_value {width: 95%; resize:vertical}
491 .sort-handle {display:inline-block; vertical-align:middle;}
491 .sort-handle {display:inline-block; vertical-align:middle;}
492
492
493 input#content_comments {width: 99%}
493 input#content_comments {width: 99%}
494
494
495 p.pagination {margin-top:8px; font-size: 90%}
495 p.pagination {margin-top:8px; font-size: 90%}
496
496
497 #search-form fieldset p {margin:0.2em 0;}
497 #search-form fieldset p {margin:0.2em 0;}
498
498
499 /***** Tabular forms ******/
499 /***** Tabular forms ******/
500 .tabular p{
500 .tabular p{
501 margin: 0;
501 margin: 0;
502 padding: 3px 0 3px 0;
502 padding: 3px 0 3px 0;
503 padding-left: 180px; /* width of left column containing the label elements */
503 padding-left: 180px; /* width of left column containing the label elements */
504 min-height: 1.8em;
504 min-height: 1.8em;
505 clear:left;
505 clear:left;
506 }
506 }
507
507
508 html>body .tabular p {overflow:hidden;}
508 html>body .tabular p {overflow:hidden;}
509
509
510 .tabular input, .tabular select {max-width:95%}
510 .tabular input, .tabular select {max-width:95%}
511 .tabular textarea {width:95%; resize:vertical;}
511 .tabular textarea {width:95%; resize:vertical;}
512
512
513 .tabular label{
513 .tabular label{
514 font-weight: bold;
514 font-weight: bold;
515 float: left;
515 float: left;
516 text-align: right;
516 text-align: right;
517 /* width of left column */
517 /* width of left column */
518 margin-left: -180px;
518 margin-left: -180px;
519 /* width of labels. Should be smaller than left column to create some right margin */
519 /* width of labels. Should be smaller than left column to create some right margin */
520 width: 175px;
520 width: 175px;
521 }
521 }
522
522
523 .tabular label.floating{
523 .tabular label.floating{
524 font-weight: normal;
524 font-weight: normal;
525 margin-left: 0px;
525 margin-left: 0px;
526 text-align: left;
526 text-align: left;
527 width: 270px;
527 width: 270px;
528 }
528 }
529
529
530 .tabular label.block{
530 .tabular label.block{
531 font-weight: normal;
531 font-weight: normal;
532 margin-left: 0px !important;
532 margin-left: 0px !important;
533 text-align: left;
533 text-align: left;
534 float: none;
534 float: none;
535 display: block;
535 display: block;
536 width: auto !important;
536 width: auto !important;
537 }
537 }
538
538
539 .tabular label.inline{
539 .tabular label.inline{
540 font-weight: normal;
540 font-weight: normal;
541 float:none;
541 float:none;
542 margin-left: 5px !important;
542 margin-left: 5px !important;
543 width: auto;
543 width: auto;
544 }
544 }
545
545
546 label.no-css {
546 label.no-css {
547 font-weight: inherit;
547 font-weight: inherit;
548 float:none;
548 float:none;
549 text-align:left;
549 text-align:left;
550 margin-left:0px;
550 margin-left:0px;
551 width:auto;
551 width:auto;
552 }
552 }
553 input#time_entry_comments { width: 90%;}
553 input#time_entry_comments { width: 90%;}
554
554
555 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
555 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
556
556
557 .tabular.settings p{ padding-left: 300px; }
557 .tabular.settings p{ padding-left: 300px; }
558 .tabular.settings label{ margin-left: -300px; width: 295px; }
558 .tabular.settings label{ margin-left: -300px; width: 295px; }
559 .tabular.settings textarea { width: 99%; }
559 .tabular.settings textarea { width: 99%; }
560
560
561 .settings.enabled_scm table {width:100%}
561 .settings.enabled_scm table {width:100%}
562 .settings.enabled_scm td.scm_name{ font-weight: bold; }
562 .settings.enabled_scm td.scm_name{ font-weight: bold; }
563
563
564 fieldset.settings label { display: block; }
564 fieldset.settings label { display: block; }
565 fieldset#notified_events .parent { padding-left: 20px; }
565 fieldset#notified_events .parent { padding-left: 20px; }
566
566
567 span.required {color: #bb0000;}
567 span.required {color: #bb0000;}
568 .summary {font-style: italic;}
568 .summary {font-style: italic;}
569
569
570 .check_box_group {
570 .check_box_group {
571 display:block;
571 display:block;
572 width:95%;
572 width:95%;
573 max-height:300px;
573 max-height:300px;
574 overflow-y:auto;
574 overflow-y:auto;
575 padding:2px 4px 4px 2px;
575 padding:2px 4px 4px 2px;
576 background:#fff;
576 background:#fff;
577 border:1px solid #9EB1C2;
577 border:1px solid #9EB1C2;
578 border-radius:2px
578 border-radius:2px
579 }
579 }
580 .check_box_group label {
580 .check_box_group label {
581 font-weight: normal;
581 font-weight: normal;
582 margin-left: 0px !important;
582 margin-left: 0px !important;
583 text-align: left;
583 text-align: left;
584 float: none;
584 float: none;
585 display: block;
585 display: block;
586 width: auto;
586 width: auto;
587 }
587 }
588 .check_box_group.bool_cf {border:0; background:inherit;}
588 .check_box_group.bool_cf {border:0; background:inherit;}
589 .check_box_group.bool_cf label {display: inline;}
589 .check_box_group.bool_cf label {display: inline;}
590
590
591 #attachments_fields input.description {margin-left:4px; width:340px;}
591 #attachments_fields input.description {margin-left:4px; width:340px;}
592 #attachments_fields span {display:block; white-space:nowrap;}
592 #attachments_fields span {display:block; white-space:nowrap;}
593 #attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
593 #attachments_fields input.filename {border:0; height:1.8em; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;}
594 #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
594 #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
595 #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
595 #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
596 #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
596 #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
597 a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
597 a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
598 a.remove-upload:hover {text-decoration:none !important;}
598 a.remove-upload:hover {text-decoration:none !important;}
599
599
600 div.fileover { background-color: lavender; }
600 div.fileover { background-color: lavender; }
601
601
602 div.attachments { margin-top: 12px; }
602 div.attachments { margin-top: 12px; }
603 div.attachments p { margin:4px 0 2px 0; }
603 div.attachments p { margin:4px 0 2px 0; }
604 div.attachments img { vertical-align: middle; }
604 div.attachments img { vertical-align: middle; }
605 div.attachments span.author { font-size: 0.9em; color: #888; }
605 div.attachments span.author { font-size: 0.9em; color: #888; }
606
606
607 div.thumbnails {margin-top:0.6em;}
607 div.thumbnails {margin-top:0.6em;}
608 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
608 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
609 div.thumbnails img {margin: 3px; vertical-align: middle;}
609 div.thumbnails img {margin: 3px; vertical-align: middle;}
610 #history div.thumbnails {margin-left: 2em;}
610 #history div.thumbnails {margin-left: 2em;}
611
611
612 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
612 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
613 .other-formats span + span:before { content: "| "; }
613 .other-formats span + span:before { content: "| "; }
614
614
615 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
615 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
616
616
617 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
617 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
618 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
618 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
619
619
620 textarea.text_cf {width:95%; resize:vertical;}
620 textarea.text_cf {width:95%; resize:vertical;}
621 input.string_cf, input.link_cf {width:95%;}
621 input.string_cf, input.link_cf {width:95%;}
622 select.bool_cf {width:auto !important;}
622 select.bool_cf {width:auto !important;}
623
623
624 #tab-content-modules fieldset p {margin:3px 0 4px 0;}
624 #tab-content-modules fieldset p {margin:3px 0 4px 0;}
625
625
626 #tab-content-users .splitcontentleft {width: 64%;}
626 #tab-content-users .splitcontentleft {width: 64%;}
627 #tab-content-users .splitcontentright {width: 34%;}
627 #tab-content-users .splitcontentright {width: 34%;}
628 #tab-content-users fieldset {padding:1em; margin-bottom: 1em;}
628 #tab-content-users fieldset {padding:1em; margin-bottom: 1em;}
629 #tab-content-users fieldset legend {font-weight: bold;}
629 #tab-content-users fieldset legend {font-weight: bold;}
630 #tab-content-users fieldset label {display: block;}
630 #tab-content-users fieldset label {display: block;}
631 #tab-content-users #principals {max-height: 400px; overflow: auto;}
631 #tab-content-users #principals {max-height: 400px; overflow: auto;}
632
632
633 #users_for_watcher {height: 200px; overflow:auto;}
633 #users_for_watcher {height: 200px; overflow:auto;}
634 #users_for_watcher label {display: block;}
634 #users_for_watcher label {display: block;}
635
635
636 table.members td.name {padding-left: 20px;}
636 table.members td.name {padding-left: 20px;}
637 table.members td.group, table.members td.groupnonmember, table.members td.groupanonymous {background: url(../images/group.png) no-repeat 0% 1px;}
637 table.members td.group, table.members td.groupnonmember, table.members td.groupanonymous {background: url(../images/group.png) no-repeat 0% 1px;}
638
638
639 input#principal_search, input#user_search {width:90%}
639 input#principal_search, input#user_search {width:90%}
640 .roles-selection label {display:inline-block; width:210px;}
640 .roles-selection label {display:inline-block; width:210px;}
641
641
642 input.autocomplete {
642 input.autocomplete {
643 background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
643 background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
644 border:1px solid #9EB1C2; border-radius:2px; height:1.5em;
644 border:1px solid #9EB1C2; border-radius:2px; height:1.5em;
645 }
645 }
646 input.autocomplete.ajax-loading {
646 input.autocomplete.ajax-loading {
647 background-image: url(../images/loading.gif);
647 background-image: url(../images/loading.gif);
648 }
648 }
649
649
650 .role-visibility {padding-left:2em;}
650 .role-visibility {padding-left:2em;}
651
651
652 .objects-selection {
652 .objects-selection {
653 height: 300px;
653 height: 300px;
654 overflow: auto;
654 overflow: auto;
655 }
655 }
656
656
657 .objects-selection label {
657 .objects-selection label {
658 display: block;
658 display: block;
659 }
659 }
660
660
661 .objects-selection>div {
661 .objects-selection>div {
662 column-count: auto;
662 column-count: auto;
663 column-width: 200px;
663 column-width: 200px;
664 -webkit-column-count: auto;
664 -webkit-column-count: auto;
665 -webkit-column-width: 200px;
665 -webkit-column-width: 200px;
666 -webkit-column-gap : 0.5rem;
666 -webkit-column-gap : 0.5rem;
667 -webkit-column-rule: 1px solid #ccc;
667 -webkit-column-rule: 1px solid #ccc;
668 -moz-column-count: auto;
668 -moz-column-count: auto;
669 -moz-column-width: 200px;
669 -moz-column-width: 200px;
670 -moz-column-gap : 0.5rem;
670 -moz-column-gap : 0.5rem;
671 -moz-column-rule: 1px solid #ccc;
671 -moz-column-rule: 1px solid #ccc;
672 }
672 }
673
673
674 /***** Flash & error messages ****/
674 /***** Flash & error messages ****/
675 #errorExplanation, div.flash, .nodata, .warning, .conflict {
675 #errorExplanation, div.flash, .nodata, .warning, .conflict {
676 padding: 4px 4px 4px 30px;
676 padding: 4px 4px 4px 30px;
677 margin-bottom: 12px;
677 margin-bottom: 12px;
678 font-size: 1.1em;
678 font-size: 1.1em;
679 border: 2px solid;
679 border: 2px solid;
680 border-radius: 3px;
680 border-radius: 3px;
681 }
681 }
682
682
683 div.flash {margin-top: 8px;}
683 div.flash {margin-top: 8px;}
684
684
685 div.flash.error, #errorExplanation {
685 div.flash.error, #errorExplanation {
686 background: url(../images/exclamation.png) 8px 50% no-repeat;
686 background: url(../images/exclamation.png) 8px 50% no-repeat;
687 background-color: #ffe3e3;
687 background-color: #ffe3e3;
688 border-color: #dd0000;
688 border-color: #dd0000;
689 color: #880000;
689 color: #880000;
690 }
690 }
691
691
692 div.flash.notice {
692 div.flash.notice {
693 background: url(../images/true.png) 8px 5px no-repeat;
693 background: url(../images/true.png) 8px 5px no-repeat;
694 background-color: #dfffdf;
694 background-color: #dfffdf;
695 border-color: #9fcf9f;
695 border-color: #9fcf9f;
696 color: #005f00;
696 color: #005f00;
697 }
697 }
698
698
699 div.flash.warning, .conflict {
699 div.flash.warning, .conflict {
700 background: url(../images/warning.png) 8px 5px no-repeat;
700 background: url(../images/warning.png) 8px 5px no-repeat;
701 background-color: #FFEBC1;
701 background-color: #FFEBC1;
702 border-color: #FDBF3B;
702 border-color: #FDBF3B;
703 color: #A6750C;
703 color: #A6750C;
704 text-align: left;
704 text-align: left;
705 }
705 }
706
706
707 .nodata, .warning {
707 .nodata, .warning {
708 text-align: center;
708 text-align: center;
709 background-color: #FFEBC1;
709 background-color: #FFEBC1;
710 border-color: #FDBF3B;
710 border-color: #FDBF3B;
711 color: #A6750C;
711 color: #A6750C;
712 }
712 }
713
713
714 #errorExplanation ul { font-size: 0.9em;}
714 #errorExplanation ul { font-size: 0.9em;}
715 #errorExplanation h2, #errorExplanation p { display: none; }
715 #errorExplanation h2, #errorExplanation p { display: none; }
716
716
717 .conflict-details {font-size:80%;}
717 .conflict-details {font-size:80%;}
718
718
719 /***** Ajax indicator ******/
719 /***** Ajax indicator ******/
720 #ajax-indicator {
720 #ajax-indicator {
721 position: absolute; /* fixed not supported by IE */
721 position: absolute; /* fixed not supported by IE */
722 background-color:#eee;
722 background-color:#eee;
723 border: 1px solid #bbb;
723 border: 1px solid #bbb;
724 top:35%;
724 top:35%;
725 left:40%;
725 left:40%;
726 width:20%;
726 width:20%;
727 font-weight:bold;
727 font-weight:bold;
728 text-align:center;
728 text-align:center;
729 padding:0.6em;
729 padding:0.6em;
730 z-index:100;
730 z-index:100;
731 opacity: 0.5;
731 opacity: 0.5;
732 }
732 }
733
733
734 html>body #ajax-indicator { position: fixed; }
734 html>body #ajax-indicator { position: fixed; }
735
735
736 #ajax-indicator span {
736 #ajax-indicator span {
737 background-position: 0% 40%;
737 background-position: 0% 40%;
738 background-repeat: no-repeat;
738 background-repeat: no-repeat;
739 background-image: url(../images/loading.gif);
739 background-image: url(../images/loading.gif);
740 padding-left: 26px;
740 padding-left: 26px;
741 vertical-align: bottom;
741 vertical-align: bottom;
742 }
742 }
743
743
744 /***** Calendar *****/
744 /***** Calendar *****/
745 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
745 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
746 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
746 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
747 table.cal thead th.week-number {width: auto;}
747 table.cal thead th.week-number {width: auto;}
748 table.cal tbody tr {height: 100px;}
748 table.cal tbody tr {height: 100px;}
749 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
749 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
750 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
750 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
751 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
751 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
752 table.cal td.odd p.day-num {color: #bbb;}
752 table.cal td.odd p.day-num {color: #bbb;}
753 table.cal td.today {background:#ffffdd;}
753 table.cal td.today {background:#ffffdd;}
754 table.cal td.today p.day-num {font-weight: bold;}
754 table.cal td.today p.day-num {font-weight: bold;}
755 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
755 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
756 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
756 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
757 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
757 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
758 p.cal.legend span {display:block;}
758 p.cal.legend span {display:block;}
759
759
760 /***** Tooltips ******/
760 /***** Tooltips ******/
761 .tooltip{position:relative;z-index:24;}
761 .tooltip{position:relative;z-index:24;}
762 .tooltip:hover{z-index:25;color:#000;}
762 .tooltip:hover{z-index:25;color:#000;}
763 .tooltip span.tip{display: none; text-align:left;}
763 .tooltip span.tip{display: none; text-align:left;}
764
764
765 div.tooltip:hover span.tip{
765 div.tooltip:hover span.tip{
766 display:block;
766 display:block;
767 position:absolute;
767 position:absolute;
768 top:12px; left:24px; width:270px;
768 top:12px; left:24px; width:270px;
769 border:1px solid #555;
769 border:1px solid #555;
770 background-color:#fff;
770 background-color:#fff;
771 padding: 4px;
771 padding: 4px;
772 font-size: 0.8em;
772 font-size: 0.8em;
773 color:#505050;
773 color:#505050;
774 }
774 }
775
775
776 img.ui-datepicker-trigger {
776 img.ui-datepicker-trigger {
777 cursor: pointer;
777 cursor: pointer;
778 vertical-align: middle;
778 vertical-align: middle;
779 margin-left: 4px;
779 margin-left: 4px;
780 }
780 }
781
781
782 /***** Progress bar *****/
782 /***** Progress bar *****/
783 table.progress {
783 table.progress {
784 border-collapse: collapse;
784 border-collapse: collapse;
785 border-spacing: 0pt;
785 border-spacing: 0pt;
786 empty-cells: show;
786 empty-cells: show;
787 text-align: center;
787 text-align: center;
788 float:left;
788 float:left;
789 margin: 1px 6px 1px 0px;
789 margin: 1px 6px 1px 0px;
790 }
790 }
791
791
792 table.progress {width:80px;}
792 table.progress td { height: 1em; }
793 table.progress td { height: 1em; }
793 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
794 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
794 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
795 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
795 table.progress td.todo { background: #eee none repeat scroll 0%; }
796 table.progress td.todo { background: #eee none repeat scroll 0%; }
796 p.percent {font-size: 80%; margin:0;}
797 p.percent {font-size: 80%; margin:0;}
797 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
798 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
798
799
799 #roadmap table.progress td { height: 1.2em; }
800 .version-overview table.progress {width:40em;}
801 .version-overview table.progress td { height: 1.2em; }
802
800 /***** Tabs *****/
803 /***** Tabs *****/
801 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
804 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
802 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
805 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
803 #content .tabs ul li {
806 #content .tabs ul li {
804 float:left;
807 float:left;
805 list-style-type:none;
808 list-style-type:none;
806 white-space:nowrap;
809 white-space:nowrap;
807 margin-right:4px;
810 margin-right:4px;
808 background:#fff;
811 background:#fff;
809 position:relative;
812 position:relative;
810 margin-bottom:-1px;
813 margin-bottom:-1px;
811 }
814 }
812 #content .tabs ul li a{
815 #content .tabs ul li a{
813 display:block;
816 display:block;
814 font-size: 0.9em;
817 font-size: 0.9em;
815 text-decoration:none;
818 text-decoration:none;
816 line-height:1.3em;
819 line-height:1.3em;
817 padding:4px 6px 4px 6px;
820 padding:4px 6px 4px 6px;
818 border: 1px solid #ccc;
821 border: 1px solid #ccc;
819 border-bottom: 1px solid #bbbbbb;
822 border-bottom: 1px solid #bbbbbb;
820 background-color: #f6f6f6;
823 background-color: #f6f6f6;
821 color:#999;
824 color:#999;
822 font-weight:bold;
825 font-weight:bold;
823 border-top-left-radius:3px;
826 border-top-left-radius:3px;
824 border-top-right-radius:3px;
827 border-top-right-radius:3px;
825 }
828 }
826
829
827 #content .tabs ul li a:hover {
830 #content .tabs ul li a:hover {
828 background-color: #ffffdd;
831 background-color: #ffffdd;
829 text-decoration:none;
832 text-decoration:none;
830 }
833 }
831
834
832 #content .tabs ul li a.selected {
835 #content .tabs ul li a.selected {
833 background-color: #fff;
836 background-color: #fff;
834 border: 1px solid #bbbbbb;
837 border: 1px solid #bbbbbb;
835 border-bottom: 1px solid #fff;
838 border-bottom: 1px solid #fff;
836 color:#444;
839 color:#444;
837 }
840 }
838
841
839 #content .tabs ul li a.selected:hover {background-color: #fff;}
842 #content .tabs ul li a.selected:hover {background-color: #fff;}
840
843
841 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
844 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
842
845
843 button.tab-left, button.tab-right {
846 button.tab-left, button.tab-right {
844 font-size: 0.9em;
847 font-size: 0.9em;
845 cursor: pointer;
848 cursor: pointer;
846 height:24px;
849 height:24px;
847 border: 1px solid #ccc;
850 border: 1px solid #ccc;
848 border-bottom: 1px solid #bbbbbb;
851 border-bottom: 1px solid #bbbbbb;
849 position:absolute;
852 position:absolute;
850 padding:4px;
853 padding:4px;
851 width: 20px;
854 width: 20px;
852 bottom: -1px;
855 bottom: -1px;
853 }
856 }
854
857
855 button.tab-left {
858 button.tab-left {
856 right: 20px;
859 right: 20px;
857 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
860 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
858 border-top-left-radius:3px;
861 border-top-left-radius:3px;
859 }
862 }
860
863
861 button.tab-right {
864 button.tab-right {
862 right: 0;
865 right: 0;
863 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
866 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
864 border-top-right-radius:3px;
867 border-top-right-radius:3px;
865 }
868 }
866
869
867 /***** Diff *****/
870 /***** Diff *****/
868 .diff_out { background: #fcc; }
871 .diff_out { background: #fcc; }
869 .diff_out span { background: #faa; }
872 .diff_out span { background: #faa; }
870 .diff_in { background: #cfc; }
873 .diff_in { background: #cfc; }
871 .diff_in span { background: #afa; }
874 .diff_in span { background: #afa; }
872
875
873 .text-diff {
876 .text-diff {
874 padding: 1em;
877 padding: 1em;
875 background-color:#f6f6f6;
878 background-color:#f6f6f6;
876 color:#505050;
879 color:#505050;
877 border: 1px solid #e4e4e4;
880 border: 1px solid #e4e4e4;
878 }
881 }
879
882
880 /***** Wiki *****/
883 /***** Wiki *****/
881 div.wiki table {
884 div.wiki table {
882 border-collapse: collapse;
885 border-collapse: collapse;
883 margin-bottom: 1em;
886 margin-bottom: 1em;
884 }
887 }
885
888
886 div.wiki table, div.wiki td, div.wiki th {
889 div.wiki table, div.wiki td, div.wiki th {
887 border: 1px solid #bbb;
890 border: 1px solid #bbb;
888 padding: 4px;
891 padding: 4px;
889 }
892 }
890
893
891 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
894 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
892
895
893 div.wiki .external {
896 div.wiki .external {
894 background-position: 0% 60%;
897 background-position: 0% 60%;
895 background-repeat: no-repeat;
898 background-repeat: no-repeat;
896 padding-left: 12px;
899 padding-left: 12px;
897 background-image: url(../images/external.png);
900 background-image: url(../images/external.png);
898 }
901 }
899
902
900 div.wiki a {word-wrap: break-word;}
903 div.wiki a {word-wrap: break-word;}
901 div.wiki a.new {color: #b73535;}
904 div.wiki a.new {color: #b73535;}
902
905
903 div.wiki ul, div.wiki ol {margin-bottom:1em;}
906 div.wiki ul, div.wiki ol {margin-bottom:1em;}
904 div.wiki li>ul, div.wiki li>ol {margin-bottom: 0;}
907 div.wiki li>ul, div.wiki li>ol {margin-bottom: 0;}
905
908
906 div.wiki pre {
909 div.wiki pre {
907 margin: 1em 1em 1em 1.6em;
910 margin: 1em 1em 1em 1.6em;
908 padding: 8px;
911 padding: 8px;
909 background-color: #fafafa;
912 background-color: #fafafa;
910 border: 1px solid #e2e2e2;
913 border: 1px solid #e2e2e2;
911 border-radius: 3px;
914 border-radius: 3px;
912 width:auto;
915 width:auto;
913 overflow-x: auto;
916 overflow-x: auto;
914 overflow-y: hidden;
917 overflow-y: hidden;
915 }
918 }
916
919
917 div.wiki ul.toc {
920 div.wiki ul.toc {
918 background-color: #ffffdd;
921 background-color: #ffffdd;
919 border: 1px solid #e4e4e4;
922 border: 1px solid #e4e4e4;
920 padding: 4px;
923 padding: 4px;
921 line-height: 1.2em;
924 line-height: 1.2em;
922 margin-bottom: 12px;
925 margin-bottom: 12px;
923 margin-right: 12px;
926 margin-right: 12px;
924 margin-left: 0;
927 margin-left: 0;
925 display: table
928 display: table
926 }
929 }
927 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
930 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
928
931
929 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
932 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
930 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
933 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
931 div.wiki ul.toc ul { margin: 0; padding: 0; }
934 div.wiki ul.toc ul { margin: 0; padding: 0; }
932 div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;}
935 div.wiki ul.toc li {list-style-type:none; margin: 0; font-size:12px;}
933 div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;}
936 div.wiki ul.toc li li {margin-left: 1.5em; font-size:10px;}
934 div.wiki ul.toc a {
937 div.wiki ul.toc a {
935 font-size: 0.9em;
938 font-size: 0.9em;
936 font-weight: normal;
939 font-weight: normal;
937 text-decoration: none;
940 text-decoration: none;
938 color: #606060;
941 color: #606060;
939 }
942 }
940 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
943 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
941
944
942 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
945 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
943 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
946 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
944 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
947 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
945
948
946 div.wiki img {vertical-align:middle; max-width:100%;}
949 div.wiki img {vertical-align:middle; max-width:100%;}
947
950
948 /***** My page layout *****/
951 /***** My page layout *****/
949 .block-receiver {
952 .block-receiver {
950 border:1px dashed #c0c0c0;
953 border:1px dashed #c0c0c0;
951 margin-bottom: 20px;
954 margin-bottom: 20px;
952 padding: 15px 0 15px 0;
955 padding: 15px 0 15px 0;
953 }
956 }
954
957
955 .mypage-box {
958 .mypage-box {
956 margin:0 0 20px 0;
959 margin:0 0 20px 0;
957 color:#505050;
960 color:#505050;
958 line-height:1.5em;
961 line-height:1.5em;
959 }
962 }
960
963
961 .handle {cursor: move;}
964 .handle {cursor: move;}
962
965
963 a.close-icon {
966 a.close-icon {
964 display:block;
967 display:block;
965 margin-top:3px;
968 margin-top:3px;
966 overflow:hidden;
969 overflow:hidden;
967 width:12px;
970 width:12px;
968 height:12px;
971 height:12px;
969 background-repeat: no-repeat;
972 background-repeat: no-repeat;
970 cursor:pointer;
973 cursor:pointer;
971 background-image:url('../images/close.png');
974 background-image:url('../images/close.png');
972 }
975 }
973 a.close-icon:hover {background-image:url('../images/close_hl.png');}
976 a.close-icon:hover {background-image:url('../images/close_hl.png');}
974
977
975 /***** Gantt chart *****/
978 /***** Gantt chart *****/
976 .gantt_hdr {
979 .gantt_hdr {
977 position:absolute;
980 position:absolute;
978 top:0;
981 top:0;
979 height:16px;
982 height:16px;
980 border-top: 1px solid #c0c0c0;
983 border-top: 1px solid #c0c0c0;
981 border-bottom: 1px solid #c0c0c0;
984 border-bottom: 1px solid #c0c0c0;
982 border-right: 1px solid #c0c0c0;
985 border-right: 1px solid #c0c0c0;
983 text-align: center;
986 text-align: center;
984 overflow: hidden;
987 overflow: hidden;
985 }
988 }
986
989
987 .gantt_hdr.nwday {background-color:#f1f1f1; color:#999;}
990 .gantt_hdr.nwday {background-color:#f1f1f1; color:#999;}
988
991
989 .gantt_subjects { font-size: 0.8em; }
992 .gantt_subjects { font-size: 0.8em; }
990 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
993 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
991
994
992 .task {
995 .task {
993 position: absolute;
996 position: absolute;
994 height:8px;
997 height:8px;
995 font-size:0.8em;
998 font-size:0.8em;
996 color:#888;
999 color:#888;
997 padding:0;
1000 padding:0;
998 margin:0;
1001 margin:0;
999 line-height:16px;
1002 line-height:16px;
1000 white-space:nowrap;
1003 white-space:nowrap;
1001 }
1004 }
1002
1005
1003 .task.label {width:100%;}
1006 .task.label {width:100%;}
1004 .task.label.project, .task.label.version { font-weight: bold; }
1007 .task.label.project, .task.label.version { font-weight: bold; }
1005
1008
1006 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
1009 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
1007 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
1010 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
1008 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
1011 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
1009
1012
1010 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
1013 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
1011 .task_late.parent, .task_done.parent { height: 3px;}
1014 .task_late.parent, .task_done.parent { height: 3px;}
1012 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
1015 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
1013 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
1016 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
1014
1017
1015 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1018 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1016 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1019 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1017 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1020 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1018 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1021 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1019
1022
1020 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1023 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1021 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1024 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1022 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1025 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1023 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1026 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1024
1027
1025 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
1028 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
1026 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
1029 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
1027
1030
1028 /***** Icons *****/
1031 /***** Icons *****/
1029 .icon {
1032 .icon {
1030 background-position: 0% 50%;
1033 background-position: 0% 50%;
1031 background-repeat: no-repeat;
1034 background-repeat: no-repeat;
1032 padding-left: 20px;
1035 padding-left: 20px;
1033 padding-top: 2px;
1036 padding-top: 2px;
1034 padding-bottom: 3px;
1037 padding-bottom: 3px;
1035 }
1038 }
1036
1039
1037 .icon-add { background-image: url(../images/add.png); }
1040 .icon-add { background-image: url(../images/add.png); }
1038 .icon-edit { background-image: url(../images/edit.png); }
1041 .icon-edit { background-image: url(../images/edit.png); }
1039 .icon-copy { background-image: url(../images/copy.png); }
1042 .icon-copy { background-image: url(../images/copy.png); }
1040 .icon-duplicate { background-image: url(../images/duplicate.png); }
1043 .icon-duplicate { background-image: url(../images/duplicate.png); }
1041 .icon-del { background-image: url(../images/delete.png); }
1044 .icon-del { background-image: url(../images/delete.png); }
1042 .icon-move { background-image: url(../images/move.png); }
1045 .icon-move { background-image: url(../images/move.png); }
1043 .icon-save { background-image: url(../images/save.png); }
1046 .icon-save { background-image: url(../images/save.png); }
1044 .icon-cancel { background-image: url(../images/cancel.png); }
1047 .icon-cancel { background-image: url(../images/cancel.png); }
1045 .icon-multiple { background-image: url(../images/table_multiple.png); }
1048 .icon-multiple { background-image: url(../images/table_multiple.png); }
1046 .icon-folder { background-image: url(../images/folder.png); }
1049 .icon-folder { background-image: url(../images/folder.png); }
1047 .open .icon-folder { background-image: url(../images/folder_open.png); }
1050 .open .icon-folder { background-image: url(../images/folder_open.png); }
1048 .icon-package { background-image: url(../images/package.png); }
1051 .icon-package { background-image: url(../images/package.png); }
1049 .icon-user { background-image: url(../images/user.png); }
1052 .icon-user { background-image: url(../images/user.png); }
1050 .icon-projects { background-image: url(../images/projects.png); }
1053 .icon-projects { background-image: url(../images/projects.png); }
1051 .icon-help { background-image: url(../images/help.png); }
1054 .icon-help { background-image: url(../images/help.png); }
1052 .icon-attachment { background-image: url(../images/attachment.png); }
1055 .icon-attachment { background-image: url(../images/attachment.png); }
1053 .icon-history { background-image: url(../images/history.png); }
1056 .icon-history { background-image: url(../images/history.png); }
1054 .icon-time { background-image: url(../images/time.png); }
1057 .icon-time { background-image: url(../images/time.png); }
1055 .icon-time-add { background-image: url(../images/time_add.png); }
1058 .icon-time-add { background-image: url(../images/time_add.png); }
1056 .icon-stats { background-image: url(../images/stats.png); }
1059 .icon-stats { background-image: url(../images/stats.png); }
1057 .icon-warning { background-image: url(../images/warning.png); }
1060 .icon-warning { background-image: url(../images/warning.png); }
1058 .icon-fav { background-image: url(../images/fav.png); }
1061 .icon-fav { background-image: url(../images/fav.png); }
1059 .icon-fav-off { background-image: url(../images/fav_off.png); }
1062 .icon-fav-off { background-image: url(../images/fav_off.png); }
1060 .icon-reload { background-image: url(../images/reload.png); }
1063 .icon-reload { background-image: url(../images/reload.png); }
1061 .icon-lock { background-image: url(../images/locked.png); }
1064 .icon-lock { background-image: url(../images/locked.png); }
1062 .icon-unlock { background-image: url(../images/unlock.png); }
1065 .icon-unlock { background-image: url(../images/unlock.png); }
1063 .icon-checked { background-image: url(../images/true.png); }
1066 .icon-checked { background-image: url(../images/true.png); }
1064 .icon-details { background-image: url(../images/zoom_in.png); }
1067 .icon-details { background-image: url(../images/zoom_in.png); }
1065 .icon-report { background-image: url(../images/report.png); }
1068 .icon-report { background-image: url(../images/report.png); }
1066 .icon-comment { background-image: url(../images/comment.png); }
1069 .icon-comment { background-image: url(../images/comment.png); }
1067 .icon-summary { background-image: url(../images/lightning.png); }
1070 .icon-summary { background-image: url(../images/lightning.png); }
1068 .icon-server-authentication { background-image: url(../images/server_key.png); }
1071 .icon-server-authentication { background-image: url(../images/server_key.png); }
1069 .icon-issue { background-image: url(../images/ticket.png); }
1072 .icon-issue { background-image: url(../images/ticket.png); }
1070 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
1073 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
1071 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1074 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
1072 .icon-passwd { background-image: url(../images/textfield_key.png); }
1075 .icon-passwd { background-image: url(../images/textfield_key.png); }
1073 .icon-test { background-image: url(../images/bullet_go.png); }
1076 .icon-test { background-image: url(../images/bullet_go.png); }
1074 .icon-email-add { background-image: url(../images/email_add.png); }
1077 .icon-email-add { background-image: url(../images/email_add.png); }
1075
1078
1076 .icon-file { background-image: url(../images/files/default.png); }
1079 .icon-file { background-image: url(../images/files/default.png); }
1077 .icon-file.text-plain { background-image: url(../images/files/text.png); }
1080 .icon-file.text-plain { background-image: url(../images/files/text.png); }
1078 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
1081 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
1079 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
1082 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
1080 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
1083 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
1081 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
1084 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
1082 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
1085 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
1083 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
1086 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
1084 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
1087 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
1085 .icon-file.text-css { background-image: url(../images/files/css.png); }
1088 .icon-file.text-css { background-image: url(../images/files/css.png); }
1086 .icon-file.text-html { background-image: url(../images/files/html.png); }
1089 .icon-file.text-html { background-image: url(../images/files/html.png); }
1087 .icon-file.image-gif { background-image: url(../images/files/image.png); }
1090 .icon-file.image-gif { background-image: url(../images/files/image.png); }
1088 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
1091 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
1089 .icon-file.image-png { background-image: url(../images/files/image.png); }
1092 .icon-file.image-png { background-image: url(../images/files/image.png); }
1090 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
1093 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
1091 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
1094 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
1092 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
1095 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
1093 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
1096 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
1094
1097
1095 img.gravatar {
1098 img.gravatar {
1096 padding: 2px;
1099 padding: 2px;
1097 border: solid 1px #d5d5d5;
1100 border: solid 1px #d5d5d5;
1098 background: #fff;
1101 background: #fff;
1099 vertical-align: middle;
1102 vertical-align: middle;
1100 }
1103 }
1101
1104
1102 div.issue img.gravatar {
1105 div.issue img.gravatar {
1103 float: left;
1106 float: left;
1104 margin: 0 6px 0 0;
1107 margin: 0 6px 0 0;
1105 padding: 5px;
1108 padding: 5px;
1106 }
1109 }
1107
1110
1108 div.issue table img.gravatar {
1111 div.issue table img.gravatar {
1109 height: 14px;
1112 height: 14px;
1110 width: 14px;
1113 width: 14px;
1111 padding: 2px;
1114 padding: 2px;
1112 float: left;
1115 float: left;
1113 margin: 0 0.5em 0 0;
1116 margin: 0 0.5em 0 0;
1114 }
1117 }
1115
1118
1116 h2 img.gravatar {margin: -2px 4px -4px 0;}
1119 h2 img.gravatar {margin: -2px 4px -4px 0;}
1117 h3 img.gravatar {margin: -4px 4px -4px 0;}
1120 h3 img.gravatar {margin: -4px 4px -4px 0;}
1118 h4 img.gravatar {margin: -6px 4px -4px 0;}
1121 h4 img.gravatar {margin: -6px 4px -4px 0;}
1119 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1122 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1120 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1123 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1121 /* Used on 12px Gravatar img tags without the icon background */
1124 /* Used on 12px Gravatar img tags without the icon background */
1122 .icon-gravatar {float: left; margin-right: 4px;}
1125 .icon-gravatar {float: left; margin-right: 4px;}
1123
1126
1124 #activity dt, .journal {clear: left;}
1127 #activity dt, .journal {clear: left;}
1125
1128
1126 .journal-link {float: right;}
1129 .journal-link {float: right;}
1127
1130
1128 h2 img { vertical-align:middle; }
1131 h2 img { vertical-align:middle; }
1129
1132
1130 .hascontextmenu { cursor: context-menu; }
1133 .hascontextmenu { cursor: context-menu; }
1131
1134
1132 .sample-data {border:1px solid #ccc; border-collapse:collapse; background-color:#fff; margin:0.5em;}
1135 .sample-data {border:1px solid #ccc; border-collapse:collapse; background-color:#fff; margin:0.5em;}
1133 .sample-data td {border:1px solid #ccc; padding: 2px 4px; font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
1136 .sample-data td {border:1px solid #ccc; padding: 2px 4px; font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
1134 .sample-data tr:first-child td {font-weight:bold; text-align:center;}
1137 .sample-data tr:first-child td {font-weight:bold; text-align:center;}
1135
1138
1136 .ui-progressbar {position: relative;}
1139 .ui-progressbar {position: relative;}
1137 #progress-label {
1140 #progress-label {
1138 position: absolute; left: 50%; top: 4px;
1141 position: absolute; left: 50%; top: 4px;
1139 font-weight: bold;
1142 font-weight: bold;
1140 color: #555; text-shadow: 1px 1px 0 #fff;
1143 color: #555; text-shadow: 1px 1px 0 #fff;
1141 }
1144 }
1142
1145
1143 /* Custom JQuery styles */
1146 /* Custom JQuery styles */
1144 .ui-datepicker-title select {width:70px !important; margin-top:-2px !important; margin-right:4px !important;}
1147 .ui-datepicker-title select {width:70px !important; margin-top:-2px !important; margin-right:4px !important;}
1145
1148
1146
1149
1147 /************* CodeRay styles *************/
1150 /************* CodeRay styles *************/
1148 .syntaxhl div {display: inline;}
1151 .syntaxhl div {display: inline;}
1149 .syntaxhl .code pre { overflow: auto }
1152 .syntaxhl .code pre { overflow: auto }
1150
1153
1151 .syntaxhl .annotation { color:#007 }
1154 .syntaxhl .annotation { color:#007 }
1152 .syntaxhl .attribute-name { color:#b48 }
1155 .syntaxhl .attribute-name { color:#b48 }
1153 .syntaxhl .attribute-value { color:#700 }
1156 .syntaxhl .attribute-value { color:#700 }
1154 .syntaxhl .binary { color:#549 }
1157 .syntaxhl .binary { color:#549 }
1155 .syntaxhl .binary .char { color:#325 }
1158 .syntaxhl .binary .char { color:#325 }
1156 .syntaxhl .binary .delimiter { color:#325 }
1159 .syntaxhl .binary .delimiter { color:#325 }
1157 .syntaxhl .char { color:#D20 }
1160 .syntaxhl .char { color:#D20 }
1158 .syntaxhl .char .content { color:#D20 }
1161 .syntaxhl .char .content { color:#D20 }
1159 .syntaxhl .char .delimiter { color:#710 }
1162 .syntaxhl .char .delimiter { color:#710 }
1160 .syntaxhl .class { color:#258; font-weight:bold }
1163 .syntaxhl .class { color:#258; font-weight:bold }
1161 .syntaxhl .class-variable { color:#369 }
1164 .syntaxhl .class-variable { color:#369 }
1162 .syntaxhl .color { color:#0A0 }
1165 .syntaxhl .color { color:#0A0 }
1163 .syntaxhl .comment { color:#385 }
1166 .syntaxhl .comment { color:#385 }
1164 .syntaxhl .comment .char { color:#385 }
1167 .syntaxhl .comment .char { color:#385 }
1165 .syntaxhl .comment .delimiter { color:#385 }
1168 .syntaxhl .comment .delimiter { color:#385 }
1166 .syntaxhl .constant { color:#258; font-weight:bold }
1169 .syntaxhl .constant { color:#258; font-weight:bold }
1167 .syntaxhl .decorator { color:#B0B }
1170 .syntaxhl .decorator { color:#B0B }
1168 .syntaxhl .definition { color:#099; font-weight:bold }
1171 .syntaxhl .definition { color:#099; font-weight:bold }
1169 .syntaxhl .delimiter { color:black }
1172 .syntaxhl .delimiter { color:black }
1170 .syntaxhl .directive { color:#088; font-weight:bold }
1173 .syntaxhl .directive { color:#088; font-weight:bold }
1171 .syntaxhl .docstring { color:#D42; }
1174 .syntaxhl .docstring { color:#D42; }
1172 .syntaxhl .doctype { color:#34b }
1175 .syntaxhl .doctype { color:#34b }
1173 .syntaxhl .done { text-decoration: line-through; color: gray }
1176 .syntaxhl .done { text-decoration: line-through; color: gray }
1174 .syntaxhl .entity { color:#800; font-weight:bold }
1177 .syntaxhl .entity { color:#800; font-weight:bold }
1175 .syntaxhl .error { color:#F00; background-color:#FAA }
1178 .syntaxhl .error { color:#F00; background-color:#FAA }
1176 .syntaxhl .escape { color:#666 }
1179 .syntaxhl .escape { color:#666 }
1177 .syntaxhl .exception { color:#C00; font-weight:bold }
1180 .syntaxhl .exception { color:#C00; font-weight:bold }
1178 .syntaxhl .float { color:#06D }
1181 .syntaxhl .float { color:#06D }
1179 .syntaxhl .function { color:#06B; font-weight:bold }
1182 .syntaxhl .function { color:#06B; font-weight:bold }
1180 .syntaxhl .function .delimiter { color:#024; font-weight:bold }
1183 .syntaxhl .function .delimiter { color:#024; font-weight:bold }
1181 .syntaxhl .global-variable { color:#d70 }
1184 .syntaxhl .global-variable { color:#d70 }
1182 .syntaxhl .hex { color:#02b }
1185 .syntaxhl .hex { color:#02b }
1183 .syntaxhl .id { color:#33D; font-weight:bold }
1186 .syntaxhl .id { color:#33D; font-weight:bold }
1184 .syntaxhl .include { color:#B44; font-weight:bold }
1187 .syntaxhl .include { color:#B44; font-weight:bold }
1185 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1188 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1186 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1189 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1187 .syntaxhl .instance-variable { color:#33B }
1190 .syntaxhl .instance-variable { color:#33B }
1188 .syntaxhl .integer { color:#06D }
1191 .syntaxhl .integer { color:#06D }
1189 .syntaxhl .imaginary { color:#f00 }
1192 .syntaxhl .imaginary { color:#f00 }
1190 .syntaxhl .important { color:#D00 }
1193 .syntaxhl .important { color:#D00 }
1191 .syntaxhl .key { color: #606 }
1194 .syntaxhl .key { color: #606 }
1192 .syntaxhl .key .char { color: #60f }
1195 .syntaxhl .key .char { color: #60f }
1193 .syntaxhl .key .delimiter { color: #404 }
1196 .syntaxhl .key .delimiter { color: #404 }
1194 .syntaxhl .keyword { color:#939; font-weight:bold }
1197 .syntaxhl .keyword { color:#939; font-weight:bold }
1195 .syntaxhl .label { color:#970; font-weight:bold }
1198 .syntaxhl .label { color:#970; font-weight:bold }
1196 .syntaxhl .local-variable { color:#950 }
1199 .syntaxhl .local-variable { color:#950 }
1197 .syntaxhl .map .content { color:#808 }
1200 .syntaxhl .map .content { color:#808 }
1198 .syntaxhl .map .delimiter { color:#40A}
1201 .syntaxhl .map .delimiter { color:#40A}
1199 .syntaxhl .map { background-color:hsla(200,100%,50%,0.06); }
1202 .syntaxhl .map { background-color:hsla(200,100%,50%,0.06); }
1200 .syntaxhl .namespace { color:#707; font-weight:bold }
1203 .syntaxhl .namespace { color:#707; font-weight:bold }
1201 .syntaxhl .octal { color:#40E }
1204 .syntaxhl .octal { color:#40E }
1202 .syntaxhl .operator { }
1205 .syntaxhl .operator { }
1203 .syntaxhl .predefined { color:#369; font-weight:bold }
1206 .syntaxhl .predefined { color:#369; font-weight:bold }
1204 .syntaxhl .predefined-constant { color:#069 }
1207 .syntaxhl .predefined-constant { color:#069 }
1205 .syntaxhl .predefined-type { color:#0a8; font-weight:bold }
1208 .syntaxhl .predefined-type { color:#0a8; font-weight:bold }
1206 .syntaxhl .preprocessor { color:#579 }
1209 .syntaxhl .preprocessor { color:#579 }
1207 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1210 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1208 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1211 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1209 .syntaxhl .regexp .content { color:#808 }
1212 .syntaxhl .regexp .content { color:#808 }
1210 .syntaxhl .regexp .delimiter { color:#404 }
1213 .syntaxhl .regexp .delimiter { color:#404 }
1211 .syntaxhl .regexp .modifier { color:#C2C }
1214 .syntaxhl .regexp .modifier { color:#C2C }
1212 .syntaxhl .reserved { color:#080; font-weight:bold }
1215 .syntaxhl .reserved { color:#080; font-weight:bold }
1213 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1216 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1214 .syntaxhl .shell .content { color:#2B2 }
1217 .syntaxhl .shell .content { color:#2B2 }
1215 .syntaxhl .shell .delimiter { color:#161 }
1218 .syntaxhl .shell .delimiter { color:#161 }
1216 .syntaxhl .string .char { color: #46a }
1219 .syntaxhl .string .char { color: #46a }
1217 .syntaxhl .string .content { color: #46a }
1220 .syntaxhl .string .content { color: #46a }
1218 .syntaxhl .string .delimiter { color: #46a }
1221 .syntaxhl .string .delimiter { color: #46a }
1219 .syntaxhl .string .modifier { color: #46a }
1222 .syntaxhl .string .modifier { color: #46a }
1220 .syntaxhl .symbol { color:#d33 }
1223 .syntaxhl .symbol { color:#d33 }
1221 .syntaxhl .symbol .content { color:#d33 }
1224 .syntaxhl .symbol .content { color:#d33 }
1222 .syntaxhl .symbol .delimiter { color:#d33 }
1225 .syntaxhl .symbol .delimiter { color:#d33 }
1223 .syntaxhl .tag { color:#070; font-weight:bold }
1226 .syntaxhl .tag { color:#070; font-weight:bold }
1224 .syntaxhl .type { color:#339; font-weight:bold }
1227 .syntaxhl .type { color:#339; font-weight:bold }
1225 .syntaxhl .value { color: #088 }
1228 .syntaxhl .value { color: #088 }
1226 .syntaxhl .variable { color:#037 }
1229 .syntaxhl .variable { color:#037 }
1227
1230
1228 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1231 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1229 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1232 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1230 .syntaxhl .change { color: #bbf; background: #007 }
1233 .syntaxhl .change { color: #bbf; background: #007 }
1231 .syntaxhl .head { color: #f8f; background: #505 }
1234 .syntaxhl .head { color: #f8f; background: #505 }
1232 .syntaxhl .head .filename { color: white; }
1235 .syntaxhl .head .filename { color: white; }
1233
1236
1234 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1237 .syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
1235 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1238 .syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
1236
1239
1237 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1240 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1238 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1241 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1239 .syntaxhl .change .change { color: #88f }
1242 .syntaxhl .change .change { color: #88f }
1240 .syntaxhl .head .head { color: #f4f }
1243 .syntaxhl .head .head { color: #f4f }
1241
1244
1242 /***** Media print specific styles *****/
1245 /***** Media print specific styles *****/
1243 @media print {
1246 @media print {
1244 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1247 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1245 #main { background: #fff; }
1248 #main { background: #fff; }
1246 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1249 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1247 #wiki_add_attachment { display:none; }
1250 #wiki_add_attachment { display:none; }
1248 .hide-when-print { display: none; }
1251 .hide-when-print { display: none; }
1249 .autoscroll {overflow-x: visible;}
1252 .autoscroll {overflow-x: visible;}
1250 table.list {margin-top:0.5em;}
1253 table.list {margin-top:0.5em;}
1251 table.list th, table.list td {border: 1px solid #aaa;}
1254 table.list th, table.list td {border: 1px solid #aaa;}
1252 }
1255 }
1253
1256
1254 /* Accessibility specific styles */
1257 /* Accessibility specific styles */
1255 .hidden-for-sighted {
1258 .hidden-for-sighted {
1256 position:absolute;
1259 position:absolute;
1257 left:-10000px;
1260 left:-10000px;
1258 top:auto;
1261 top:auto;
1259 width:1px;
1262 width:1px;
1260 height:1px;
1263 height:1px;
1261 overflow:hidden;
1264 overflow:hidden;
1262 }
1265 }
@@ -1,783 +1,785
1 /*----------------------------------------*\
1 /*----------------------------------------*\
2 RESPONSIVE CSS
2 RESPONSIVE CSS
3 \*----------------------------------------*/
3 \*----------------------------------------*/
4
4
5
5
6 /*
6 /*
7
7
8 CONTENTS
8 CONTENTS
9
9
10 A) BASIC MOBILE RESETS
10 A) BASIC MOBILE RESETS
11 B) HEADER & TOP MENUS
11 B) HEADER & TOP MENUS
12 C) MAIN CONTENT & SIDEBAR
12 C) MAIN CONTENT & SIDEBAR
13 D) TOGGLE BUTTON & FLYOUT MENU
13 D) TOGGLE BUTTON & FLYOUT MENU
14 E) UX ELEMENTS
14 E) UX ELEMENTS
15 F) PAGE SPECIFIC STYLES
15 F) PAGE SPECIFIC STYLES
16 G) FORMS
16 G) FORMS
17
17
18 */
18 */
19
19
20
20
21 /* Hide new elements (toggle button and flyout menu) above 900px */
21 /* Hide new elements (toggle button and flyout menu) above 900px */
22 .mobile-toggle-button,
22 .mobile-toggle-button,
23 .flyout-menu
23 .flyout-menu
24 {
24 {
25 display: none;
25 display: none;
26 }
26 }
27
27
28 /*
28 /*
29 redmine's body is set to min-width: 900px
29 redmine's body is set to min-width: 900px
30 add first breakpoint here and start adding responsiveness
30 add first breakpoint here and start adding responsiveness
31 */
31 */
32
32
33 @media all and (max-width: 899px)
33 @media all and (max-width: 899px)
34 {
34 {
35 /*----------------------------------------*\
35 /*----------------------------------------*\
36 A) BASIC MOBILE RESETS
36 A) BASIC MOBILE RESETS
37 \*----------------------------------------*/
37 \*----------------------------------------*/
38
38
39 /*
39 /*
40 apply natural border box, see: http://www.paulirish.com/2012/box-sizing-border-box-ftw/
40 apply natural border box, see: http://www.paulirish.com/2012/box-sizing-border-box-ftw/
41 this helps us to better deal with percentages and padding / margin
41 this helps us to better deal with percentages and padding / margin
42 */
42 */
43 *,
43 *,
44 *:before,
44 *:before,
45 *:after
45 *:after
46 {
46 {
47 -webkit-box-sizing: border-box;
47 -webkit-box-sizing: border-box;
48 -moz-box-sizing: border-box;
48 -moz-box-sizing: border-box;
49 box-sizing: border-box;
49 box-sizing: border-box;
50 }
50 }
51
51
52 body,
52 body,
53 html
53 html
54 {
54 {
55 height: 100%;
55 height: 100%;
56 margin: 0;
56 margin: 0;
57 padding: 0;
57 padding: 0;
58 }
58 }
59
59
60 html
60 html
61 {
61 {
62 overflow-y: auto; /* avoid 2nd scrollbar on desktop */
62 overflow-y: auto; /* avoid 2nd scrollbar on desktop */
63 }
63 }
64
64
65 body
65 body
66 {
66 {
67 min-width: 0; /* reset the min-width of 900px */
67 min-width: 0; /* reset the min-width of 900px */
68
68
69 -webkit-overflow-scrolling: touch;
69 -webkit-overflow-scrolling: touch;
70 }
70 }
71
71
72
72
73 body,
73 body,
74 input,
74 input,
75 select,
75 select,
76 textarea,
76 textarea,
77 button
77 button
78 {
78 {
79 font-size: 14px; /* Set font-size for standard elements to 14px */
79 font-size: 14px; /* Set font-size for standard elements to 14px */
80 }
80 }
81
81
82
82
83 select
83 select
84 {
84 {
85 max-width: 100%; /* prevent long names within select menues from breaking content */
85 max-width: 100%; /* prevent long names within select menues from breaking content */
86 }
86 }
87
87
88
88
89 #wrapper
89 #wrapper
90 {
90 {
91 position: relative;
91 position: relative;
92
92
93 overflow-x: hidden; /* hide horizontal overflow */
93 overflow-x: hidden; /* hide horizontal overflow */
94
94
95 max-width: 100%;
95 max-width: 100%;
96 }
96 }
97
97
98 #wrapper,
98 #wrapper,
99 #wrapper2
99 #wrapper2
100 {
100 {
101 margin: 0;
101 margin: 0;
102 }
102 }
103
103
104 /*----------------------------------------*\
104 /*----------------------------------------*\
105 B) HEADER & TOP MENUS
105 B) HEADER & TOP MENUS
106 \*----------------------------------------*/
106 \*----------------------------------------*/
107
107
108 #header
108 #header
109 {
109 {
110 width: 100%;
110 width: 100%;
111 height: 64px; /* the height of our header on mobile */
111 height: 64px; /* the height of our header on mobile */
112 min-height: 0;
112 min-height: 0;
113 margin: 0;
113 margin: 0;
114 padding: 0;
114 padding: 0;
115
115
116 border: none;
116 border: none;
117 background-color: #628db6;
117 background-color: #628db6;
118 }
118 }
119
119
120 /* Hide project name on mobile (project name is still visible in select menu) */
120 /* Hide project name on mobile (project name is still visible in select menu) */
121 #header h1
121 #header h1
122 {
122 {
123 display: none !important;
123 display: none !important;
124 }
124 }
125
125
126 /* reset #header a color for mobile toggle button */
126 /* reset #header a color for mobile toggle button */
127 #header a.mobile-toggle-button
127 #header a.mobile-toggle-button
128 {
128 {
129 color: #f8f8f8;
129 color: #f8f8f8;
130 }
130 }
131
131
132
132
133 /* Hide top-menu and main-menu on mobile, because it's placed in our flyout menu */
133 /* Hide top-menu and main-menu on mobile, because it's placed in our flyout menu */
134 #top-menu,
134 #top-menu,
135 #header #main-menu
135 #header #main-menu
136 {
136 {
137 display: none;
137 display: none;
138 }
138 }
139
139
140 /* the quick search within header holding search form and #project_quick_jump_box box*/
140 /* the quick search within header holding search form and #project_quick_jump_box box*/
141 #header #quick-search
141 #header #quick-search
142 {
142 {
143 float: none;
143 float: none;
144 clear: none; /* there are themes which set clear property, this resets it */
144 clear: none; /* there are themes which set clear property, this resets it */
145
145
146 max-width: 100%; /* reset max-width */
146 max-width: 100%; /* reset max-width */
147 margin: 0;
147 margin: 0;
148
148
149 background: inherit;
149 background: inherit;
150 }
150 }
151
151
152 /* this represents the dropdown arrow to left of the mobile project menu */
152 /* this represents the dropdown arrow to left of the mobile project menu */
153 #header .jump-box-arrow:before
153 #header .jump-box-arrow:before
154 {
154 {
155 /* set a font-size in order to achive same result in different themes */
155 /* set a font-size in order to achive same result in different themes */
156 font-family: Verdana, sans-serif;
156 font-family: Verdana, sans-serif;
157 font-size: 2em;
157 font-size: 2em;
158 line-height: 64px;
158 line-height: 64px;
159
159
160 position: absolute;
160 position: absolute;
161 left: 0;
161 left: 0;
162
162
163 width: 2em;
163 width: 2em;
164 padding: 0 .5em;
164 padding: 0 .5em;
165 /* achieve dropdwon arrow by scaling a caret character */
165 /* achieve dropdwon arrow by scaling a caret character */
166
166
167 content: '^';
167 content: '^';
168 -webkit-transform: scale(1,-.8);
168 -webkit-transform: scale(1,-.8);
169 -ms-transform: scale(1,-.8);
169 -ms-transform: scale(1,-.8);
170 transform: scale(1,-.8);
170 transform: scale(1,-.8);
171 text-align: right;
171 text-align: right;
172 pointer-events: none;
172 pointer-events: none;
173
173
174 opacity: .6;
174 opacity: .6;
175 }
175 }
176
176
177 /* styles for combobox within quick-search (#project_quick_jump_box) */
177 /* styles for combobox within quick-search (#project_quick_jump_box) */
178 #header #quick-search select
178 #header #quick-search select
179 {
179 {
180 font-size: 1.5em;
180 font-size: 1.5em;
181 font-weight: bold;
181 font-weight: bold;
182 line-height: 1.2;
182 line-height: 1.2;
183
183
184 position: absolute;
184 position: absolute;
185 top: 15px;
185 top: 15px;
186 left: 0;
186 left: 0;
187
187
188 float: left;
188 float: left;
189
189
190 width: 100%;
190 width: 100%;
191 max-width: 100%;
191 max-width: 100%;
192 height: 2em;
192 height: 2em;
193 height: 35px;
193 height: 35px;
194 padding: 5px;
194 padding: 5px;
195 padding-right: 72px;
195 padding-right: 72px;
196 padding-left: 50px;
196 padding-left: 50px;
197
197
198 text-indent: .01px;
198 text-indent: .01px;
199
199
200 color: inherit;
200 color: inherit;
201 border: 0;
201 border: 0;
202 -webkit-border-radius: 0;
202 -webkit-border-radius: 0;
203 border-radius: 0;
203 border-radius: 0;
204 background: none;
204 background: none;
205 -webkit-box-shadow: none;
205 -webkit-box-shadow: none;
206 box-shadow: none;
206 box-shadow: none;
207 /* hide default browser arrow */
207 /* hide default browser arrow */
208
208
209 -webkit-appearance: none;
209 -webkit-appearance: none;
210 -moz-appearance: none;
210 -moz-appearance: none;
211 }
211 }
212
212
213 #header #quick-search form
213 #header #quick-search form
214 {
214 {
215 display: none;
215 display: none;
216 }
216 }
217
217
218 /*----------------------------------------*\
218 /*----------------------------------------*\
219 C) MAIN CONTENT & SIDEBAR
219 C) MAIN CONTENT & SIDEBAR
220 \*----------------------------------------*/
220 \*----------------------------------------*/
221
221
222 #main
222 #main
223 {
223 {
224 padding: 0;
224 padding: 0;
225 }
225 }
226
226
227 #main.nosidebar #content,
227 #main.nosidebar #content,
228 div#content
228 div#content
229 {
229 {
230 width: 100%;
230 width: 100%;
231 min-height: 0; /* reset min-height of #content */
231 min-height: 0; /* reset min-height of #content */
232 margin: 0;
232 margin: 0;
233 }
233 }
234
234
235
235
236 /* hide sidebar and sidebar switch panel, since it's placed in mobile flyout menu */
236 /* hide sidebar and sidebar switch panel, since it's placed in mobile flyout menu */
237 #sidebar,
237 #sidebar,
238 #sidebar-switch-panel
238 #sidebar-switch-panel
239 {
239 {
240 display: none;
240 display: none;
241 }
241 }
242
242
243 .splitcontentleft
243 .splitcontentleft
244 {
244 {
245 width: 100%; /* use full width */
245 width: 100%; /* use full width */
246 }
246 }
247
247
248 .splitcontentright
248 .splitcontentright
249 {
249 {
250 width: 100%; /* use full width */
250 width: 100%; /* use full width */
251 }
251 }
252
252
253 /*----------------------------------------*\
253 /*----------------------------------------*\
254 D) TOGGLE BUTTON & FLYOUT MENU
254 D) TOGGLE BUTTON & FLYOUT MENU
255 \*----------------------------------------*/
255 \*----------------------------------------*/
256
256
257 /* Mobile toggle button */
257 /* Mobile toggle button */
258
258
259 .mobile-toggle-button
259 .mobile-toggle-button
260 {
260 {
261 font-size: 42px;
261 font-size: 42px;
262 line-height: 64px;
262 line-height: 64px;
263
263
264 position: relative;
264 position: relative;
265 z-index: 10;
265 z-index: 10;
266
266
267 display: block; /* remove display: none; of non-mobile version */
267 display: block; /* remove display: none; of non-mobile version */
268 float: right;
268 float: right;
269
269
270 width: 60px;
270 width: 60px;
271 height: 64px;
271 height: 64px;
272 margin-top: 0;
272 margin-top: 0;
273
273
274 text-align: center;
274 text-align: center;
275
275
276 border-left: 1px solid #ddd;
276 border-left: 1px solid #ddd;
277 }
277 }
278
278
279 .mobile-toggle-button:hover,
279 .mobile-toggle-button:hover,
280 .mobile-toggle-button:active
280 .mobile-toggle-button:active
281 {
281 {
282 text-decoration: none;
282 text-decoration: none;
283 }
283 }
284
284
285 .mobile-toggle-button:after
285 .mobile-toggle-button:after
286 {
286 {
287 font-family: Verdana, sans-serif;
287 font-family: Verdana, sans-serif;
288
288
289 display: block;
289 display: block;
290
290
291 margin-top: -3px;
291 margin-top: -3px;
292
292
293 content: '\2261';
293 content: '\2261';
294 }
294 }
295
295
296 /* search magnifier icon */
296 /* search magnifier icon */
297 .search-magnifier
297 .search-magnifier
298 {
298 {
299 font-family: Verdana;
299 font-family: Verdana;
300
300
301 cursor: pointer;
301 cursor: pointer;
302 -webkit-transform: rotate(-45deg);
302 -webkit-transform: rotate(-45deg);
303 -moz-transform: rotate(45deg);
303 -moz-transform: rotate(45deg);
304 -o-transform: rotate(45deg);
304 -o-transform: rotate(45deg);
305
305
306 color: #bbb;
306 color: #bbb;
307 }
307 }
308
308
309 .search-magnifier--flyout
309 .search-magnifier--flyout
310 {
310 {
311 font-size: 25px;
311 font-size: 25px;
312 line-height: 54px;
312 line-height: 54px;
313
313
314 position: absolute;
314 position: absolute;
315 z-index: 1;
315 z-index: 1;
316 left: 12px;
316 left: 12px;
317 }
317 }
318
318
319
319
320 /* Flyout Menu */
320 /* Flyout Menu */
321
321
322 .flyout-menu
322 .flyout-menu
323 {
323 {
324 position: absolute;
324 position: absolute;
325 right: -250px;
325 right: -250px;
326
326
327 display: block; /* remove display: none; of non-mobile version */
327 display: block; /* remove display: none; of non-mobile version */
328 overflow-x: hidden;
328 overflow-x: hidden;
329
329
330 width: 250px;
330 width: 250px;
331 height: 100%;
331 height: 100%;
332 margin: 0; /* reset margin for themes that define it */
332 margin: 0; /* reset margin for themes that define it */
333 padding: 0; /* reset padding for themes that define it */
333 padding: 0; /* reset padding for themes that define it */
334
334
335 color: white;
335 color: white;
336 background-color: #3e5b76;
336 background-color: #3e5b76;
337 }
337 }
338
338
339
339
340 /* avoid zoom on search input focus for ios devices */
340 /* avoid zoom on search input focus for ios devices */
341 .flyout-menu input[type='text']
341 .flyout-menu input[type='text']
342 {
342 {
343 font-size: 16px;
343 font-size: 16px;
344 }
344 }
345
345
346 .flyout-menu h3
346 .flyout-menu h3
347 {
347 {
348 font-size: 11px;
348 font-size: 11px;
349 line-height: 19px;
349 line-height: 19px;
350
350
351 height: 20px;
351 height: 20px;
352 margin: 0;
352 margin: 0;
353 padding: 0;
353 padding: 0;
354
354
355 letter-spacing: .1em;
355 letter-spacing: .1em;
356 text-transform: uppercase;
356 text-transform: uppercase;
357
357
358 color: white;
358 color: white;
359 border-top: 1px solid #506a83;
359 border-top: 1px solid #506a83;
360 border-bottom: 1px solid #506a83;
360 border-bottom: 1px solid #506a83;
361 background-color: #628db6;
361 background-color: #628db6;
362 }
362 }
363
363
364 .flyout-menu h4
364 .flyout-menu h4
365 {
365 {
366 color: white;
366 color: white;
367 }
367 }
368
368
369 .flyout-menu h3,
369 .flyout-menu h3,
370 .flyout-menu h4,
370 .flyout-menu h4,
371 .flyout-menu > p,
371 .flyout-menu > p,
372 .flyout-menu > a,
372 .flyout-menu > a,
373 .flyout-menu ul li a,
373 .flyout-menu ul li a,
374 .flyout-menu__search,
374 .flyout-menu__search,
375 .flyout-menu__sidebar > div,
375 .flyout-menu__sidebar > div,
376 .flyout-menu__sidebar > p,
376 .flyout-menu__sidebar > p,
377 .flyout-menu__sidebar > a,
377 .flyout-menu__sidebar > a,
378 .flyout-menu__sidebar > form,
378 .flyout-menu__sidebar > form,
379 .flyout-menu > div,
379 .flyout-menu > div,
380 .flyout-menu > form
380 .flyout-menu > form
381 {
381 {
382 padding-left: 8px;
382 padding-left: 8px;
383 }
383 }
384
384
385 .flyout-menu .flyout-menu__avatar
385 .flyout-menu .flyout-menu__avatar
386 {
386 {
387 margin-top: -1px; /* move avatar up 1px */
387 margin-top: -1px; /* move avatar up 1px */
388 padding-left: 0;
388 padding-left: 0;
389 }
389 }
390
390
391 .flyout-menu__sidebar > form
391 .flyout-menu__sidebar > form
392 {
392 {
393 display: block;
393 display: block;
394 }
394 }
395
395
396 .flyout-menu__sidebar > form h3
396 .flyout-menu__sidebar > form h3
397 {
397 {
398 margin-left: -8px;
398 margin-left: -8px;
399 }
399 }
400
400
401 .flyout-menu__sidebar > form label
401 .flyout-menu__sidebar > form label
402 {
402 {
403 display: inline-block;
403 display: inline-block;
404
404
405 margin: 8px 0;
405 margin: 8px 0;
406 }
406 }
407
407
408 .flyout-menu__sidebar > form br br
408 .flyout-menu__sidebar > form br br
409 {
409 {
410 display: none;
410 display: none;
411 }
411 }
412
412
413 .flyout-menu ul
413 .flyout-menu ul
414 {
414 {
415 margin: 0;
415 margin: 0;
416 padding: 0;
416 padding: 0;
417
417
418 list-style: none;
418 list-style: none;
419 }
419 }
420
420
421 .flyout-menu #watchers
421 .flyout-menu #watchers
422 {
422 {
423 display: -webkit-flex;
423 display: -webkit-flex;
424 display: -ms-flexbox;
424 display: -ms-flexbox;
425 display: -webkit-box;
425 display: -webkit-box;
426 display: flex;
426 display: flex;
427 flex-direction: column;
427 flex-direction: column;
428
428
429 -webkit-flex-direction: column;
429 -webkit-flex-direction: column;
430 -ms-flex-direction: column;
430 -ms-flex-direction: column;
431 -webkit-box-orient: vertical;
431 -webkit-box-orient: vertical;
432 -webkit-box-direction: normal;
432 -webkit-box-direction: normal;
433 }
433 }
434
434
435 .flyout-menu #watchers .contextual
435 .flyout-menu #watchers .contextual
436 {
436 {
437 -webkit-box-ordinal-group: 4;
437 -webkit-box-ordinal-group: 4;
438 -webkit-order: 3;
438 -webkit-order: 3;
439 -ms-flex-order: 3;
439 -ms-flex-order: 3;
440 order: 3;
440 order: 3;
441 }
441 }
442
442
443 .flyout-menu #watchers h3
443 .flyout-menu #watchers h3
444 {
444 {
445 margin-left: -8px;
445 margin-left: -8px;
446 }
446 }
447
447
448 .flyout-menu #watchers ul li
448 .flyout-menu #watchers ul li
449 {
449 {
450 display: -webkit-flex;
450 display: -webkit-flex;
451 display: -ms-flexbox;
451 display: -ms-flexbox;
452 display: -webkit-box;
452 display: -webkit-box;
453 display: flex;
453 display: flex;
454 flex-direction: row;
454 flex-direction: row;
455
455
456 -webkit-flex-direction: row;
456 -webkit-flex-direction: row;
457 -ms-flex-direction: row;
457 -ms-flex-direction: row;
458 -webkit-box-orient: horizontal;
458 -webkit-box-orient: horizontal;
459 -webkit-box-direction: normal;
459 -webkit-box-direction: normal;
460 -webkit-align-items: center;
460 -webkit-align-items: center;
461 -ms-flex-align: center;
461 -ms-flex-align: center;
462 -webkit-box-align: center;
462 -webkit-box-align: center;
463 align-items: center;
463 align-items: center;
464 }
464 }
465
465
466 .flyout-menu ul li a
466 .flyout-menu ul li a
467 {
467 {
468 line-height: 40px;
468 line-height: 40px;
469
469
470 display: block;
470 display: block;
471 overflow: hidden;
471 overflow: hidden;
472
472
473 height: 40px;
473 height: 40px;
474
474
475 white-space: nowrap;
475 white-space: nowrap;
476 text-overflow: ellipsis;
476 text-overflow: ellipsis;
477
477
478 border-top: 1px solid rgba(255,255,255,.1);
478 border-top: 1px solid rgba(255,255,255,.1);
479 }
479 }
480
480
481 .flyout-menu ul li:first-child a
481 .flyout-menu ul li:first-child a
482 {
482 {
483 line-height: 39px;
483 line-height: 39px;
484
484
485 height: 39px;
485 height: 39px;
486
486
487 border-top: none;
487 border-top: none;
488 }
488 }
489
489
490 .flyout-menu a
490 .flyout-menu a
491 {
491 {
492 color: white;
492 color: white;
493 }
493 }
494
494
495 .flyout-menu ul li a:hover
495 .flyout-menu ul li a:hover
496 {
496 {
497 text-decoration: none;
497 text-decoration: none;
498 }
498 }
499
499
500 .flyout-menu ul li a.new-object,
500 .flyout-menu ul li a.new-object,
501 .new-object ~ .menu-children
501 .new-object ~ .menu-children
502 {
502 {
503 display: none;
503 display: none;
504 }
504 }
505
505
506 /* Left flyout search container */
506 /* Left flyout search container */
507 .flyout-menu__search
507 .flyout-menu__search
508 {
508 {
509 line-height: 54px;
509 line-height: 54px;
510
510
511 height: 64px;
511 height: 64px;
512 padding-top: 3px;
512 padding-top: 3px;
513 padding-right: 8px;
513 padding-right: 8px;
514 }
514 }
515
515
516 .flyout-menu__search input[type='text']
516 .flyout-menu__search input[type='text']
517 {
517 {
518 line-height: 2;
518 line-height: 2;
519
519
520 width: 100%;
520 width: 100%;
521 height: 38px;
521 height: 38px;
522 padding-left: 27px;
522 padding-left: 27px;
523
523
524 vertical-align: middle;
524 vertical-align: middle;
525
525
526 border: none;
526 border: none;
527 -webkit-border-radius: 3px;
527 -webkit-border-radius: 3px;
528 border-radius: 3px;
528 border-radius: 3px;
529 background-color: #fff;
529 background-color: #fff;
530 }
530 }
531
531
532 .flyout-menu__avatar
532 .flyout-menu__avatar
533 {
533 {
534 display: -webkit-box;
534 display: -webkit-box;
535 display: -webkit-flex;
535 display: -webkit-flex;
536 display: -ms-flexbox;
536 display: -ms-flexbox;
537 display: flex;
537 display: flex;
538
538
539 width: 100%;
539 width: 100%;
540
540
541 border-top: 1px solid rgba(255,255,255,.1);
541 border-top: 1px solid rgba(255,255,255,.1);
542 }
542 }
543
543
544
544
545 .flyout-menu__avatar img.gravatar
545 .flyout-menu__avatar img.gravatar
546 {
546 {
547 width: 40px;
547 width: 40px;
548 height: 40px;
548 height: 40px;
549 padding: 0;
549 padding: 0;
550
550
551 vertical-align: top;
551 vertical-align: top;
552
552
553 border-width: 0;
553 border-width: 0;
554 }
554 }
555
555
556 .flyout-menu__avatar a
556 .flyout-menu__avatar a
557 {
557 {
558 line-height: 40px;
558 line-height: 40px;
559
559
560 height: auto;
560 height: auto;
561 height: 40px;
561 height: 40px;
562
562
563 text-decoration: none;
563 text-decoration: none;
564
564
565 color: white;
565 color: white;
566 }
566 }
567
567
568 /* avatar */
568 /* avatar */
569 .flyout-menu__avatar a:first-child
569 .flyout-menu__avatar a:first-child
570 {
570 {
571 line-height: 0;
571 line-height: 0;
572
572
573 width: 40px;
573 width: 40px;
574 padding: 0;
574 padding: 0;
575 }
575 }
576
576
577 .flyout-menu__avatar .user
577 .flyout-menu__avatar .user
578 {
578 {
579 padding-left: 15px;
579 padding-left: 15px;
580 }
580 }
581
581
582 /* user link when no avatar is present */
582 /* user link when no avatar is present */
583 .flyout-menu__avatar--no-avatar a.user
583 .flyout-menu__avatar--no-avatar a.user
584 {
584 {
585 line-height: 40px;
585 line-height: 40px;
586
586
587 padding-left: 8px;
587 padding-left: 8px;
588 }
588 }
589
589
590
590
591 .flyout-is-active body
591 .flyout-is-active body
592 {
592 {
593 overflow: hidden; /* for body not to have scrollbars when left flyout menu is active */
593 overflow: hidden; /* for body not to have scrollbars when left flyout menu is active */
594 }
594 }
595
595
596 html.flyout-is-active
596 html.flyout-is-active
597 {
597 {
598 overflow: hidden;
598 overflow: hidden;
599 }
599 }
600
600
601
601
602 .flyout-is-active #wrapper
602 .flyout-is-active #wrapper
603 {
603 {
604 right: 250px; /* when left flyout is active, move body to the right (same amount like flyout-menu's width) */
604 right: 250px; /* when left flyout is active, move body to the right (same amount like flyout-menu's width) */
605
605
606 overflow: visible;
606 overflow: visible;
607
607
608 height: 100%;
608 height: 100%;
609 }
609 }
610
610
611 .flyout-is-active .mobile-toggle-button:after
611 .flyout-is-active .mobile-toggle-button:after
612 {
612 {
613 content: '\00D7'; /* close glyph */
613 content: '\00D7'; /* close glyph */
614 }
614 }
615
615
616 .flyout-is-active #wrapper2
616 .flyout-is-active #wrapper2
617 {
617 {
618
618
619 /*
619 /*
620 * only relevant for devices with cursor when flyout it active, in order to show,
620 * only relevant for devices with cursor when flyout it active, in order to show,
621 * that whole wrapper content is clickable and closes flyout menu
621 * that whole wrapper content is clickable and closes flyout menu
622 */
622 */
623 cursor: pointer;
623 cursor: pointer;
624 }
624 }
625
625
626
626
627 #admin-menu
627 #admin-menu
628 {
628 {
629 padding-left: 0;
629 padding-left: 0;
630 }
630 }
631
631
632 #admin-menu li
632 #admin-menu li
633 {
633 {
634 padding-bottom: 0;
634 padding-bottom: 0;
635 }
635 }
636
636
637 #admin-menu a,
637 #admin-menu a,
638 #admin-menu a.selected
638 #admin-menu a.selected
639 {
639 {
640 line-height: 40px;
640 line-height: 40px;
641
641
642 padding: 0;
642 padding: 0;
643 padding-left: 32px !important;
643 padding-left: 32px !important;
644
644
645 background-position: 8px 50%;
645 background-position: 8px 50%;
646 }
646 }
647
647
648 /*----------------------------------------*\
648 /*----------------------------------------*\
649 E) UX ELEMENTS
649 E) UX ELEMENTS
650 \*----------------------------------------*/
650 \*----------------------------------------*/
651
651
652 /* Contextual Buttons */
652 /* Contextual Buttons */
653
653
654 #content>.contextual
654 #content>.contextual
655 {
655 {
656 width: 100%;
656 width: 100%;
657 margin-bottom: .5em;
657 margin-bottom: .5em;
658 padding-left: 0; /* reset left padding in order to use whole space */
658 padding-left: 0; /* reset left padding in order to use whole space */
659
659
660 white-space: normal;
660 white-space: normal;
661
661
662 color: transparent;
662 color: transparent;
663 }
663 }
664
664
665 #content>.contextual a,
665 #content>.contextual a,
666 p.buttons a
666 p.buttons a
667 {
667 {
668 font-weight: bold;
668 font-weight: bold;
669
669
670 display: inline-block;
670 display: inline-block;
671
671
672 margin: 5px 0;
672 margin: 5px 0;
673 margin-right: 2px;
673 margin-right: 2px;
674 padding: 9px 9px 9px 9px;
674 padding: 9px 9px 9px 9px;
675
675
676 border: 1px solid #ddd;
676 border: 1px solid #ddd;
677 -webkit-border-radius: 3px;
677 -webkit-border-radius: 3px;
678 border-radius: 3px;
678 border-radius: 3px;
679 background-color: transparent;
679 background-color: transparent;
680 background-position-x: 4px;
680 background-position-x: 4px;
681 }
681 }
682
682
683 #content>.contextual a.icon,
683 #content>.contextual a.icon,
684 p.buttons a.icon
684 p.buttons a.icon
685 {
685 {
686 padding-left: 25px;
686 padding-left: 25px;
687 }
687 }
688
688
689 .flyout-menu .contextual
689 .flyout-menu .contextual
690 {
690 {
691 float: none;
691 float: none;
692 }
692 }
693
693
694 /* loading indicator */
694 /* loading indicator */
695 #ajax-indicator {
695 #ajax-indicator {
696 width: 60%;
696 width: 60%;
697 left: 20%;
697 left: 20%;
698 }
698 }
699
699
700 /* jquery ui dialogs */
700 /* jquery ui dialogs */
701 .ui-dialog
701 .ui-dialog
702 {
702 {
703 max-width: 98%;
703 max-width: 98%;
704 margin: 1%;
704 margin: 1%;
705 }
705 }
706
706
707 .ui-dialog .ui-dialog-content
707 .ui-dialog .ui-dialog-content
708 {
708 {
709 padding-left: 0;
709 padding-left: 0;
710 padding-right: 0;
710 padding-right: 0;
711 }
711 }
712
712
713 #filters-table {width:100%; float:none;}
713 #filters-table {width:100%; float:none;}
714 .add-filter {width:100%; float:none; text-align: left; margin-top: 8px;}
714 .add-filter {width:100%; float:none; text-align: left; margin-top: 8px;}
715
715
716 /*----------------------------------------*\
716 /*----------------------------------------*\
717 F) PAGE SPECIFIC STYLES
717 F) PAGE SPECIFIC STYLES
718 \*----------------------------------------*/
718 \*----------------------------------------*/
719
719
720 /* page /login */
720 /* page /login */
721
721
722 #login-form table
722 #login-form table
723 {
723 {
724 width: 100%;
724 width: 100%;
725 }
725 }
726
726
727 #login-form input#username,
727 #login-form input#username,
728 #login-form input#password,
728 #login-form input#password,
729 #login-form input#openid_url
729 #login-form input#openid_url
730 {
730 {
731 width: 100%;
731 width: 100%;
732 height: auto;
732 height: auto;
733 }
733 }
734
734
735 /* some themes add a margin to login page, remove it on mobile */
735 /* some themes add a margin to login page, remove it on mobile */
736 .action-login #main
736 .action-login #main
737 {
737 {
738 margin: 0;
738 margin: 0;
739 }
739 }
740
740
741 div#activity dl, #search-results { margin-left: 0; }
741 div#activity dl, #search-results { margin-left: 0; }
742
742
743 .version-overview table.progress {width:75%;}
743 div#version-summary {float:none; width:100%; margin-left:0;}
744 div#version-summary {float:none; width:100%; margin-left:0;}
745 body.controller-versions.action-show div#roadmap .related-issues {width:100%;}
744
746
745 /*----------------------------------------*\
747 /*----------------------------------------*\
746 G) FORMS
748 G) FORMS
747 \*----------------------------------------*/
749 \*----------------------------------------*/
748
750
749 /* tabular forms resets for mobile */
751 /* tabular forms resets for mobile */
750 .tabular p, .tabular.settings p {
752 .tabular p, .tabular.settings p {
751 padding-left: 0;
753 padding-left: 0;
752 }
754 }
753
755
754 .tabular label, .tabular.settings label {
756 .tabular label, .tabular.settings label {
755 display: block;
757 display: block;
756 width: 100%;
758 width: 100%;
757 margin-left: 0;
759 margin-left: 0;
758 text-align: left;
760 text-align: left;
759 }
761 }
760
762
761 .tabular input, .tabular select, .tabular textarea {
763 .tabular input, .tabular select, .tabular textarea {
762 width: 100%;
764 width: 100%;
763 max-width: 100%;
765 max-width: 100%;
764 }
766 }
765
767
766 .tabular input[type="checkbox"], .tabular input.date {
768 .tabular input[type="checkbox"], .tabular input.date {
767 width: auto;
769 width: auto;
768 max-width: 95%;
770 max-width: 95%;
769 }
771 }
770
772
771 /* new issue form */
773 /* new issue form */
772 #all_attributes p:first-child {
774 #all_attributes p:first-child {
773 float: none !important;
775 float: none !important;
774 }
776 }
775
777
776 #issue_is_private_label {
778 #issue_is_private_label {
777 display: inline;
779 display: inline;
778 }
780 }
779
781
780 span#watchers_inputs {
782 span#watchers_inputs {
781 width: 100%;
783 width: 100%;
782 }
784 }
783 }
785 }
General Comments 0
You need to be logged in to leave comments. Login now