##// END OF EJS Templates
Merged r3282 to r3284 from trunk....
Jean-Philippe Lang -
r3173:9d82bff1a84f
parent child
Show More
@@ -1,43 +1,42
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module JournalsHelper
19 19 def render_notes(journal, options={})
20 20 content = ''
21 21 editable = journal.editable_by?(User.current)
22 22 links = []
23 23 if !journal.notes.blank?
24 24 links << link_to_remote(image_tag('comment.png'),
25 25 { :url => {:controller => 'issues', :action => 'reply', :id => journal.journalized, :journal_id => journal} },
26 26 :title => l(:button_quote)) if options[:reply_links]
27 27 links << link_to_in_place_notes_editor(image_tag('edit.png'), "journal-#{journal.id}-notes",
28 28 { :controller => 'journals', :action => 'edit', :id => journal },
29 29 :title => l(:button_edit)) if editable
30 30 end
31 31 content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
32 32 content << textilizable(journal, :notes)
33 33 css_classes = "wiki"
34 34 css_classes << " editable" if editable
35 css_classes << " gravatar-margin" if Setting.gravatar_enabled?
36 35 content_tag('div', content, :id => "journal-#{journal.id}-notes", :class => css_classes)
37 36 end
38 37
39 38 def link_to_in_place_notes_editor(text, field_id, url, options={})
40 39 onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
41 40 link_to text, '#', options.merge(:onclick => onclick)
42 41 end
43 42 end
@@ -1,559 +1,559
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class QueryColumn
19 19 attr_accessor :name, :sortable, :groupable, :default_order
20 20 include Redmine::I18n
21 21
22 22 def initialize(name, options={})
23 23 self.name = name
24 24 self.sortable = options[:sortable]
25 25 self.groupable = options[:groupable] || false
26 26 if groupable == true
27 27 self.groupable = name.to_s
28 28 end
29 29 self.default_order = options[:default_order]
30 30 end
31 31
32 32 def caption
33 33 l("field_#{name}")
34 34 end
35 35
36 36 # Returns true if the column is sortable, otherwise false
37 37 def sortable?
38 38 !sortable.nil?
39 39 end
40 40
41 41 def value(issue)
42 42 issue.send name
43 43 end
44 44 end
45 45
46 46 class QueryCustomFieldColumn < QueryColumn
47 47
48 48 def initialize(custom_field)
49 49 self.name = "cf_#{custom_field.id}".to_sym
50 50 self.sortable = custom_field.order_statement || false
51 51 if %w(list date bool int).include?(custom_field.field_format)
52 52 self.groupable = custom_field.order_statement
53 53 end
54 54 self.groupable ||= false
55 55 @cf = custom_field
56 56 end
57 57
58 58 def caption
59 59 @cf.name
60 60 end
61 61
62 62 def custom_field
63 63 @cf
64 64 end
65 65
66 66 def value(issue)
67 67 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
68 68 cv && @cf.cast_value(cv.value)
69 69 end
70 70 end
71 71
72 72 class Query < ActiveRecord::Base
73 73 class StatementInvalid < ::ActiveRecord::StatementInvalid
74 74 end
75 75
76 76 belongs_to :project
77 77 belongs_to :user
78 78 serialize :filters
79 79 serialize :column_names
80 80 serialize :sort_criteria, Array
81 81
82 82 attr_protected :project_id, :user_id
83 83
84 84 validates_presence_of :name, :on => :save
85 85 validates_length_of :name, :maximum => 255
86 86
87 87 @@operators = { "=" => :label_equals,
88 88 "!" => :label_not_equals,
89 89 "o" => :label_open_issues,
90 90 "c" => :label_closed_issues,
91 91 "!*" => :label_none,
92 92 "*" => :label_all,
93 93 ">=" => :label_greater_or_equal,
94 94 "<=" => :label_less_or_equal,
95 95 "<t+" => :label_in_less_than,
96 96 ">t+" => :label_in_more_than,
97 97 "t+" => :label_in,
98 98 "t" => :label_today,
99 99 "w" => :label_this_week,
100 100 ">t-" => :label_less_than_ago,
101 101 "<t-" => :label_more_than_ago,
102 102 "t-" => :label_ago,
103 103 "~" => :label_contains,
104 104 "!~" => :label_not_contains }
105 105
106 106 cattr_reader :operators
107 107
108 108 @@operators_by_filter_type = { :list => [ "=", "!" ],
109 109 :list_status => [ "o", "=", "!", "c", "*" ],
110 110 :list_optional => [ "=", "!", "!*", "*" ],
111 111 :list_subprojects => [ "*", "!*", "=" ],
112 112 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
113 113 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
114 114 :string => [ "=", "~", "!", "!~" ],
115 115 :text => [ "~", "!~" ],
116 116 :integer => [ "=", ">=", "<=", "!*", "*" ] }
117 117
118 118 cattr_reader :operators_by_filter_type
119 119
120 120 @@available_columns = [
121 121 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
122 122 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
123 123 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
124 124 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
125 125 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
126 126 QueryColumn.new(:author),
127 127 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
128 128 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
129 129 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
130 130 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
131 131 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
132 132 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
133 133 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
134 134 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
135 135 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
136 136 ]
137 137 cattr_reader :available_columns
138 138
139 139 def initialize(attributes = nil)
140 140 super attributes
141 141 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
142 142 end
143 143
144 144 def after_initialize
145 145 # Store the fact that project is nil (used in #editable_by?)
146 146 @is_for_all = project.nil?
147 147 end
148 148
149 149 def validate
150 150 filters.each_key do |field|
151 151 errors.add label_for(field), :blank unless
152 152 # filter requires one or more values
153 153 (values_for(field) and !values_for(field).first.blank?) or
154 154 # filter doesn't require any value
155 155 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
156 156 end if filters
157 157 end
158 158
159 159 def editable_by?(user)
160 160 return false unless user
161 161 # Admin can edit them all and regular users can edit their private queries
162 162 return true if user.admin? || (!is_public && self.user_id == user.id)
163 163 # Members can not edit public queries that are for all project (only admin is allowed to)
164 164 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
165 165 end
166 166
167 167 def available_filters
168 168 return @available_filters if @available_filters
169 169
170 170 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
171 171
172 172 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
173 173 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
174 174 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
175 175 "subject" => { :type => :text, :order => 8 },
176 176 "created_on" => { :type => :date_past, :order => 9 },
177 177 "updated_on" => { :type => :date_past, :order => 10 },
178 178 "start_date" => { :type => :date, :order => 11 },
179 179 "due_date" => { :type => :date, :order => 12 },
180 180 "estimated_hours" => { :type => :integer, :order => 13 },
181 181 "done_ratio" => { :type => :integer, :order => 14 }}
182 182
183 183 user_values = []
184 184 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
185 185 if project
186 186 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
187 187 else
188 188 # members of the user's projects
189 189 # OPTIMIZE: Is selecting from users per project (N+1)
190 190 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
191 191 end
192 192 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
193 193 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
194 194
195 195 if User.current.logged?
196 196 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
197 197 end
198 198
199 199 if project
200 200 # project specific filters
201 201 unless @project.issue_categories.empty?
202 202 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
203 203 end
204 204 unless @project.shared_versions.empty?
205 205 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
206 206 end
207 207 unless @project.descendants.active.empty?
208 208 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
209 209 end
210 210 add_custom_fields_filters(@project.all_issue_custom_fields)
211 211 else
212 212 # global filters for cross project issue list
213 213 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
214 214 end
215 215 @available_filters
216 216 end
217 217
218 218 def add_filter(field, operator, values)
219 219 # values must be an array
220 220 return unless values and values.is_a? Array # and !values.first.empty?
221 221 # check if field is defined as an available filter
222 222 if available_filters.has_key? field
223 223 filter_options = available_filters[field]
224 224 # check if operator is allowed for that filter
225 225 #if @@operators_by_filter_type[filter_options[:type]].include? operator
226 226 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
227 227 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
228 228 #end
229 229 filters[field] = {:operator => operator, :values => values }
230 230 end
231 231 end
232 232
233 233 def add_short_filter(field, expression)
234 234 return unless expression
235 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
235 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
236 236 add_filter field, (parms[0] || "="), [parms[1] || ""]
237 237 end
238 238
239 239 def has_filter?(field)
240 240 filters and filters[field]
241 241 end
242 242
243 243 def operator_for(field)
244 244 has_filter?(field) ? filters[field][:operator] : nil
245 245 end
246 246
247 247 def values_for(field)
248 248 has_filter?(field) ? filters[field][:values] : nil
249 249 end
250 250
251 251 def label_for(field)
252 252 label = available_filters[field][:name] if available_filters.has_key?(field)
253 253 label ||= field.gsub(/\_id$/, "")
254 254 end
255 255
256 256 def available_columns
257 257 return @available_columns if @available_columns
258 258 @available_columns = Query.available_columns
259 259 @available_columns += (project ?
260 260 project.all_issue_custom_fields :
261 261 IssueCustomField.find(:all)
262 262 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
263 263 end
264 264
265 265 # Returns an array of columns that can be used to group the results
266 266 def groupable_columns
267 267 available_columns.select {|c| c.groupable}
268 268 end
269 269
270 270 def columns
271 271 if has_default_columns?
272 272 available_columns.select do |c|
273 273 # Adds the project column by default for cross-project lists
274 274 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
275 275 end
276 276 else
277 277 # preserve the column_names order
278 278 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
279 279 end
280 280 end
281 281
282 282 def column_names=(names)
283 283 if names
284 284 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
285 285 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
286 286 # Set column_names to nil if default columns
287 287 if names.map(&:to_s) == Setting.issue_list_default_columns
288 288 names = nil
289 289 end
290 290 end
291 291 write_attribute(:column_names, names)
292 292 end
293 293
294 294 def has_column?(column)
295 295 column_names && column_names.include?(column.name)
296 296 end
297 297
298 298 def has_default_columns?
299 299 column_names.nil? || column_names.empty?
300 300 end
301 301
302 302 def sort_criteria=(arg)
303 303 c = []
304 304 if arg.is_a?(Hash)
305 305 arg = arg.keys.sort.collect {|k| arg[k]}
306 306 end
307 307 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
308 308 write_attribute(:sort_criteria, c)
309 309 end
310 310
311 311 def sort_criteria
312 312 read_attribute(:sort_criteria) || []
313 313 end
314 314
315 315 def sort_criteria_key(arg)
316 316 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
317 317 end
318 318
319 319 def sort_criteria_order(arg)
320 320 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
321 321 end
322 322
323 323 # Returns the SQL sort order that should be prepended for grouping
324 324 def group_by_sort_order
325 325 if grouped? && (column = group_by_column)
326 326 column.sortable.is_a?(Array) ?
327 327 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
328 328 "#{column.sortable} #{column.default_order}"
329 329 end
330 330 end
331 331
332 332 # Returns true if the query is a grouped query
333 333 def grouped?
334 334 !group_by.blank?
335 335 end
336 336
337 337 def group_by_column
338 338 groupable_columns.detect {|c| c.name.to_s == group_by}
339 339 end
340 340
341 341 def group_by_statement
342 342 group_by_column.groupable
343 343 end
344 344
345 345 def project_statement
346 346 project_clauses = []
347 347 if project && !@project.descendants.active.empty?
348 348 ids = [project.id]
349 349 if has_filter?("subproject_id")
350 350 case operator_for("subproject_id")
351 351 when '='
352 352 # include the selected subprojects
353 353 ids += values_for("subproject_id").each(&:to_i)
354 354 when '!*'
355 355 # main project only
356 356 else
357 357 # all subprojects
358 358 ids += project.descendants.collect(&:id)
359 359 end
360 360 elsif Setting.display_subprojects_issues?
361 361 ids += project.descendants.collect(&:id)
362 362 end
363 363 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
364 364 elsif project
365 365 project_clauses << "#{Project.table_name}.id = %d" % project.id
366 366 end
367 367 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
368 368 project_clauses.join(' AND ')
369 369 end
370 370
371 371 def statement
372 372 # filters clauses
373 373 filters_clauses = []
374 374 filters.each_key do |field|
375 375 next if field == "subproject_id"
376 376 v = values_for(field).clone
377 377 next unless v and !v.empty?
378 378 operator = operator_for(field)
379 379
380 380 # "me" value subsitution
381 381 if %w(assigned_to_id author_id watcher_id).include?(field)
382 382 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
383 383 end
384 384
385 385 sql = ''
386 386 if field =~ /^cf_(\d+)$/
387 387 # custom field
388 388 db_table = CustomValue.table_name
389 389 db_field = 'value'
390 390 is_custom_filter = true
391 391 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
392 392 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
393 393 elsif field == 'watcher_id'
394 394 db_table = Watcher.table_name
395 395 db_field = 'user_id'
396 396 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
397 397 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
398 398 else
399 399 # regular field
400 400 db_table = Issue.table_name
401 401 db_field = field
402 402 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
403 403 end
404 404 filters_clauses << sql
405 405
406 406 end if filters and valid?
407 407
408 408 (filters_clauses << project_statement).join(' AND ')
409 409 end
410 410
411 411 # Returns the issue count
412 412 def issue_count
413 413 Issue.count(:include => [:status, :project], :conditions => statement)
414 414 rescue ::ActiveRecord::StatementInvalid => e
415 415 raise StatementInvalid.new(e.message)
416 416 end
417 417
418 418 # Returns the issue count by group or nil if query is not grouped
419 419 def issue_count_by_group
420 420 r = nil
421 421 if grouped?
422 422 begin
423 423 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
424 424 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
425 425 rescue ActiveRecord::RecordNotFound
426 426 r = {nil => issue_count}
427 427 end
428 428 c = group_by_column
429 429 if c.is_a?(QueryCustomFieldColumn)
430 430 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
431 431 end
432 432 end
433 433 r
434 434 rescue ::ActiveRecord::StatementInvalid => e
435 435 raise StatementInvalid.new(e.message)
436 436 end
437 437
438 438 # Returns the issues
439 439 # Valid options are :order, :offset, :limit, :include, :conditions
440 440 def issues(options={})
441 441 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
442 442 order_option = nil if order_option.blank?
443 443
444 444 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
445 445 :conditions => Query.merge_conditions(statement, options[:conditions]),
446 446 :order => order_option,
447 447 :limit => options[:limit],
448 448 :offset => options[:offset]
449 449 rescue ::ActiveRecord::StatementInvalid => e
450 450 raise StatementInvalid.new(e.message)
451 451 end
452 452
453 453 # Returns the journals
454 454 # Valid options are :order, :offset, :limit
455 455 def journals(options={})
456 456 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
457 457 :conditions => statement,
458 458 :order => options[:order],
459 459 :limit => options[:limit],
460 460 :offset => options[:offset]
461 461 rescue ::ActiveRecord::StatementInvalid => e
462 462 raise StatementInvalid.new(e.message)
463 463 end
464 464
465 465 # Returns the versions
466 466 # Valid options are :conditions
467 467 def versions(options={})
468 468 Version.find :all, :include => :project,
469 469 :conditions => Query.merge_conditions(project_statement, options[:conditions])
470 470 rescue ::ActiveRecord::StatementInvalid => e
471 471 raise StatementInvalid.new(e.message)
472 472 end
473 473
474 474 private
475 475
476 476 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
477 477 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
478 478 sql = ''
479 479 case operator
480 480 when "="
481 481 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
482 482 when "!"
483 483 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
484 484 when "!*"
485 485 sql = "#{db_table}.#{db_field} IS NULL"
486 486 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
487 487 when "*"
488 488 sql = "#{db_table}.#{db_field} IS NOT NULL"
489 489 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
490 490 when ">="
491 491 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
492 492 when "<="
493 493 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
494 494 when "o"
495 495 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
496 496 when "c"
497 497 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
498 498 when ">t-"
499 499 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
500 500 when "<t-"
501 501 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
502 502 when "t-"
503 503 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
504 504 when ">t+"
505 505 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
506 506 when "<t+"
507 507 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
508 508 when "t+"
509 509 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
510 510 when "t"
511 511 sql = date_range_clause(db_table, db_field, 0, 0)
512 512 when "w"
513 513 from = l(:general_first_day_of_week) == '7' ?
514 514 # week starts on sunday
515 515 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
516 516 # week starts on monday (Rails default)
517 517 Time.now.at_beginning_of_week
518 518 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
519 519 when "~"
520 520 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
521 521 when "!~"
522 522 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
523 523 end
524 524
525 525 return sql
526 526 end
527 527
528 528 def add_custom_fields_filters(custom_fields)
529 529 @available_filters ||= {}
530 530
531 531 custom_fields.select(&:is_filter?).each do |field|
532 532 case field.field_format
533 533 when "text"
534 534 options = { :type => :text, :order => 20 }
535 535 when "list"
536 536 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
537 537 when "date"
538 538 options = { :type => :date, :order => 20 }
539 539 when "bool"
540 540 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
541 541 else
542 542 options = { :type => :string, :order => 20 }
543 543 end
544 544 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
545 545 end
546 546 end
547 547
548 548 # Returns a SQL clause for a date or datetime field.
549 549 def date_range_clause(table, field, from, to)
550 550 s = []
551 551 if from
552 552 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
553 553 end
554 554 if to
555 555 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
556 556 end
557 557 s.join(' AND ')
558 558 end
559 559 end
@@ -1,34 +1,35
1 1 <div class="contextual">
2 2 <%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %>
3 3 </div>
4 4
5 5 <h2><%=l(:label_auth_source_plural)%></h2>
6 6
7 7 <table class="list">
8 8 <thead><tr>
9 9 <th><%=l(:field_name)%></th>
10 10 <th><%=l(:field_type)%></th>
11 11 <th><%=l(:field_host)%></th>
12 12 <th><%=l(:label_user_plural)%></th>
13 13 <th></th>
14 14 </tr></thead>
15 15 <tbody>
16 16 <% for source in @auth_sources %>
17 17 <tr class="<%= cycle("odd", "even") %>">
18 18 <td><%= link_to source.name, :action => 'edit', :id => source%></td>
19 19 <td align="center"><%= source.auth_method_name %></td>
20 20 <td align="center"><%= source.host %></td>
21 21 <td align="center"><%= source.users.count %></td>
22 22 <td class="buttons">
23 23 <%= link_to l(:button_test), :action => 'test_connection', :id => source %>
24 24 <%= link_to l(:button_delete), { :action => 'destroy', :id => source },
25 :method => :post,
25 26 :confirm => l(:text_are_you_sure),
26 27 :class => 'icon icon-del',
27 28 :disabled => source.users.any? %>
28 29 </td>
29 30 </tr>
30 31 <% end %>
31 32 </tbody>
32 33 </table>
33 34
34 35 <p class="pagination"><%= pagination_links_full @auth_source_pages %></p>
@@ -1,16 +1,17
1 1 <% reply_links = authorize_for('issues', 'edit') -%>
2 2 <% for journal in journals %>
3 3 <div id="change-<%= journal.id %>" class="journal">
4 4 <h4><div style="float:right;"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
5 <%= avatar(journal.user, :size => "24") %>
5 6 <%= content_tag('a', '', :name => "note-#{journal.indice}")%>
6 7 <%= authoring journal.created_on, journal.user, :label => :label_updated_time_by %></h4>
7 <%= avatar(journal.user, :size => "32") %>
8
8 9 <ul>
9 10 <% for detail in journal.details %>
10 11 <li><%= show_detail(detail) %></li>
11 12 <% end %>
12 13 </ul>
13 14 <%= render_notes(journal, :reply_links => reply_links) unless journal.notes.blank? %>
14 15 </div>
15 16 <%= call_hook(:view_issues_history_journal_bottom, { :journal => journal }) %>
16 17 <% end %>
@@ -1,115 +1,115
1 1 <%= render :partial => 'action_menu' %>
2 2
3 3 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
4 4
5 5 <div class="<%= @issue.css_classes %> details">
6 <%= avatar(@issue.author, :size => "64") %>
6 <%= avatar(@issue.author, :size => "50") %>
7 7 <h3><%=h @issue.subject %></h3>
8 8 <p class="author">
9 9 <%= authoring @issue.created_on, @issue.author %>.
10 10 <% if @issue.created_on != @issue.updated_on %>
11 11 <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
12 12 <% end %>
13 13 </p>
14 14
15 15 <table class="attributes">
16 16 <tr>
17 17 <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
18 18 <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
19 19 </tr>
20 20 <tr>
21 21 <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
22 22 <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
23 23 </tr>
24 24 <tr>
25 25 <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
26 26 <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
27 27 </tr>
28 28 <tr>
29 29 <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
30 30 <% if User.current.allowed_to?(:view_time_entries, @project) %>
31 31 <th class="spent-time"><%=l(:label_spent_time)%>:</th>
32 32 <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
33 33 <% end %>
34 34 </tr>
35 35 <tr>
36 36 <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
37 37 <% if @issue.estimated_hours %>
38 38 <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
39 39 <% end %>
40 40 </tr>
41 41 <%= render_custom_fields_rows(@issue) %>
42 42 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
43 43 </table>
44 44 <hr />
45 45
46 46 <div class="contextual">
47 47 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:action => 'reply', :id => @issue} }, :class => 'icon icon-comment') unless @issue.description.blank? %>
48 48 </div>
49 49
50 50 <p><strong><%=l(:field_description)%></strong></p>
51 51 <div class="wiki">
52 52 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
53 53 </div>
54 54
55 55 <%= link_to_attachments @issue %>
56 56
57 57 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
58 58
59 59 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
60 60 <hr />
61 61 <div id="relations">
62 62 <%= render :partial => 'relations' %>
63 63 </div>
64 64 <% end %>
65 65
66 66 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
67 67 (@issue.watchers.any? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
68 68 <hr />
69 69 <div id="watchers">
70 70 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
71 71 </div>
72 72 <% end %>
73 73
74 74 </div>
75 75
76 76 <% if @changesets.any? && User.current.allowed_to?(:view_changesets, @project) %>
77 77 <div id="issue-changesets">
78 78 <h3><%=l(:label_associated_revisions)%></h3>
79 79 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
80 80 </div>
81 81 <% end %>
82 82
83 83 <% if @journals.any? %>
84 84 <div id="history">
85 85 <h3><%=l(:label_history)%></h3>
86 86 <%= render :partial => 'history', :locals => { :journals => @journals } %>
87 87 </div>
88 88 <% end %>
89 89
90 90 <%= render :partial => 'action_menu', :locals => {:replace_watcher => 'watcher2' } %>
91 91
92 92 <div style="clear: both;"></div>
93 93
94 94 <% if authorize_for('issues', 'edit') %>
95 95 <div id="update" style="display:none;">
96 96 <h3><%= l(:button_update) %></h3>
97 97 <%= render :partial => 'edit' %>
98 98 </div>
99 99 <% end %>
100 100
101 101 <% other_formats_links do |f| %>
102 102 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
103 103 <%= f.link_to 'PDF' %>
104 104 <% end %>
105 105
106 106 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
107 107
108 108 <% content_for :sidebar do %>
109 109 <%= render :partial => 'issues/sidebar' %>
110 110 <% end %>
111 111
112 112 <% content_for :header_tags do %>
113 113 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
114 114 <%= stylesheet_link_tag 'scm' %>
115 115 <% end %>
@@ -1,64 +1,65
1 1 <%= breadcrumb link_to(l(:label_board_plural), {:controller => 'boards', :action => 'index', :project_id => @project}),
2 2 link_to(h(@board.name), {:controller => 'boards', :action => 'show', :project_id => @project, :id => @board}) %>
3 3
4 4 <div class="contextual">
5 5 <%= watcher_tag(@topic, User.current) %>
6 6 <%= link_to_remote_if_authorized l(:button_quote), { :url => {:action => 'quote', :id => @topic} }, :class => 'icon icon-comment' %>
7 7 <%= link_to(l(:button_edit), {:action => 'edit', :id => @topic}, :class => 'icon icon-edit') if @message.editable_by?(User.current) %>
8 8 <%= link_to(l(:button_delete), {:action => 'destroy', :id => @topic}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') if @message.destroyable_by?(User.current) %>
9 9 </div>
10 10
11 <h2><%=h @topic.subject %></h2>
11 <h2><%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %></h2>
12 12
13 13 <div class="message">
14 14 <p><span class="author"><%= authoring @topic.created_on, @topic.author %></span></p>
15 15 <div class="wiki">
16 16 <%= textilizable(@topic.content, :attachments => @topic.attachments) %>
17 17 </div>
18 18 <%= link_to_attachments @topic, :author => false %>
19 19 </div>
20 20 <br />
21 21
22 22 <% unless @replies.empty? %>
23 23 <h3 class="comments"><%= l(:label_reply_plural) %></h3>
24 24 <% @replies.each do |message| %>
25 25 <div class="message reply" id="<%= "message-#{message.id}" %>">
26 26 <div class="contextual">
27 27 <%= link_to_remote_if_authorized image_tag('comment.png'), { :url => {:action => 'quote', :id => message} }, :title => l(:button_quote) %>
28 28 <%= link_to(image_tag('edit.png'), {:action => 'edit', :id => message}, :title => l(:button_edit)) if message.editable_by?(User.current) %>
29 29 <%= link_to(image_tag('delete.png'), {:action => 'destroy', :id => message}, :method => :post, :confirm => l(:text_are_you_sure), :title => l(:button_delete)) if message.destroyable_by?(User.current) %>
30 30 </div>
31 31 <h4>
32 <%= avatar(message.author, :size => "24") %>
32 33 <%= link_to h(message.subject), { :controller => 'messages', :action => 'show', :board_id => @board, :id => @topic, :anchor => "message-#{message.id}" } %>
33 34 -
34 35 <%= authoring message.created_on, message.author %>
35 36 </h4>
36 37 <div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div>
37 38 <%= link_to_attachments message, :author => false %>
38 39 </div>
39 40 <% end %>
40 41 <% end %>
41 42
42 43 <% if !@topic.locked? && authorize_for('messages', 'reply') %>
43 44 <p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p>
44 45 <div id="reply" style="display:none;">
45 46 <% form_for :reply, @reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
46 47 <%= render :partial => 'form', :locals => {:f => f, :replying => true} %>
47 48 <%= submit_tag l(:button_submit) %>
48 49 <%= link_to_remote l(:label_preview),
49 50 { :url => { :controller => 'messages', :action => 'preview', :board_id => @board },
50 51 :method => 'post',
51 52 :update => 'preview',
52 53 :with => "Form.serialize('message-form')",
53 54 :complete => "Element.scrollTo('preview')"
54 55 }, :accesskey => accesskey(:preview) %>
55 56 <% end %>
56 57 <div id="preview" class="wiki"></div>
57 58 </div>
58 59 <% end %>
59 60
60 61 <% content_for :header_tags do %>
61 62 <%= stylesheet_link_tag 'scm' %>
62 63 <% end %>
63 64
64 65 <% html_title h(@topic.subject) %>
@@ -1,65 +1,65
1 1 <div class="contextual">
2 2 <%= link_to_if_authorized l(:button_edit),
3 3 {:controller => 'news', :action => 'edit', :id => @news},
4 4 :class => 'icon icon-edit',
5 5 :accesskey => accesskey(:edit),
6 6 :onclick => 'Element.show("edit-news"); return false;' %>
7 7 <%= link_to_if_authorized l(:button_delete), {:controller => 'news', :action => 'destroy', :id => @news}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 8 </div>
9 9
10 <h2><%=h @news.title %></h2>
10 <h2><%= avatar(@news.author, :size => "24") %><%=h @news.title %></h2>
11 11
12 12 <% if authorize_for('news', 'edit') %>
13 13 <div id="edit-news" style="display:none;">
14 14 <% labelled_tabular_form_for :news, @news, :url => { :action => "edit", :id => @news },
15 15 :html => { :id => 'news-form' } do |f| %>
16 16 <%= render :partial => 'form', :locals => { :f => f } %>
17 17 <%= submit_tag l(:button_save) %>
18 18 <%= link_to_remote l(:label_preview),
19 19 { :url => { :controller => 'news', :action => 'preview', :project_id => @project },
20 20 :method => 'post',
21 21 :update => 'preview',
22 22 :with => "Form.serialize('news-form')"
23 23 }, :accesskey => accesskey(:preview) %> |
24 24 <%= link_to l(:button_cancel), "#", :onclick => 'Element.hide("edit-news"); return false;' %>
25 25 <% end %>
26 26 <div id="preview" class="wiki"></div>
27 27 </div>
28 28 <% end %>
29 29
30 30 <p><em><% unless @news.summary.blank? %><%=h @news.summary %><br /><% end %>
31 31 <span class="author"><%= authoring @news.created_on, @news.author %></span></em></p>
32 32 <div class="wiki">
33 33 <%= textilizable(@news.description) %>
34 34 </div>
35 35 <br />
36 36
37 37 <div id="comments" style="margin-bottom:16px;">
38 38 <h3 class="comments"><%= l(:label_comment_plural) %></h3>
39 39 <% @comments.each do |comment| %>
40 40 <% next if comment.new_record? %>
41 41 <div class="contextual">
42 42 <%= link_to_if_authorized image_tag('delete.png'), {:controller => 'news', :action => 'destroy_comment', :id => @news, :comment_id => comment},
43 43 :confirm => l(:text_are_you_sure), :method => :post, :title => l(:button_delete) %>
44 44 </div>
45 <h4><%= authoring comment.created_on, comment.author %></h4>
45 <h4><%= avatar(comment.author, :size => "24") %><%= authoring comment.created_on, comment.author %></h4>
46 46 <%= textilizable(comment.comments) %>
47 47 <% end if @comments.any? %>
48 48 </div>
49 49
50 50 <% if authorize_for 'news', 'add_comment' %>
51 51 <p><%= toggle_link l(:label_comment_add), "add_comment_form", :focus => "comment_comments" %></p>
52 52 <% form_tag({:action => 'add_comment', :id => @news}, :id => "add_comment_form", :style => "display:none;") do %>
53 53 <div class="box">
54 54 <%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit' %>
55 55 <%= wikitoolbar_for 'comment_comments' %>
56 56 </div>
57 57 <p><%= submit_tag l(:button_add) %></p>
58 58 <% end %>
59 59 <% end %>
60 60
61 61 <% html_title @news.title -%>
62 62
63 63 <% content_for :header_tags do %>
64 64 <%= stylesheet_link_tag 'scm' %>
65 65 <% end %>
@@ -1,48 +1,53
1 1 <% if @statuses.empty? or rows.empty? %>
2 2 <p><i><%=l(:label_no_data)%></i></p>
3 3 <% else %>
4 4 <% col_width = 70 / (@statuses.length+3) %>
5 5 <table class="list">
6 6 <thead><tr>
7 7 <th style="width:25%"></th>
8 8 <% for status in @statuses %>
9 9 <th style="width:<%= col_width %>%"><%= status.name %></th>
10 10 <% end %>
11 11 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_open_issues_plural)%></strong></th>
12 12 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_closed_issues_plural)%></strong></th>
13 13 <th align="center" style="width:<%= col_width %>%"><strong><%=l(:label_total)%></strong></th>
14 14 </tr></thead>
15 15 <tbody>
16 16 <% for row in rows %>
17 17 <tr class="<%= cycle("odd", "even") %>">
18 18 <td><%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
19 19 :set_filter => 1,
20 :subproject_id => '!*',
20 21 "#{field_name}" => row.id %></td>
21 22 <% for status in @statuses %>
22 23 <td align="center"><%= aggregate_link data, { field_name => row.id, "status_id" => status.id },
23 24 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
24 25 :set_filter => 1,
26 :subproject_id => '!*',
25 27 "status_id" => status.id,
26 28 "#{field_name}" => row.id %></td>
27 29 <% end %>
28 30 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
29 31 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
30 32 :set_filter => 1,
33 :subproject_id => '!*',
31 34 "#{field_name}" => row.id,
32 35 "status_id" => "o" %></td>
33 36 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
34 37 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
35 38 :set_filter => 1,
39 :subproject_id => '!*',
36 40 "#{field_name}" => row.id,
37 41 "status_id" => "c" %></td>
38 42 <td align="center"><%= aggregate_link data, { field_name => row.id },
39 43 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
40 44 :set_filter => 1,
45 :subproject_id => '!*',
41 46 "#{field_name}" => row.id,
42 47 "status_id" => "*" %></td>
43 48 </tr>
44 49 <% end %>
45 50 </tbody>
46 51 </table>
47 52 <% end
48 53 reset_cycle %> No newline at end of file
@@ -1,37 +1,41
1 1 <% if @statuses.empty? or rows.empty? %>
2 2 <p><i><%=l(:label_no_data)%></i></p>
3 3 <% else %>
4 4 <table class="list">
5 5 <thead><tr>
6 6 <th style="width:25%"></th>
7 7 <th align="center" style="width:25%"><%=l(:label_open_issues_plural)%></th>
8 8 <th align="center" style="width:25%"><%=l(:label_closed_issues_plural)%></th>
9 9 <th align="center" style="width:25%"><%=l(:label_total)%></th>
10 10 </tr></thead>
11 11 <tbody>
12 12 <% for row in rows %>
13 13 <tr class="<%= cycle("odd", "even") %>">
14 14 <td><%= link_to row.name, :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
15 15 :set_filter => 1,
16 :subproject_id => '!*',
16 17 "#{field_name}" => row.id %></td>
17 18 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 0 },
18 19 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
19 20 :set_filter => 1,
21 :subproject_id => '!*',
20 22 "#{field_name}" => row.id,
21 23 "status_id" => "o" %></td>
22 24 <td align="center"><%= aggregate_link data, { field_name => row.id, "closed" => 1 },
23 25 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
24 26 :set_filter => 1,
27 :subproject_id => '!*',
25 28 "#{field_name}" => row.id,
26 29 "status_id" => "c" %></td>
27 30 <td align="center"><%= aggregate_link data, { field_name => row.id },
28 31 :controller => 'issues', :action => 'index', :project_id => ((row.is_a?(Project) ? row : @project)),
29 32 :set_filter => 1,
33 :subproject_id => '!*',
30 34 "#{field_name}" => row.id,
31 35 "status_id" => "*" %></td>
32 36 </tr>
33 37 <% end %>
34 38 </tbody>
35 39 </table>
36 40 <% end
37 41 reset_cycle %> No newline at end of file
@@ -1,70 +1,70
1 1 <div class="contextual">
2 2 <%= link_to(l(:button_edit), {:controller => 'users', :action => 'edit', :id => @user}, :class => 'icon icon-edit') if User.current.admin? %>
3 3 </div>
4 4
5 <h2><%= avatar @user %> <%=h @user.name %></h2>
5 <h2><%= avatar @user, :size => "50" %> <%=h @user.name %></h2>
6 6
7 7 <div class="splitcontentleft">
8 8 <ul>
9 9 <% unless @user.pref.hide_mail %>
10 10 <li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li>
11 11 <% end %>
12 12 <% for custom_value in @custom_values %>
13 13 <% if !custom_value.value.blank? %>
14 14 <li><%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 15 <% end %>
16 16 <% end %>
17 17 <li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li>
18 18 <% unless @user.last_login_on.nil? %>
19 19 <li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li>
20 20 <% end %>
21 21 </ul>
22 22
23 23 <% unless @memberships.empty? %>
24 24 <h3><%=l(:label_project_plural)%></h3>
25 25 <ul>
26 26 <% for membership in @memberships %>
27 27 <li><%= link_to(h(membership.project.name), :controller => 'projects', :action => 'show', :id => membership.project) %>
28 28 (<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)</li>
29 29 <% end %>
30 30 </ul>
31 31 <% end %>
32 32 <%= call_hook :view_account_left_bottom, :user => @user %>
33 33 </div>
34 34
35 35 <div class="splitcontentright">
36 36
37 37 <% unless @events_by_day.empty? %>
38 38 <h3><%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %></h3>
39 39
40 40 <p>
41 41 <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %>
42 42 </p>
43 43
44 44 <div id="activity">
45 45 <% @events_by_day.keys.sort.reverse.each do |day| %>
46 46 <h4><%= format_activity_day(day) %></h4>
47 47 <dl>
48 48 <% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
49 49 <dt class="<%= e.event_type %>">
50 50 <span class="time"><%= format_time(e.event_datetime, false) %></span>
51 51 <%= content_tag('span', h(e.project), :class => 'project') %>
52 52 <%= link_to format_activity_title(e.event_title), e.event_url %></dt>
53 53 <dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd>
54 54 <% end -%>
55 55 </dl>
56 56 <% end -%>
57 57 </div>
58 58
59 59 <% other_formats_links do |f| %>
60 60 <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :key => User.current.rss_key} %>
61 61 <% end %>
62 62
63 63 <% content_for :header_tags do %>
64 64 <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :user_id => @user, :format => :atom, :key => User.current.rss_key) %>
65 65 <% end %>
66 66 <% end %>
67 67 <%= call_hook :view_account_right_bottom, :user => @user %>
68 68 </div>
69 69
70 70 <% html_title @user.name %>
@@ -1,848 +1,850
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 8
9 9 /***** Layout *****/
10 10 #wrapper {background: white;}
11 11
12 12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 13 #top-menu ul {margin: 0; padding: 0;}
14 14 #top-menu li {
15 15 float:left;
16 16 list-style-type:none;
17 17 margin: 0px 0px 0px 0px;
18 18 padding: 0px 0px 0px 0px;
19 19 white-space:nowrap;
20 20 }
21 21 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 23
24 24 #account {float:right;}
25 25
26 26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 27 #header a {color:#f8f8f8;}
28 28 #header h1 a.ancestor { font-size: 80%; }
29 29 #quick-search {float:right;}
30 30
31 31 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 32 #main-menu ul {margin: 0; padding: 0;}
33 33 #main-menu li {
34 34 float:left;
35 35 list-style-type:none;
36 36 margin: 0px 2px 0px 0px;
37 37 padding: 0px 0px 0px 0px;
38 38 white-space:nowrap;
39 39 }
40 40 #main-menu li a {
41 41 display: block;
42 42 color: #fff;
43 43 text-decoration: none;
44 44 font-weight: bold;
45 45 margin: 0;
46 46 padding: 4px 10px 4px 10px;
47 47 }
48 48 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 49 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50 50
51 51 #admin-menu ul {margin: 0; padding: 0;}
52 52 #admin-menu li {margin: 0; padding: 0 0 12px 0; list-style-type:none;}
53 53
54 54 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
55 55 #admin-menu a.projects { background-image: url(../images/projects.png); }
56 56 #admin-menu a.users { background-image: url(../images/user.png); }
57 57 #admin-menu a.groups { background-image: url(../images/group.png); }
58 58 #admin-menu a.roles { background-image: url(../images/database_key.png); }
59 59 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
60 60 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
61 61 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
62 62 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
63 63 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
64 64 #admin-menu a.settings { background-image: url(../images/changeset.png); }
65 65 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
66 66 #admin-menu a.info { background-image: url(../images/help.png); }
67 67
68 68 #main {background-color:#EEEEEE;}
69 69
70 70 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
71 71 * html #sidebar{ width: 17%; }
72 72 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
73 73 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
74 74 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
75 75
76 76 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
77 77 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
78 78 html>body #content { min-height: 600px; overflow: auto; }
79 79 * html body #content { height: 600px; } /* IE */
80 80
81 81 #main.nosidebar #sidebar{ display: none; }
82 82 #main.nosidebar #content{ width: auto; border-right: 0; }
83 83
84 84 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
85 85
86 86 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
87 87 #login-form table td {padding: 6px;}
88 88 #login-form label {font-weight: bold;}
89 89 #login-form input#username, #login-form input#password { width: 300px; }
90 90
91 91 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
92 92
93 93 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
94 94
95 95 /***** Links *****/
96 96 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
97 97 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
98 98 a img{ border: 0; }
99 99
100 100 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
101 101
102 102 /***** Tables *****/
103 103 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
104 104 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
105 105 table.list td { vertical-align: top; }
106 106 table.list td.id { width: 2%; text-align: center;}
107 107 table.list td.checkbox { width: 15px; padding: 0px;}
108 108 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
109 109 table.list td.buttons a { padding-right: 0.6em; }
110 110
111 111 tr.project td.name a { padding-left: 16px; white-space:nowrap; }
112 112 tr.project.parent td.name a { background: url('../images/bullet_toggle_minus.png') no-repeat; }
113 113
114 114 tr.issue { text-align: center; white-space: nowrap; }
115 115 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
116 116 tr.issue td.subject { text-align: left; }
117 117 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
118 118
119 119 tr.entry { border: 1px solid #f8f8f8; }
120 120 tr.entry td { white-space: nowrap; }
121 121 tr.entry td.filename { width: 30%; }
122 122 tr.entry td.size { text-align: right; font-size: 90%; }
123 123 tr.entry td.revision, tr.entry td.author { text-align: center; }
124 124 tr.entry td.age { text-align: right; }
125 125 tr.entry.file td.filename a { margin-left: 16px; }
126 126
127 127 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
128 128 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
129 129
130 130 tr.changeset td.author { text-align: center; width: 15%; }
131 131 tr.changeset td.committed_on { text-align: center; width: 15%; }
132 132
133 133 table.files tr.file td { text-align: center; }
134 134 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
135 135 table.files tr.file td.digest { font-size: 80%; }
136 136
137 137 table.members td.roles, table.memberships td.roles { width: 45%; }
138 138
139 139 tr.message { height: 2.6em; }
140 140 tr.message td.last_message { font-size: 80%; }
141 141 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
142 142 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
143 143
144 144 tr.version.closed, tr.version.closed a { color: #999; }
145 145 tr.version td.name { padding-left: 20px; }
146 146 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
147 147 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; }
148 148
149 149 tr.user td { width:13%; }
150 150 tr.user td.email { width:18%; }
151 151 tr.user td { white-space: nowrap; }
152 152 tr.user.locked, tr.user.registered { color: #aaa; }
153 153 tr.user.locked a, tr.user.registered a { color: #aaa; }
154 154
155 155 tr.time-entry { text-align: center; white-space: nowrap; }
156 156 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
157 157 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
158 158 td.hours .hours-dec { font-size: 0.9em; }
159 159
160 160 table.plugins td { vertical-align: middle; }
161 161 table.plugins td.configure { text-align: right; padding-right: 1em; }
162 162 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
163 163 table.plugins span.description { display: block; font-size: 0.9em; }
164 164 table.plugins span.url { display: block; font-size: 0.9em; }
165 165
166 166 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
167 167 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
168 168
169 169 table.list tbody tr:hover { background-color:#ffffdd; }
170 170 table.list tbody tr.group:hover { background-color:inherit; }
171 171 table td {padding:2px;}
172 172 table p {margin:0;}
173 173 .odd {background-color:#f6f7f8;}
174 174 .even {background-color: #fff;}
175 175
176 176 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
177 177 a.sort.asc { background-image: url(../images/sort_asc.png); }
178 178 a.sort.desc { background-image: url(../images/sort_desc.png); }
179 179
180 180 table.attributes { width: 100% }
181 181 table.attributes th { vertical-align: top; text-align: left; }
182 182 table.attributes td { vertical-align: top; }
183 183
184 184 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
185 185
186 186 td.center {text-align:center;}
187 187
188 188 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
189 189
190 190 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
191 191 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
192 192 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
193 193 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
194 194
195 195 .highlight { background-color: #FCFD8D;}
196 196 .highlight.token-1 { background-color: #faa;}
197 197 .highlight.token-2 { background-color: #afa;}
198 198 .highlight.token-3 { background-color: #aaf;}
199 199
200 200 .box{
201 201 padding:6px;
202 202 margin-bottom: 10px;
203 203 background-color:#f6f6f6;
204 204 color:#505050;
205 205 line-height:1.5em;
206 206 border: 1px solid #e4e4e4;
207 207 }
208 208
209 209 div.square {
210 210 border: 1px solid #999;
211 211 float: left;
212 212 margin: .3em .4em 0 .4em;
213 213 overflow: hidden;
214 214 width: .6em; height: .6em;
215 215 }
216 216 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
217 217 .contextual input, .contextual select {font-size:0.9em;}
218 218 .message .contextual { margin-top: 0; }
219 219
220 220 .splitcontentleft{float:left; width:49%;}
221 221 .splitcontentright{float:right; width:49%;}
222 222 form {display: inline;}
223 223 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
224 224 fieldset {border: 1px solid #e4e4e4; margin:0;}
225 225 legend {color: #484848;}
226 226 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
227 227 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
228 228 blockquote blockquote { margin-left: 0;}
229 229 acronym { border-bottom: 1px dotted; cursor: help; }
230 230 textarea.wiki-edit { width: 99%; }
231 231 li p {margin-top: 0;}
232 232 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
233 233 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
234 234 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
235 235 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
236 236
237 237 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
238 238 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
239 239 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
240 240
241 241 fieldset#date-range p { margin: 2px 0 2px 0; }
242 242 fieldset#filters table { border-collapse: collapse; }
243 243 fieldset#filters table td { padding: 0; vertical-align: middle; }
244 244 fieldset#filters tr.filter { height: 2em; }
245 245 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
246 246 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
247 247
248 248 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
249 249 div#issue-changesets .changeset { padding: 4px;}
250 250 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
251 251 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
252 252
253 253 div#activity dl, #search-results { margin-left: 2em; }
254 254 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
255 255 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
256 256 div#activity dt.me .time { border-bottom: 1px solid #999; }
257 257 div#activity dt .time { color: #777; font-size: 80%; }
258 258 div#activity dd .description, #search-results dd .description { font-style: italic; }
259 259 div#activity span.project:after, #search-results span.project:after { content: " -"; }
260 260 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
261 261
262 262 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
263 263
264 264 div#search-results-counts {float:right;}
265 265 div#search-results-counts ul { margin-top: 0.5em; }
266 266 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
267 267
268 268 dt.issue { background-image: url(../images/ticket.png); }
269 269 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
270 270 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
271 271 dt.issue-note { background-image: url(../images/ticket_note.png); }
272 272 dt.changeset { background-image: url(../images/changeset.png); }
273 273 dt.news { background-image: url(../images/news.png); }
274 274 dt.message { background-image: url(../images/message.png); }
275 275 dt.reply { background-image: url(../images/comments.png); }
276 276 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
277 277 dt.attachment { background-image: url(../images/attachment.png); }
278 278 dt.document { background-image: url(../images/document.png); }
279 279 dt.project { background-image: url(../images/projects.png); }
280 280 dt.time-entry { background-image: url(../images/time.png); }
281 281
282 282 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
283 283
284 284 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
285 285 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
286 286 div#roadmap .wiki h1:first-child { display: none; }
287 287 div#roadmap .wiki h1 { font-size: 120%; }
288 288 div#roadmap .wiki h2 { font-size: 110%; }
289 289
290 290 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
291 291 div#version-summary fieldset { margin-bottom: 1em; }
292 292 div#version-summary .total-hours { text-align: right; }
293 293
294 294 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
295 295 table#time-report tbody tr { font-style: italic; color: #777; }
296 296 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
297 297 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
298 298 table#time-report .hours-dec { font-size: 0.9em; }
299 299
300 300 form#issue-form .attributes { margin-bottom: 8px; }
301 301 form#issue-form .attributes p { padding-top: 1px; padding-bottom: 2px; }
302 302 form#issue-form .attributes select { min-width: 30%; }
303 303
304 304 ul.projects { margin: 0; padding-left: 1em; }
305 305 ul.projects.root { margin: 0; padding: 0; }
306 306 ul.projects ul { border-left: 3px solid #e0e0e0; }
307 307 ul.projects li { list-style-type:none; }
308 308 ul.projects li.root { margin-bottom: 1em; }
309 309 ul.projects li.child { margin-top: 1em;}
310 310 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
311 311 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
312 312
313 313 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
314 314 #tracker_project_ids li { list-style-type:none; }
315 315
316 316 ul.properties {padding:0; font-size: 0.9em; color: #777;}
317 317 ul.properties li {list-style-type:none;}
318 318 ul.properties li span {font-style:italic;}
319 319
320 320 .total-hours { font-size: 110%; font-weight: bold; }
321 321 .total-hours span.hours-int { font-size: 120%; }
322 322
323 323 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
324 324 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
325 325
326 326 #workflow_copy_form select { width: 200px; }
327 327
328 328 .pagination {font-size: 90%}
329 329 p.pagination {margin-top:8px;}
330 330
331 331 /***** Tabular forms ******/
332 332 .tabular p{
333 333 margin: 0;
334 334 padding: 5px 0 8px 0;
335 335 padding-left: 180px; /*width of left column containing the label elements*/
336 336 height: 1%;
337 337 clear:left;
338 338 }
339 339
340 340 html>body .tabular p {overflow:hidden;}
341 341
342 342 .tabular label{
343 343 font-weight: bold;
344 344 float: left;
345 345 text-align: right;
346 346 margin-left: -180px; /*width of left column*/
347 347 width: 175px; /*width of labels. Should be smaller than left column to create some right
348 348 margin*/
349 349 }
350 350
351 351 .tabular label.floating{
352 352 font-weight: normal;
353 353 margin-left: 0px;
354 354 text-align: left;
355 355 width: 270px;
356 356 }
357 357
358 358 .tabular label.block{
359 359 font-weight: normal;
360 360 margin-left: 0px !important;
361 361 text-align: left;
362 362 float: none;
363 363 display: block;
364 364 width: auto;
365 365 }
366 366
367 367 input#time_entry_comments { width: 90%;}
368 368
369 369 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
370 370
371 371 .tabular.settings p{ padding-left: 300px; }
372 372 .tabular.settings label{ margin-left: -300px; width: 295px; }
373 373 .tabular.settings textarea { width: 99%; }
374 374
375 375 fieldset.settings label { display: block; }
376 376
377 377 .required {color: #bb0000;}
378 378 .summary {font-style: italic;}
379 379
380 380 #attachments_fields input[type=text] {margin-left: 8px; }
381 381
382 382 div.attachments { margin-top: 12px; }
383 383 div.attachments p { margin:4px 0 2px 0; }
384 384 div.attachments img { vertical-align: middle; }
385 385 div.attachments span.author { font-size: 0.9em; color: #888; }
386 386
387 387 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
388 388 .other-formats span + span:before { content: "| "; }
389 389
390 390 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
391 391
392 392 /* Project members tab */
393 393 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
394 394 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
395 395 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
396 396 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
397 397 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
398 398 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
399 399
400 400 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
401 401
402 402 * html div#tab-content-members fieldset div { height: 450px; }
403 403
404 404 /***** Flash & error messages ****/
405 405 #errorExplanation, div.flash, .nodata, .warning {
406 406 padding: 4px 4px 4px 30px;
407 407 margin-bottom: 12px;
408 408 font-size: 1.1em;
409 409 border: 2px solid;
410 410 }
411 411
412 412 div.flash {margin-top: 8px;}
413 413
414 414 div.flash.error, #errorExplanation {
415 415 background: url(../images/exclamation.png) 8px 50% no-repeat;
416 416 background-color: #ffe3e3;
417 417 border-color: #dd0000;
418 418 color: #880000;
419 419 }
420 420
421 421 div.flash.notice {
422 422 background: url(../images/true.png) 8px 5px no-repeat;
423 423 background-color: #dfffdf;
424 424 border-color: #9fcf9f;
425 425 color: #005f00;
426 426 }
427 427
428 428 div.flash.warning {
429 429 background: url(../images/warning.png) 8px 5px no-repeat;
430 430 background-color: #FFEBC1;
431 431 border-color: #FDBF3B;
432 432 color: #A6750C;
433 433 text-align: left;
434 434 }
435 435
436 436 .nodata, .warning {
437 437 text-align: center;
438 438 background-color: #FFEBC1;
439 439 border-color: #FDBF3B;
440 440 color: #A6750C;
441 441 }
442 442
443 443 #errorExplanation ul { font-size: 0.9em;}
444 444 #errorExplanation h2, #errorExplanation p { display: none; }
445 445
446 446 /***** Ajax indicator ******/
447 447 #ajax-indicator {
448 448 position: absolute; /* fixed not supported by IE */
449 449 background-color:#eee;
450 450 border: 1px solid #bbb;
451 451 top:35%;
452 452 left:40%;
453 453 width:20%;
454 454 font-weight:bold;
455 455 text-align:center;
456 456 padding:0.6em;
457 457 z-index:100;
458 458 filter:alpha(opacity=50);
459 459 opacity: 0.5;
460 460 }
461 461
462 462 html>body #ajax-indicator { position: fixed; }
463 463
464 464 #ajax-indicator span {
465 465 background-position: 0% 40%;
466 466 background-repeat: no-repeat;
467 467 background-image: url(../images/loading.gif);
468 468 padding-left: 26px;
469 469 vertical-align: bottom;
470 470 }
471 471
472 472 /***** Calendar *****/
473 473 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
474 474 table.cal thead th {width: 14%;}
475 475 table.cal tbody tr {height: 100px;}
476 476 table.cal th { background-color:#EEEEEE; padding: 4px; }
477 477 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
478 478 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
479 479 table.cal td.odd p.day-num {color: #bbb;}
480 480 table.cal td.today {background:#ffffdd;}
481 481 table.cal td.today p.day-num {font-weight: bold;}
482 482
483 483 /***** Tooltips ******/
484 484 .tooltip{position:relative;z-index:24;}
485 485 .tooltip:hover{z-index:25;color:#000;}
486 486 .tooltip span.tip{display: none; text-align:left;}
487 487
488 488 div.tooltip:hover span.tip{
489 489 display:block;
490 490 position:absolute;
491 491 top:12px; left:24px; width:270px;
492 492 border:1px solid #555;
493 493 background-color:#fff;
494 494 padding: 4px;
495 495 font-size: 0.8em;
496 496 color:#505050;
497 497 }
498 498
499 499 /***** Progress bar *****/
500 500 table.progress {
501 501 border: 1px solid #D7D7D7;
502 502 border-collapse: collapse;
503 503 border-spacing: 0pt;
504 504 empty-cells: show;
505 505 text-align: center;
506 506 float:left;
507 507 margin: 1px 6px 1px 0px;
508 508 }
509 509
510 510 table.progress td { height: 0.9em; }
511 511 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
512 512 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
513 513 table.progress td.open { background: #FFF none repeat scroll 0%; }
514 514 p.pourcent {font-size: 80%;}
515 515 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
516 516
517 517 /***** Tabs *****/
518 518 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative; overflow:hidden;}
519 519 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em; width: 2000px;}
520 520 #content .tabs>ul { bottom:-1px; } /* others */
521 521 #content .tabs ul li {
522 522 float:left;
523 523 list-style-type:none;
524 524 white-space:nowrap;
525 525 margin-right:8px;
526 526 background:#fff;
527 527 }
528 528 #content .tabs ul li a{
529 529 display:block;
530 530 font-size: 0.9em;
531 531 text-decoration:none;
532 532 line-height:1.3em;
533 533 padding:4px 6px 4px 6px;
534 534 border: 1px solid #ccc;
535 535 border-bottom: 1px solid #bbbbbb;
536 536 background-color: #eeeeee;
537 537 color:#777;
538 538 font-weight:bold;
539 539 }
540 540
541 541 #content .tabs ul li a:hover {
542 542 background-color: #ffffdd;
543 543 text-decoration:none;
544 544 }
545 545
546 546 #content .tabs ul li a.selected {
547 547 background-color: #fff;
548 548 border: 1px solid #bbbbbb;
549 549 border-bottom: 1px solid #fff;
550 550 }
551 551
552 552 #content .tabs ul li a.selected:hover {
553 553 background-color: #fff;
554 554 }
555 555
556 556 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: -1px; }
557 557
558 558 button.tab-left, button.tab-right {
559 559 font-size: 0.9em;
560 560 cursor: pointer;
561 561 height:24px;
562 562 border: 1px solid #ccc;
563 563 border-bottom: 1px solid #bbbbbb;
564 564 position:absolute;
565 565 padding:4px;
566 566 width: 20px;
567 567 }
568 568
569 569 button.tab-left {
570 570 right: 20px;
571 571 bottom: 0;
572 572 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
573 573 }
574 574
575 575 button.tab-right {
576 576 right: 0;
577 577 bottom: 0;
578 578 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;}
579 579 }
580 580
581 581 /***** Auto-complete *****/
582 582 div.autocomplete {
583 583 position:absolute;
584 584 width:250px;
585 585 background-color:white;
586 586 margin:0;
587 587 padding:0;
588 588 }
589 589 div.autocomplete ul {
590 590 list-style-type:none;
591 591 margin:0;
592 592 padding:0;
593 593 }
594 594 div.autocomplete ul li.selected { background-color: #ffb;}
595 595 div.autocomplete ul li {
596 596 list-style-type:none;
597 597 display:block;
598 598 margin:0;
599 599 padding:2px;
600 600 cursor:pointer;
601 601 font-size: 90%;
602 602 border-bottom: 1px solid #ccc;
603 603 border-left: 1px solid #ccc;
604 604 border-right: 1px solid #ccc;
605 605 }
606 606 div.autocomplete ul li span.informal {
607 607 font-size: 80%;
608 608 color: #aaa;
609 609 }
610 610
611 611 /***** Diff *****/
612 612 .diff_out { background: #fcc; }
613 613 .diff_in { background: #cfc; }
614 614
615 615 /***** Wiki *****/
616 616 div.wiki table {
617 617 border: 1px solid #505050;
618 618 border-collapse: collapse;
619 619 margin-bottom: 1em;
620 620 }
621 621
622 622 div.wiki table, div.wiki td, div.wiki th {
623 623 border: 1px solid #bbb;
624 624 padding: 4px;
625 625 }
626 626
627 627 div.wiki .external {
628 628 background-position: 0% 60%;
629 629 background-repeat: no-repeat;
630 630 padding-left: 12px;
631 631 background-image: url(../images/external.png);
632 632 }
633 633
634 634 div.wiki a.new {
635 635 color: #b73535;
636 636 }
637 637
638 638 div.wiki pre {
639 639 margin: 1em 1em 1em 1.6em;
640 640 padding: 2px;
641 641 background-color: #fafafa;
642 642 border: 1px solid #dadada;
643 643 width:95%;
644 644 overflow-x: auto;
645 645 }
646 646
647 647 div.wiki ul.toc {
648 648 background-color: #ffffdd;
649 649 border: 1px solid #e4e4e4;
650 650 padding: 4px;
651 651 line-height: 1.2em;
652 652 margin-bottom: 12px;
653 653 margin-right: 12px;
654 654 margin-left: 0;
655 655 display: table
656 656 }
657 657 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
658 658
659 659 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
660 660 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
661 661 div.wiki ul.toc li { list-style-type:none;}
662 662 div.wiki ul.toc li.heading2 { margin-left: 6px; }
663 663 div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; }
664 664
665 665 div.wiki ul.toc a {
666 666 font-size: 0.9em;
667 667 font-weight: normal;
668 668 text-decoration: none;
669 669 color: #606060;
670 670 }
671 671 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
672 672
673 673 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
674 674 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
675 675 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
676 676
677 677 /***** My page layout *****/
678 678 .block-receiver {
679 679 border:1px dashed #c0c0c0;
680 680 margin-bottom: 20px;
681 681 padding: 15px 0 15px 0;
682 682 }
683 683
684 684 .mypage-box {
685 685 margin:0 0 20px 0;
686 686 color:#505050;
687 687 line-height:1.5em;
688 688 }
689 689
690 690 .handle {
691 691 cursor: move;
692 692 }
693 693
694 694 a.close-icon {
695 695 display:block;
696 696 margin-top:3px;
697 697 overflow:hidden;
698 698 width:12px;
699 699 height:12px;
700 700 background-repeat: no-repeat;
701 701 cursor:pointer;
702 702 background-image:url('../images/close.png');
703 703 }
704 704
705 705 a.close-icon:hover {
706 706 background-image:url('../images/close_hl.png');
707 707 }
708 708
709 709 /***** Gantt chart *****/
710 710 .gantt_hdr {
711 711 position:absolute;
712 712 top:0;
713 713 height:16px;
714 714 border-top: 1px solid #c0c0c0;
715 715 border-bottom: 1px solid #c0c0c0;
716 716 border-right: 1px solid #c0c0c0;
717 717 text-align: center;
718 718 overflow: hidden;
719 719 }
720 720
721 721 .task {
722 722 position: absolute;
723 723 height:8px;
724 724 font-size:0.8em;
725 725 color:#888;
726 726 padding:0;
727 727 margin:0;
728 728 line-height:0.8em;
729 729 }
730 730
731 731 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
732 732 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
733 733 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
734 734 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
735 735
736 736 /***** Icons *****/
737 737 .icon {
738 738 background-position: 0% 50%;
739 739 background-repeat: no-repeat;
740 740 padding-left: 20px;
741 741 padding-top: 2px;
742 742 padding-bottom: 3px;
743 743 }
744 744
745 745 .icon-add { background-image: url(../images/add.png); }
746 746 .icon-edit { background-image: url(../images/edit.png); }
747 747 .icon-copy { background-image: url(../images/copy.png); }
748 748 .icon-duplicate { background-image: url(../images/duplicate.png); }
749 749 .icon-del { background-image: url(../images/delete.png); }
750 750 .icon-move { background-image: url(../images/move.png); }
751 751 .icon-save { background-image: url(../images/save.png); }
752 752 .icon-cancel { background-image: url(../images/cancel.png); }
753 753 .icon-multiple { background-image: url(../images/table_multiple.png); }
754 754 .icon-folder { background-image: url(../images/folder.png); }
755 755 .open .icon-folder { background-image: url(../images/folder_open.png); }
756 756 .icon-package { background-image: url(../images/package.png); }
757 757 .icon-home { background-image: url(../images/home.png); }
758 758 .icon-user { background-image: url(../images/user.png); }
759 759 .icon-projects { background-image: url(../images/projects.png); }
760 760 .icon-help { background-image: url(../images/help.png); }
761 761 .icon-attachment { background-image: url(../images/attachment.png); }
762 762 .icon-history { background-image: url(../images/history.png); }
763 763 .icon-time { background-image: url(../images/time.png); }
764 764 .icon-time-add { background-image: url(../images/time_add.png); }
765 765 .icon-stats { background-image: url(../images/stats.png); }
766 766 .icon-warning { background-image: url(../images/warning.png); }
767 767 .icon-fav { background-image: url(../images/fav.png); }
768 768 .icon-fav-off { background-image: url(../images/fav_off.png); }
769 769 .icon-reload { background-image: url(../images/reload.png); }
770 770 .icon-lock { background-image: url(../images/locked.png); }
771 771 .icon-unlock { background-image: url(../images/unlock.png); }
772 772 .icon-checked { background-image: url(../images/true.png); }
773 773 .icon-details { background-image: url(../images/zoom_in.png); }
774 774 .icon-report { background-image: url(../images/report.png); }
775 775 .icon-comment { background-image: url(../images/comment.png); }
776 776 .icon-summary { background-image: url(../images/lightning.png); }
777 777
778 778 .icon-file { background-image: url(../images/files/default.png); }
779 779 .icon-file.text-plain { background-image: url(../images/files/text.png); }
780 780 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
781 781 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
782 782 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
783 783 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
784 784 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
785 785 .icon-file.image-gif { background-image: url(../images/files/image.png); }
786 786 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
787 787 .icon-file.image-png { background-image: url(../images/files/image.png); }
788 788 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
789 789 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
790 790 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
791 791 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
792 792
793 793 img.gravatar {
794 794 padding: 2px;
795 795 border: solid 1px #d5d5d5;
796 796 background: #fff;
797 797 }
798 798
799 799 div.issue img.gravatar {
800 800 float: right;
801 801 margin: 0 0 0 1em;
802 802 padding: 5px;
803 803 }
804 804
805 805 div.issue table img.gravatar {
806 806 height: 14px;
807 807 width: 14px;
808 808 padding: 2px;
809 809 float: left;
810 810 margin: 0 0.5em 0 0;
811 811 }
812 812
813 #history img.gravatar {
813 h2 img.gravatar {
814 814 padding: 3px;
815 margin: 0 1.5em 1em 0;
815 margin: -2px 4px 0 0;
816 float: left;
817 }
818
819 h4 img.gravatar {
820 padding: 3px;
821 margin: -6px 4px 0 0;
816 822 float: left;
817 823 }
818 824
819 825 td.username img.gravatar {
820 826 float: left;
821 827 margin: 0 1em 0 0;
822 828 }
823 829
824 830 #activity dt img.gravatar {
825 831 float: left;
826 832 margin: 0 1em 1em 0;
827 833 }
828 834
829 835 #activity dt,
830 836 .journal {
831 837 clear: left;
832 838 }
833 839
834 .gravatar-margin {
835 margin-left: 40px;
836 }
837
838 840 h2 img { vertical-align:middle; }
839 841
840 842 .hascontextmenu { cursor: context-menu; }
841 843
842 844 /***** Media print specific styles *****/
843 845 @media print {
844 846 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
845 847 #main { background: #fff; }
846 848 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
847 849 #wiki_add_attachment { display:none; }
848 850 }
General Comments 0
You need to be logged in to leave comments. Login now