##// END OF EJS Templates
Build issue filters using javascript....
Jean-Philippe Lang -
r9979:1749fbf3e616
parent child
Show More
@@ -1,131 +1,134
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 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
21 def filters_options_for_select(query)
22 def operators_for_select(filter_type)
22 options = [[]]
23 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
23 options += query.available_filters.sort {|a,b| a[1][:order] <=> b[1][:order]}.map do |field, field_options|
24 [field_options[:name], field]
25 end
26 options_for_select(options)
24 end
27 end
25
28
26 def column_header(column)
29 def column_header(column)
27 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
30 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
28 :default_order => column.default_order) :
31 :default_order => column.default_order) :
29 content_tag('th', h(column.caption))
32 content_tag('th', h(column.caption))
30 end
33 end
31
34
32 def column_content(column, issue)
35 def column_content(column, issue)
33 value = column.value(issue)
36 value = column.value(issue)
34 if value.is_a?(Array)
37 if value.is_a?(Array)
35 value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
38 value.collect {|v| column_value(column, issue, v)}.compact.sort.join(', ').html_safe
36 else
39 else
37 column_value(column, issue, value)
40 column_value(column, issue, value)
38 end
41 end
39 end
42 end
40
43
41 def column_value(column, issue, value)
44 def column_value(column, issue, value)
42 case value.class.name
45 case value.class.name
43 when 'String'
46 when 'String'
44 if column.name == :subject
47 if column.name == :subject
45 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
48 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
46 else
49 else
47 h(value)
50 h(value)
48 end
51 end
49 when 'Time'
52 when 'Time'
50 format_time(value)
53 format_time(value)
51 when 'Date'
54 when 'Date'
52 format_date(value)
55 format_date(value)
53 when 'Fixnum', 'Float'
56 when 'Fixnum', 'Float'
54 if column.name == :done_ratio
57 if column.name == :done_ratio
55 progress_bar(value, :width => '80px')
58 progress_bar(value, :width => '80px')
56 elsif column.name == :spent_hours
59 elsif column.name == :spent_hours
57 sprintf "%.2f", value
60 sprintf "%.2f", value
58 else
61 else
59 h(value.to_s)
62 h(value.to_s)
60 end
63 end
61 when 'User'
64 when 'User'
62 link_to_user value
65 link_to_user value
63 when 'Project'
66 when 'Project'
64 link_to_project value
67 link_to_project value
65 when 'Version'
68 when 'Version'
66 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
69 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
67 when 'TrueClass'
70 when 'TrueClass'
68 l(:general_text_Yes)
71 l(:general_text_Yes)
69 when 'FalseClass'
72 when 'FalseClass'
70 l(:general_text_No)
73 l(:general_text_No)
71 when 'Issue'
74 when 'Issue'
72 link_to_issue(value, :subject => false)
75 link_to_issue(value, :subject => false)
73 else
76 else
74 h(value)
77 h(value)
75 end
78 end
76 end
79 end
77
80
78 # Retrieve query from session or build a new query
81 # Retrieve query from session or build a new query
79 def retrieve_query
82 def retrieve_query
80 if !params[:query_id].blank?
83 if !params[:query_id].blank?
81 cond = "project_id IS NULL"
84 cond = "project_id IS NULL"
82 cond << " OR project_id = #{@project.id}" if @project
85 cond << " OR project_id = #{@project.id}" if @project
83 @query = Query.find(params[:query_id], :conditions => cond)
86 @query = Query.find(params[:query_id], :conditions => cond)
84 raise ::Unauthorized unless @query.visible?
87 raise ::Unauthorized unless @query.visible?
85 @query.project = @project
88 @query.project = @project
86 session[:query] = {:id => @query.id, :project_id => @query.project_id}
89 session[:query] = {:id => @query.id, :project_id => @query.project_id}
87 sort_clear
90 sort_clear
88 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
91 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
89 # Give it a name, required to be valid
92 # Give it a name, required to be valid
90 @query = Query.new(:name => "_")
93 @query = Query.new(:name => "_")
91 @query.project = @project
94 @query.project = @project
92 build_query_from_params
95 build_query_from_params
93 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
96 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
94 else
97 else
95 # retrieve from session
98 # retrieve from session
96 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
99 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
97 @query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
100 @query ||= Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
98 @query.project = @project
101 @query.project = @project
99 end
102 end
100 end
103 end
101
104
102 def retrieve_query_from_session
105 def retrieve_query_from_session
103 if session[:query]
106 if session[:query]
104 if session[:query][:id]
107 if session[:query][:id]
105 @query = Query.find_by_id(session[:query][:id])
108 @query = Query.find_by_id(session[:query][:id])
106 return unless @query
109 return unless @query
107 else
110 else
108 @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
111 @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
109 end
112 end
110 if session[:query].has_key?(:project_id)
113 if session[:query].has_key?(:project_id)
111 @query.project_id = session[:query][:project_id]
114 @query.project_id = session[:query][:project_id]
112 else
115 else
113 @query.project = @project
116 @query.project = @project
114 end
117 end
115 @query
118 @query
116 end
119 end
117 end
120 end
118
121
119 def build_query_from_params
122 def build_query_from_params
120 if params[:fields] || params[:f]
123 if params[:fields] || params[:f]
121 @query.filters = {}
124 @query.filters = {}
122 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
125 @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
123 else
126 else
124 @query.available_filters.keys.each do |field|
127 @query.available_filters.keys.each do |field|
125 @query.add_short_filter(field, params[field]) if params[field]
128 @query.add_short_filter(field, params[field]) if params[field]
126 end
129 end
127 end
130 end
128 @query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
131 @query.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
129 @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
132 @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
130 end
133 end
131 end
134 end
@@ -1,914 +1,932
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !@sortable.nil?
39 !@sortable.nil?
40 end
40 end
41
41
42 def sortable
42 def sortable
43 @sortable.is_a?(Proc) ? @sortable.call : @sortable
43 @sortable.is_a?(Proc) ? @sortable.call : @sortable
44 end
44 end
45
45
46 def value(issue)
46 def value(issue)
47 issue.send name
47 issue.send name
48 end
48 end
49
49
50 def css_classes
50 def css_classes
51 name
51 name
52 end
52 end
53 end
53 end
54
54
55 class QueryCustomFieldColumn < QueryColumn
55 class QueryCustomFieldColumn < QueryColumn
56
56
57 def initialize(custom_field)
57 def initialize(custom_field)
58 self.name = "cf_#{custom_field.id}".to_sym
58 self.name = "cf_#{custom_field.id}".to_sym
59 self.sortable = custom_field.order_statement || false
59 self.sortable = custom_field.order_statement || false
60 self.groupable = custom_field.group_statement || false
60 self.groupable = custom_field.group_statement || false
61 @cf = custom_field
61 @cf = custom_field
62 end
62 end
63
63
64 def caption
64 def caption
65 @cf.name
65 @cf.name
66 end
66 end
67
67
68 def custom_field
68 def custom_field
69 @cf
69 @cf
70 end
70 end
71
71
72 def value(issue)
72 def value(issue)
73 cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
73 cv = issue.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
74 cv.size > 1 ? cv : cv.first
74 cv.size > 1 ? cv : cv.first
75 end
75 end
76
76
77 def css_classes
77 def css_classes
78 @css_classes ||= "#{name} #{@cf.field_format}"
78 @css_classes ||= "#{name} #{@cf.field_format}"
79 end
79 end
80 end
80 end
81
81
82 class Query < ActiveRecord::Base
82 class Query < ActiveRecord::Base
83 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 class StatementInvalid < ::ActiveRecord::StatementInvalid
84 end
84 end
85
85
86 belongs_to :project
86 belongs_to :project
87 belongs_to :user
87 belongs_to :user
88 serialize :filters
88 serialize :filters
89 serialize :column_names
89 serialize :column_names
90 serialize :sort_criteria, Array
90 serialize :sort_criteria, Array
91
91
92 attr_protected :project_id, :user_id
92 attr_protected :project_id, :user_id
93
93
94 validates_presence_of :name
94 validates_presence_of :name
95 validates_length_of :name, :maximum => 255
95 validates_length_of :name, :maximum => 255
96 validate :validate_query_filters
96 validate :validate_query_filters
97
97
98 @@operators = { "=" => :label_equals,
98 @@operators = { "=" => :label_equals,
99 "!" => :label_not_equals,
99 "!" => :label_not_equals,
100 "o" => :label_open_issues,
100 "o" => :label_open_issues,
101 "c" => :label_closed_issues,
101 "c" => :label_closed_issues,
102 "!*" => :label_none,
102 "!*" => :label_none,
103 "*" => :label_all,
103 "*" => :label_all,
104 ">=" => :label_greater_or_equal,
104 ">=" => :label_greater_or_equal,
105 "<=" => :label_less_or_equal,
105 "<=" => :label_less_or_equal,
106 "><" => :label_between,
106 "><" => :label_between,
107 "<t+" => :label_in_less_than,
107 "<t+" => :label_in_less_than,
108 ">t+" => :label_in_more_than,
108 ">t+" => :label_in_more_than,
109 "t+" => :label_in,
109 "t+" => :label_in,
110 "t" => :label_today,
110 "t" => :label_today,
111 "w" => :label_this_week,
111 "w" => :label_this_week,
112 ">t-" => :label_less_than_ago,
112 ">t-" => :label_less_than_ago,
113 "<t-" => :label_more_than_ago,
113 "<t-" => :label_more_than_ago,
114 "t-" => :label_ago,
114 "t-" => :label_ago,
115 "~" => :label_contains,
115 "~" => :label_contains,
116 "!~" => :label_not_contains }
116 "!~" => :label_not_contains }
117
117
118 cattr_reader :operators
118 cattr_reader :operators
119
119
120 @@operators_by_filter_type = { :list => [ "=", "!" ],
120 @@operators_by_filter_type = { :list => [ "=", "!" ],
121 :list_status => [ "o", "=", "!", "c", "*" ],
121 :list_status => [ "o", "=", "!", "c", "*" ],
122 :list_optional => [ "=", "!", "!*", "*" ],
122 :list_optional => [ "=", "!", "!*", "*" ],
123 :list_subprojects => [ "*", "!*", "=" ],
123 :list_subprojects => [ "*", "!*", "=" ],
124 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
124 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-", "!*", "*" ],
125 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
125 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w", "!*", "*" ],
126 :string => [ "=", "~", "!", "!~", "!*", "*" ],
126 :string => [ "=", "~", "!", "!~", "!*", "*" ],
127 :text => [ "~", "!~", "!*", "*" ],
127 :text => [ "~", "!~", "!*", "*" ],
128 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
128 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
129 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
129 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
130
130
131 cattr_reader :operators_by_filter_type
131 cattr_reader :operators_by_filter_type
132
132
133 @@available_columns = [
133 @@available_columns = [
134 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
134 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
135 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
135 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
136 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
136 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
137 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
137 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
138 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
138 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
139 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
139 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
140 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
140 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
141 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
141 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
142 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
142 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
143 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
143 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
144 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
144 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
145 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
145 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
146 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
146 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
147 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
147 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
148 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
148 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
149 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
149 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
150 ]
150 ]
151 cattr_reader :available_columns
151 cattr_reader :available_columns
152
152
153 scope :visible, lambda {|*args|
153 scope :visible, lambda {|*args|
154 user = args.shift || User.current
154 user = args.shift || User.current
155 base = Project.allowed_to_condition(user, :view_issues, *args)
155 base = Project.allowed_to_condition(user, :view_issues, *args)
156 user_id = user.logged? ? user.id : 0
156 user_id = user.logged? ? user.id : 0
157 {
157 {
158 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
158 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
159 :include => :project
159 :include => :project
160 }
160 }
161 }
161 }
162
162
163 def initialize(attributes=nil, *args)
163 def initialize(attributes=nil, *args)
164 super attributes
164 super attributes
165 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
165 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
166 @is_for_all = project.nil?
166 @is_for_all = project.nil?
167 end
167 end
168
168
169 def validate_query_filters
169 def validate_query_filters
170 filters.each_key do |field|
170 filters.each_key do |field|
171 if values_for(field)
171 if values_for(field)
172 case type_for(field)
172 case type_for(field)
173 when :integer
173 when :integer
174 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
174 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
175 when :float
175 when :float
176 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
176 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
177 when :date, :date_past
177 when :date, :date_past
178 case operator_for(field)
178 case operator_for(field)
179 when "=", ">=", "<=", "><"
179 when "=", ">=", "<=", "><"
180 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
180 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
181 when ">t-", "<t-", "t-"
181 when ">t-", "<t-", "t-"
182 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
182 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
183 end
183 end
184 end
184 end
185 end
185 end
186
186
187 add_filter_error(field, :blank) unless
187 add_filter_error(field, :blank) unless
188 # filter requires one or more values
188 # filter requires one or more values
189 (values_for(field) and !values_for(field).first.blank?) or
189 (values_for(field) and !values_for(field).first.blank?) or
190 # filter doesn't require any value
190 # filter doesn't require any value
191 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
191 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
192 end if filters
192 end if filters
193 end
193 end
194
194
195 def add_filter_error(field, message)
195 def add_filter_error(field, message)
196 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
196 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
197 errors.add(:base, m)
197 errors.add(:base, m)
198 end
198 end
199
199
200 # Returns true if the query is visible to +user+ or the current user.
200 # Returns true if the query is visible to +user+ or the current user.
201 def visible?(user=User.current)
201 def visible?(user=User.current)
202 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
202 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
203 end
203 end
204
204
205 def editable_by?(user)
205 def editable_by?(user)
206 return false unless user
206 return false unless user
207 # Admin can edit them all and regular users can edit their private queries
207 # Admin can edit them all and regular users can edit their private queries
208 return true if user.admin? || (!is_public && self.user_id == user.id)
208 return true if user.admin? || (!is_public && self.user_id == user.id)
209 # Members can not edit public queries that are for all project (only admin is allowed to)
209 # Members can not edit public queries that are for all project (only admin is allowed to)
210 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
210 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
211 end
211 end
212
212
213 def trackers
213 def trackers
214 @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
214 @trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
215 end
215 end
216
216
217 # Returns a hash of localized labels for all filter operators
218 def self.operators_labels
219 operators.inject({}) {|h, operator| h[operator.first] = l(operator.last); h}
220 end
221
217 def available_filters
222 def available_filters
218 return @available_filters if @available_filters
223 return @available_filters if @available_filters
219
224
220 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
225 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
221 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
226 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
222 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
227 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
223 "subject" => { :type => :text, :order => 8 },
228 "subject" => { :type => :text, :order => 8 },
224 "created_on" => { :type => :date_past, :order => 9 },
229 "created_on" => { :type => :date_past, :order => 9 },
225 "updated_on" => { :type => :date_past, :order => 10 },
230 "updated_on" => { :type => :date_past, :order => 10 },
226 "start_date" => { :type => :date, :order => 11 },
231 "start_date" => { :type => :date, :order => 11 },
227 "due_date" => { :type => :date, :order => 12 },
232 "due_date" => { :type => :date, :order => 12 },
228 "estimated_hours" => { :type => :float, :order => 13 },
233 "estimated_hours" => { :type => :float, :order => 13 },
229 "done_ratio" => { :type => :integer, :order => 14 }}
234 "done_ratio" => { :type => :integer, :order => 14 }}
230
235
231 principals = []
236 principals = []
232 if project
237 if project
233 principals += project.principals.sort
238 principals += project.principals.sort
234 unless project.leaf?
239 unless project.leaf?
235 subprojects = project.descendants.visible.all
240 subprojects = project.descendants.visible.all
236 if subprojects.any?
241 if subprojects.any?
237 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
242 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
238 principals += Principal.member_of(subprojects)
243 principals += Principal.member_of(subprojects)
239 end
244 end
240 end
245 end
241 else
246 else
242 all_projects = Project.visible.all
247 all_projects = Project.visible.all
243 if all_projects.any?
248 if all_projects.any?
244 # members of visible projects
249 # members of visible projects
245 principals += Principal.member_of(all_projects)
250 principals += Principal.member_of(all_projects)
246
251
247 # project filter
252 # project filter
248 project_values = []
253 project_values = []
249 if User.current.logged? && User.current.memberships.any?
254 if User.current.logged? && User.current.memberships.any?
250 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
255 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
251 end
256 end
252 Project.project_tree(all_projects) do |p, level|
257 Project.project_tree(all_projects) do |p, level|
253 prefix = (level > 0 ? ('--' * level + ' ') : '')
258 prefix = (level > 0 ? ('--' * level + ' ') : '')
254 project_values << ["#{prefix}#{p.name}", p.id.to_s]
259 project_values << ["#{prefix}#{p.name}", p.id.to_s]
255 end
260 end
256 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
261 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
257 end
262 end
258 end
263 end
259 principals.uniq!
264 principals.uniq!
260 principals.sort!
265 principals.sort!
261 users = principals.select {|p| p.is_a?(User)}
266 users = principals.select {|p| p.is_a?(User)}
262
267
263 assigned_to_values = []
268 assigned_to_values = []
264 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
269 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
265 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
270 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
266 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
271 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
267
272
268 author_values = []
273 author_values = []
269 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
274 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
270 author_values += users.collect{|s| [s.name, s.id.to_s] }
275 author_values += users.collect{|s| [s.name, s.id.to_s] }
271 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
276 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
272
277
273 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
278 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
274 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
279 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
275
280
276 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
281 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
277 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
282 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
278
283
279 if User.current.logged?
284 if User.current.logged?
280 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
285 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
281 end
286 end
282
287
283 if project
288 if project
284 # project specific filters
289 # project specific filters
285 categories = project.issue_categories.all
290 categories = project.issue_categories.all
286 unless categories.empty?
291 unless categories.empty?
287 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
292 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
288 end
293 end
289 versions = project.shared_versions.all
294 versions = project.shared_versions.all
290 unless versions.empty?
295 unless versions.empty?
291 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
296 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
292 end
297 end
293 add_custom_fields_filters(project.all_issue_custom_fields)
298 add_custom_fields_filters(project.all_issue_custom_fields)
294 else
299 else
295 # global filters for cross project issue list
300 # global filters for cross project issue list
296 system_shared_versions = Version.visible.find_all_by_sharing('system')
301 system_shared_versions = Version.visible.find_all_by_sharing('system')
297 unless system_shared_versions.empty?
302 unless system_shared_versions.empty?
298 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
303 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
299 end
304 end
300 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
301 end
306 end
302
307
303 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
308 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
304 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
309 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
305 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
310 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
306 end
311 end
307
312
308 Tracker.disabled_core_fields(trackers).each {|field|
313 Tracker.disabled_core_fields(trackers).each {|field|
309 @available_filters.delete field
314 @available_filters.delete field
310 }
315 }
311
316
317 @available_filters.each do |field, options|
318 options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
319 end
320
312 @available_filters
321 @available_filters
313 end
322 end
314
323
324 # Returns a representation of the available filters for JSON serialization
325 def available_filters_as_json
326 json = {}
327 available_filters.each do |field, options|
328 json[field] = options.slice(:type, :name, :values).stringify_keys
329 end
330 json
331 end
332
315 def add_filter(field, operator, values)
333 def add_filter(field, operator, values)
316 # values must be an array
334 # values must be an array
317 return unless values.nil? || values.is_a?(Array)
335 return unless values.nil? || values.is_a?(Array)
318 # check if field is defined as an available filter
336 # check if field is defined as an available filter
319 if available_filters.has_key? field
337 if available_filters.has_key? field
320 filter_options = available_filters[field]
338 filter_options = available_filters[field]
321 # check if operator is allowed for that filter
339 # check if operator is allowed for that filter
322 #if @@operators_by_filter_type[filter_options[:type]].include? operator
340 #if @@operators_by_filter_type[filter_options[:type]].include? operator
323 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
341 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
324 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
342 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
325 #end
343 #end
326 filters[field] = {:operator => operator, :values => (values || [''])}
344 filters[field] = {:operator => operator, :values => (values || [''])}
327 end
345 end
328 end
346 end
329
347
330 def add_short_filter(field, expression)
348 def add_short_filter(field, expression)
331 return unless expression && available_filters.has_key?(field)
349 return unless expression && available_filters.has_key?(field)
332 field_type = available_filters[field][:type]
350 field_type = available_filters[field][:type]
333 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
351 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
334 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
352 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
335 add_filter field, operator, $1.present? ? $1.split('|') : ['']
353 add_filter field, operator, $1.present? ? $1.split('|') : ['']
336 end || add_filter(field, '=', expression.split('|'))
354 end || add_filter(field, '=', expression.split('|'))
337 end
355 end
338
356
339 # Add multiple filters using +add_filter+
357 # Add multiple filters using +add_filter+
340 def add_filters(fields, operators, values)
358 def add_filters(fields, operators, values)
341 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
359 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
342 fields.each do |field|
360 fields.each do |field|
343 add_filter(field, operators[field], values && values[field])
361 add_filter(field, operators[field], values && values[field])
344 end
362 end
345 end
363 end
346 end
364 end
347
365
348 def has_filter?(field)
366 def has_filter?(field)
349 filters and filters[field]
367 filters and filters[field]
350 end
368 end
351
369
352 def type_for(field)
370 def type_for(field)
353 available_filters[field][:type] if available_filters.has_key?(field)
371 available_filters[field][:type] if available_filters.has_key?(field)
354 end
372 end
355
373
356 def operator_for(field)
374 def operator_for(field)
357 has_filter?(field) ? filters[field][:operator] : nil
375 has_filter?(field) ? filters[field][:operator] : nil
358 end
376 end
359
377
360 def values_for(field)
378 def values_for(field)
361 has_filter?(field) ? filters[field][:values] : nil
379 has_filter?(field) ? filters[field][:values] : nil
362 end
380 end
363
381
364 def value_for(field, index=0)
382 def value_for(field, index=0)
365 (values_for(field) || [])[index]
383 (values_for(field) || [])[index]
366 end
384 end
367
385
368 def label_for(field)
386 def label_for(field)
369 label = available_filters[field][:name] if available_filters.has_key?(field)
387 label = available_filters[field][:name] if available_filters.has_key?(field)
370 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
388 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
371 end
389 end
372
390
373 def available_columns
391 def available_columns
374 return @available_columns if @available_columns
392 return @available_columns if @available_columns
375 @available_columns = ::Query.available_columns.dup
393 @available_columns = ::Query.available_columns.dup
376 @available_columns += (project ?
394 @available_columns += (project ?
377 project.all_issue_custom_fields :
395 project.all_issue_custom_fields :
378 IssueCustomField.find(:all)
396 IssueCustomField.find(:all)
379 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
397 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
380
398
381 if User.current.allowed_to?(:view_time_entries, project, :global => true)
399 if User.current.allowed_to?(:view_time_entries, project, :global => true)
382 index = nil
400 index = nil
383 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
401 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
384 index = (index ? index + 1 : -1)
402 index = (index ? index + 1 : -1)
385 # insert the column after estimated_hours or at the end
403 # insert the column after estimated_hours or at the end
386 @available_columns.insert index, QueryColumn.new(:spent_hours,
404 @available_columns.insert index, QueryColumn.new(:spent_hours,
387 :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
405 :sortable => "(SELECT COALESCE(SUM(hours), 0) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id)",
388 :default_order => 'desc',
406 :default_order => 'desc',
389 :caption => :label_spent_time
407 :caption => :label_spent_time
390 )
408 )
391 end
409 end
392
410
393 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
411 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
394 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
412 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
395 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
413 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
396 end
414 end
397
415
398 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
416 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
399 @available_columns.reject! {|column|
417 @available_columns.reject! {|column|
400 disabled_fields.include?(column.name.to_s)
418 disabled_fields.include?(column.name.to_s)
401 }
419 }
402
420
403 @available_columns
421 @available_columns
404 end
422 end
405
423
406 def self.available_columns=(v)
424 def self.available_columns=(v)
407 self.available_columns = (v)
425 self.available_columns = (v)
408 end
426 end
409
427
410 def self.add_available_column(column)
428 def self.add_available_column(column)
411 self.available_columns << (column) if column.is_a?(QueryColumn)
429 self.available_columns << (column) if column.is_a?(QueryColumn)
412 end
430 end
413
431
414 # Returns an array of columns that can be used to group the results
432 # Returns an array of columns that can be used to group the results
415 def groupable_columns
433 def groupable_columns
416 available_columns.select {|c| c.groupable}
434 available_columns.select {|c| c.groupable}
417 end
435 end
418
436
419 # Returns a Hash of columns and the key for sorting
437 # Returns a Hash of columns and the key for sorting
420 def sortable_columns
438 def sortable_columns
421 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
439 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
422 h[column.name.to_s] = column.sortable
440 h[column.name.to_s] = column.sortable
423 h
441 h
424 })
442 })
425 end
443 end
426
444
427 def columns
445 def columns
428 # preserve the column_names order
446 # preserve the column_names order
429 (has_default_columns? ? default_columns_names : column_names).collect do |name|
447 (has_default_columns? ? default_columns_names : column_names).collect do |name|
430 available_columns.find { |col| col.name == name }
448 available_columns.find { |col| col.name == name }
431 end.compact
449 end.compact
432 end
450 end
433
451
434 def default_columns_names
452 def default_columns_names
435 @default_columns_names ||= begin
453 @default_columns_names ||= begin
436 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
454 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
437
455
438 project.present? ? default_columns : [:project] | default_columns
456 project.present? ? default_columns : [:project] | default_columns
439 end
457 end
440 end
458 end
441
459
442 def column_names=(names)
460 def column_names=(names)
443 if names
461 if names
444 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
462 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
445 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
463 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
446 # Set column_names to nil if default columns
464 # Set column_names to nil if default columns
447 if names == default_columns_names
465 if names == default_columns_names
448 names = nil
466 names = nil
449 end
467 end
450 end
468 end
451 write_attribute(:column_names, names)
469 write_attribute(:column_names, names)
452 end
470 end
453
471
454 def has_column?(column)
472 def has_column?(column)
455 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
473 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
456 end
474 end
457
475
458 def has_default_columns?
476 def has_default_columns?
459 column_names.nil? || column_names.empty?
477 column_names.nil? || column_names.empty?
460 end
478 end
461
479
462 def sort_criteria=(arg)
480 def sort_criteria=(arg)
463 c = []
481 c = []
464 if arg.is_a?(Hash)
482 if arg.is_a?(Hash)
465 arg = arg.keys.sort.collect {|k| arg[k]}
483 arg = arg.keys.sort.collect {|k| arg[k]}
466 end
484 end
467 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
485 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
468 write_attribute(:sort_criteria, c)
486 write_attribute(:sort_criteria, c)
469 end
487 end
470
488
471 def sort_criteria
489 def sort_criteria
472 read_attribute(:sort_criteria) || []
490 read_attribute(:sort_criteria) || []
473 end
491 end
474
492
475 def sort_criteria_key(arg)
493 def sort_criteria_key(arg)
476 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
494 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
477 end
495 end
478
496
479 def sort_criteria_order(arg)
497 def sort_criteria_order(arg)
480 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
498 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
481 end
499 end
482
500
483 # Returns the SQL sort order that should be prepended for grouping
501 # Returns the SQL sort order that should be prepended for grouping
484 def group_by_sort_order
502 def group_by_sort_order
485 if grouped? && (column = group_by_column)
503 if grouped? && (column = group_by_column)
486 column.sortable.is_a?(Array) ?
504 column.sortable.is_a?(Array) ?
487 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
505 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
488 "#{column.sortable} #{column.default_order}"
506 "#{column.sortable} #{column.default_order}"
489 end
507 end
490 end
508 end
491
509
492 # Returns true if the query is a grouped query
510 # Returns true if the query is a grouped query
493 def grouped?
511 def grouped?
494 !group_by_column.nil?
512 !group_by_column.nil?
495 end
513 end
496
514
497 def group_by_column
515 def group_by_column
498 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
516 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
499 end
517 end
500
518
501 def group_by_statement
519 def group_by_statement
502 group_by_column.try(:groupable)
520 group_by_column.try(:groupable)
503 end
521 end
504
522
505 def project_statement
523 def project_statement
506 project_clauses = []
524 project_clauses = []
507 if project && !project.descendants.active.empty?
525 if project && !project.descendants.active.empty?
508 ids = [project.id]
526 ids = [project.id]
509 if has_filter?("subproject_id")
527 if has_filter?("subproject_id")
510 case operator_for("subproject_id")
528 case operator_for("subproject_id")
511 when '='
529 when '='
512 # include the selected subprojects
530 # include the selected subprojects
513 ids += values_for("subproject_id").each(&:to_i)
531 ids += values_for("subproject_id").each(&:to_i)
514 when '!*'
532 when '!*'
515 # main project only
533 # main project only
516 else
534 else
517 # all subprojects
535 # all subprojects
518 ids += project.descendants.collect(&:id)
536 ids += project.descendants.collect(&:id)
519 end
537 end
520 elsif Setting.display_subprojects_issues?
538 elsif Setting.display_subprojects_issues?
521 ids += project.descendants.collect(&:id)
539 ids += project.descendants.collect(&:id)
522 end
540 end
523 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
541 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
524 elsif project
542 elsif project
525 project_clauses << "#{Project.table_name}.id = %d" % project.id
543 project_clauses << "#{Project.table_name}.id = %d" % project.id
526 end
544 end
527 project_clauses.any? ? project_clauses.join(' AND ') : nil
545 project_clauses.any? ? project_clauses.join(' AND ') : nil
528 end
546 end
529
547
530 def statement
548 def statement
531 # filters clauses
549 # filters clauses
532 filters_clauses = []
550 filters_clauses = []
533 filters.each_key do |field|
551 filters.each_key do |field|
534 next if field == "subproject_id"
552 next if field == "subproject_id"
535 v = values_for(field).clone
553 v = values_for(field).clone
536 next unless v and !v.empty?
554 next unless v and !v.empty?
537 operator = operator_for(field)
555 operator = operator_for(field)
538
556
539 # "me" value subsitution
557 # "me" value subsitution
540 if %w(assigned_to_id author_id watcher_id).include?(field)
558 if %w(assigned_to_id author_id watcher_id).include?(field)
541 if v.delete("me")
559 if v.delete("me")
542 if User.current.logged?
560 if User.current.logged?
543 v.push(User.current.id.to_s)
561 v.push(User.current.id.to_s)
544 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
562 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
545 else
563 else
546 v.push("0")
564 v.push("0")
547 end
565 end
548 end
566 end
549 end
567 end
550
568
551 if field == 'project_id'
569 if field == 'project_id'
552 if v.delete('mine')
570 if v.delete('mine')
553 v += User.current.memberships.map(&:project_id).map(&:to_s)
571 v += User.current.memberships.map(&:project_id).map(&:to_s)
554 end
572 end
555 end
573 end
556
574
557 if field =~ /^cf_(\d+)$/
575 if field =~ /^cf_(\d+)$/
558 # custom field
576 # custom field
559 filters_clauses << sql_for_custom_field(field, operator, v, $1)
577 filters_clauses << sql_for_custom_field(field, operator, v, $1)
560 elsif respond_to?("sql_for_#{field}_field")
578 elsif respond_to?("sql_for_#{field}_field")
561 # specific statement
579 # specific statement
562 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
580 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
563 else
581 else
564 # regular field
582 # regular field
565 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
583 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
566 end
584 end
567 end if filters and valid?
585 end if filters and valid?
568
586
569 filters_clauses << project_statement
587 filters_clauses << project_statement
570 filters_clauses.reject!(&:blank?)
588 filters_clauses.reject!(&:blank?)
571
589
572 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
590 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
573 end
591 end
574
592
575 # Returns the issue count
593 # Returns the issue count
576 def issue_count
594 def issue_count
577 Issue.visible.count(:include => [:status, :project], :conditions => statement)
595 Issue.visible.count(:include => [:status, :project], :conditions => statement)
578 rescue ::ActiveRecord::StatementInvalid => e
596 rescue ::ActiveRecord::StatementInvalid => e
579 raise StatementInvalid.new(e.message)
597 raise StatementInvalid.new(e.message)
580 end
598 end
581
599
582 # Returns the issue count by group or nil if query is not grouped
600 # Returns the issue count by group or nil if query is not grouped
583 def issue_count_by_group
601 def issue_count_by_group
584 r = nil
602 r = nil
585 if grouped?
603 if grouped?
586 begin
604 begin
587 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
605 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
588 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
606 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
589 rescue ActiveRecord::RecordNotFound
607 rescue ActiveRecord::RecordNotFound
590 r = {nil => issue_count}
608 r = {nil => issue_count}
591 end
609 end
592 c = group_by_column
610 c = group_by_column
593 if c.is_a?(QueryCustomFieldColumn)
611 if c.is_a?(QueryCustomFieldColumn)
594 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
612 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
595 end
613 end
596 end
614 end
597 r
615 r
598 rescue ::ActiveRecord::StatementInvalid => e
616 rescue ::ActiveRecord::StatementInvalid => e
599 raise StatementInvalid.new(e.message)
617 raise StatementInvalid.new(e.message)
600 end
618 end
601
619
602 # Returns the issues
620 # Returns the issues
603 # Valid options are :order, :offset, :limit, :include, :conditions
621 # Valid options are :order, :offset, :limit, :include, :conditions
604 def issues(options={})
622 def issues(options={})
605 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
623 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
606 order_option = nil if order_option.blank?
624 order_option = nil if order_option.blank?
607
625
608 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
626 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
609 :conditions => statement,
627 :conditions => statement,
610 :order => order_option,
628 :order => order_option,
611 :joins => joins_for_order_statement(order_option),
629 :joins => joins_for_order_statement(order_option),
612 :limit => options[:limit],
630 :limit => options[:limit],
613 :offset => options[:offset]
631 :offset => options[:offset]
614
632
615 if has_column?(:spent_hours)
633 if has_column?(:spent_hours)
616 Issue.load_visible_spent_hours(issues)
634 Issue.load_visible_spent_hours(issues)
617 end
635 end
618 issues
636 issues
619 rescue ::ActiveRecord::StatementInvalid => e
637 rescue ::ActiveRecord::StatementInvalid => e
620 raise StatementInvalid.new(e.message)
638 raise StatementInvalid.new(e.message)
621 end
639 end
622
640
623 # Returns the issues ids
641 # Returns the issues ids
624 def issue_ids(options={})
642 def issue_ids(options={})
625 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
643 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
626 order_option = nil if order_option.blank?
644 order_option = nil if order_option.blank?
627
645
628 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
646 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
629 :conditions => statement,
647 :conditions => statement,
630 :order => order_option,
648 :order => order_option,
631 :joins => joins_for_order_statement(order_option),
649 :joins => joins_for_order_statement(order_option),
632 :limit => options[:limit],
650 :limit => options[:limit],
633 :offset => options[:offset]).find_ids
651 :offset => options[:offset]).find_ids
634 rescue ::ActiveRecord::StatementInvalid => e
652 rescue ::ActiveRecord::StatementInvalid => e
635 raise StatementInvalid.new(e.message)
653 raise StatementInvalid.new(e.message)
636 end
654 end
637
655
638 # Returns the journals
656 # Returns the journals
639 # Valid options are :order, :offset, :limit
657 # Valid options are :order, :offset, :limit
640 def journals(options={})
658 def journals(options={})
641 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
659 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
642 :conditions => statement,
660 :conditions => statement,
643 :order => options[:order],
661 :order => options[:order],
644 :limit => options[:limit],
662 :limit => options[:limit],
645 :offset => options[:offset]
663 :offset => options[:offset]
646 rescue ::ActiveRecord::StatementInvalid => e
664 rescue ::ActiveRecord::StatementInvalid => e
647 raise StatementInvalid.new(e.message)
665 raise StatementInvalid.new(e.message)
648 end
666 end
649
667
650 # Returns the versions
668 # Returns the versions
651 # Valid options are :conditions
669 # Valid options are :conditions
652 def versions(options={})
670 def versions(options={})
653 Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement
671 Version.visible.scoped(:conditions => options[:conditions]).find :all, :include => :project, :conditions => project_statement
654 rescue ::ActiveRecord::StatementInvalid => e
672 rescue ::ActiveRecord::StatementInvalid => e
655 raise StatementInvalid.new(e.message)
673 raise StatementInvalid.new(e.message)
656 end
674 end
657
675
658 def sql_for_watcher_id_field(field, operator, value)
676 def sql_for_watcher_id_field(field, operator, value)
659 db_table = Watcher.table_name
677 db_table = Watcher.table_name
660 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
678 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
661 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
679 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
662 end
680 end
663
681
664 def sql_for_member_of_group_field(field, operator, value)
682 def sql_for_member_of_group_field(field, operator, value)
665 if operator == '*' # Any group
683 if operator == '*' # Any group
666 groups = Group.all
684 groups = Group.all
667 operator = '=' # Override the operator since we want to find by assigned_to
685 operator = '=' # Override the operator since we want to find by assigned_to
668 elsif operator == "!*"
686 elsif operator == "!*"
669 groups = Group.all
687 groups = Group.all
670 operator = '!' # Override the operator since we want to find by assigned_to
688 operator = '!' # Override the operator since we want to find by assigned_to
671 else
689 else
672 groups = Group.find_all_by_id(value)
690 groups = Group.find_all_by_id(value)
673 end
691 end
674 groups ||= []
692 groups ||= []
675
693
676 members_of_groups = groups.inject([]) {|user_ids, group|
694 members_of_groups = groups.inject([]) {|user_ids, group|
677 if group && group.user_ids.present?
695 if group && group.user_ids.present?
678 user_ids << group.user_ids
696 user_ids << group.user_ids
679 end
697 end
680 user_ids.flatten.uniq.compact
698 user_ids.flatten.uniq.compact
681 }.sort.collect(&:to_s)
699 }.sort.collect(&:to_s)
682
700
683 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
701 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
684 end
702 end
685
703
686 def sql_for_assigned_to_role_field(field, operator, value)
704 def sql_for_assigned_to_role_field(field, operator, value)
687 case operator
705 case operator
688 when "*", "!*" # Member / Not member
706 when "*", "!*" # Member / Not member
689 sw = operator == "!*" ? 'NOT' : ''
707 sw = operator == "!*" ? 'NOT' : ''
690 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
708 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
691 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
709 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
692 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
710 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
693 when "=", "!"
711 when "=", "!"
694 role_cond = value.any? ?
712 role_cond = value.any? ?
695 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
713 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
696 "1=0"
714 "1=0"
697
715
698 sw = operator == "!" ? 'NOT' : ''
716 sw = operator == "!" ? 'NOT' : ''
699 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
717 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
700 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
718 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
701 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
719 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
702 end
720 end
703 end
721 end
704
722
705 def sql_for_is_private_field(field, operator, value)
723 def sql_for_is_private_field(field, operator, value)
706 op = (operator == "=" ? 'IN' : 'NOT IN')
724 op = (operator == "=" ? 'IN' : 'NOT IN')
707 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
725 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
708
726
709 "#{Issue.table_name}.is_private #{op} (#{va})"
727 "#{Issue.table_name}.is_private #{op} (#{va})"
710 end
728 end
711
729
712 private
730 private
713
731
714 def sql_for_custom_field(field, operator, value, custom_field_id)
732 def sql_for_custom_field(field, operator, value, custom_field_id)
715 db_table = CustomValue.table_name
733 db_table = CustomValue.table_name
716 db_field = 'value'
734 db_field = 'value'
717 filter = @available_filters[field]
735 filter = @available_filters[field]
718 if filter && filter[:format] == 'user'
736 if filter && filter[:format] == 'user'
719 if value.delete('me')
737 if value.delete('me')
720 value.push User.current.id.to_s
738 value.push User.current.id.to_s
721 end
739 end
722 end
740 end
723 not_in = nil
741 not_in = nil
724 if operator == '!'
742 if operator == '!'
725 # Makes ! operator work for custom fields with multiple values
743 # Makes ! operator work for custom fields with multiple values
726 operator = '='
744 operator = '='
727 not_in = 'NOT'
745 not_in = 'NOT'
728 end
746 end
729 "#{Issue.table_name}.id #{not_in} 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=#{custom_field_id} WHERE " +
747 "#{Issue.table_name}.id #{not_in} 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=#{custom_field_id} WHERE " +
730 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
748 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
731 end
749 end
732
750
733 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
751 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
734 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
752 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
735 sql = ''
753 sql = ''
736 case operator
754 case operator
737 when "="
755 when "="
738 if value.any?
756 if value.any?
739 case type_for(field)
757 case type_for(field)
740 when :date, :date_past
758 when :date, :date_past
741 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
759 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
742 when :integer
760 when :integer
743 if is_custom_filter
761 if is_custom_filter
744 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
762 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) = #{value.first.to_i})"
745 else
763 else
746 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
764 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
747 end
765 end
748 when :float
766 when :float
749 if is_custom_filter
767 if is_custom_filter
750 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
768 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
751 else
769 else
752 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
770 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
753 end
771 end
754 else
772 else
755 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
773 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
756 end
774 end
757 else
775 else
758 # IN an empty set
776 # IN an empty set
759 sql = "1=0"
777 sql = "1=0"
760 end
778 end
761 when "!"
779 when "!"
762 if value.any?
780 if value.any?
763 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
781 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
764 else
782 else
765 # NOT IN an empty set
783 # NOT IN an empty set
766 sql = "1=1"
784 sql = "1=1"
767 end
785 end
768 when "!*"
786 when "!*"
769 sql = "#{db_table}.#{db_field} IS NULL"
787 sql = "#{db_table}.#{db_field} IS NULL"
770 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
788 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
771 when "*"
789 when "*"
772 sql = "#{db_table}.#{db_field} IS NOT NULL"
790 sql = "#{db_table}.#{db_field} IS NOT NULL"
773 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
791 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
774 when ">="
792 when ">="
775 if [:date, :date_past].include?(type_for(field))
793 if [:date, :date_past].include?(type_for(field))
776 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
794 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
777 else
795 else
778 if is_custom_filter
796 if is_custom_filter
779 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
797 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f})"
780 else
798 else
781 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
799 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
782 end
800 end
783 end
801 end
784 when "<="
802 when "<="
785 if [:date, :date_past].include?(type_for(field))
803 if [:date, :date_past].include?(type_for(field))
786 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
804 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
787 else
805 else
788 if is_custom_filter
806 if is_custom_filter
789 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
807 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f})"
790 else
808 else
791 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
809 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
792 end
810 end
793 end
811 end
794 when "><"
812 when "><"
795 if [:date, :date_past].include?(type_for(field))
813 if [:date, :date_past].include?(type_for(field))
796 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
814 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
797 else
815 else
798 if is_custom_filter
816 if is_custom_filter
799 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
817 sql = "(#{db_table}.#{db_field} <> '' AND CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
800 else
818 else
801 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
819 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
802 end
820 end
803 end
821 end
804 when "o"
822 when "o"
805 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
823 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
806 when "c"
824 when "c"
807 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
825 sql = "#{Issue.table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
808 when ">t-"
826 when ">t-"
809 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
827 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
810 when "<t-"
828 when "<t-"
811 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
829 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
812 when "t-"
830 when "t-"
813 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
831 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
814 when ">t+"
832 when ">t+"
815 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
833 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
816 when "<t+"
834 when "<t+"
817 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
835 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
818 when "t+"
836 when "t+"
819 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
837 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
820 when "t"
838 when "t"
821 sql = relative_date_clause(db_table, db_field, 0, 0)
839 sql = relative_date_clause(db_table, db_field, 0, 0)
822 when "w"
840 when "w"
823 first_day_of_week = l(:general_first_day_of_week).to_i
841 first_day_of_week = l(:general_first_day_of_week).to_i
824 day_of_week = Date.today.cwday
842 day_of_week = Date.today.cwday
825 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
843 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
826 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
844 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
827 when "~"
845 when "~"
828 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
846 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
829 when "!~"
847 when "!~"
830 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
848 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
831 else
849 else
832 raise "Unknown query operator #{operator}"
850 raise "Unknown query operator #{operator}"
833 end
851 end
834
852
835 return sql
853 return sql
836 end
854 end
837
855
838 def add_custom_fields_filters(custom_fields)
856 def add_custom_fields_filters(custom_fields)
839 @available_filters ||= {}
857 @available_filters ||= {}
840
858
841 custom_fields.select(&:is_filter?).each do |field|
859 custom_fields.select(&:is_filter?).each do |field|
842 case field.field_format
860 case field.field_format
843 when "text"
861 when "text"
844 options = { :type => :text, :order => 20 }
862 options = { :type => :text, :order => 20 }
845 when "list"
863 when "list"
846 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
864 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
847 when "date"
865 when "date"
848 options = { :type => :date, :order => 20 }
866 options = { :type => :date, :order => 20 }
849 when "bool"
867 when "bool"
850 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
868 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
851 when "int"
869 when "int"
852 options = { :type => :integer, :order => 20 }
870 options = { :type => :integer, :order => 20 }
853 when "float"
871 when "float"
854 options = { :type => :float, :order => 20 }
872 options = { :type => :float, :order => 20 }
855 when "user", "version"
873 when "user", "version"
856 next unless project
874 next unless project
857 values = field.possible_values_options(project)
875 values = field.possible_values_options(project)
858 if User.current.logged? && field.field_format == 'user'
876 if User.current.logged? && field.field_format == 'user'
859 values.unshift ["<< #{l(:label_me)} >>", "me"]
877 values.unshift ["<< #{l(:label_me)} >>", "me"]
860 end
878 end
861 options = { :type => :list_optional, :values => values, :order => 20}
879 options = { :type => :list_optional, :values => values, :order => 20}
862 else
880 else
863 options = { :type => :string, :order => 20 }
881 options = { :type => :string, :order => 20 }
864 end
882 end
865 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format })
883 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format })
866 end
884 end
867 end
885 end
868
886
869 # Returns a SQL clause for a date or datetime field.
887 # Returns a SQL clause for a date or datetime field.
870 def date_clause(table, field, from, to)
888 def date_clause(table, field, from, to)
871 s = []
889 s = []
872 if from
890 if from
873 from_yesterday = from - 1
891 from_yesterday = from - 1
874 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
892 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
875 if self.class.default_timezone == :utc
893 if self.class.default_timezone == :utc
876 from_yesterday_time = from_yesterday_time.utc
894 from_yesterday_time = from_yesterday_time.utc
877 end
895 end
878 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
896 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
879 end
897 end
880 if to
898 if to
881 to_time = Time.local(to.year, to.month, to.day)
899 to_time = Time.local(to.year, to.month, to.day)
882 if self.class.default_timezone == :utc
900 if self.class.default_timezone == :utc
883 to_time = to_time.utc
901 to_time = to_time.utc
884 end
902 end
885 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
903 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
886 end
904 end
887 s.join(' AND ')
905 s.join(' AND ')
888 end
906 end
889
907
890 # Returns a SQL clause for a date or datetime field using relative dates.
908 # Returns a SQL clause for a date or datetime field using relative dates.
891 def relative_date_clause(table, field, days_from, days_to)
909 def relative_date_clause(table, field, days_from, days_to)
892 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
910 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
893 end
911 end
894
912
895 # Additional joins required for the given sort options
913 # Additional joins required for the given sort options
896 def joins_for_order_statement(order_options)
914 def joins_for_order_statement(order_options)
897 joins = []
915 joins = []
898
916
899 if order_options
917 if order_options
900 if order_options.include?('authors')
918 if order_options.include?('authors')
901 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
919 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
902 end
920 end
903 order_options.scan(/cf_\d+/).uniq.each do |name|
921 order_options.scan(/cf_\d+/).uniq.each do |name|
904 column = available_columns.detect {|c| c.name.to_s == name}
922 column = available_columns.detect {|c| c.name.to_s == name}
905 join = column && column.custom_field.join_for_order_statement
923 join = column && column.custom_field.join_for_order_statement
906 if join
924 if join
907 joins << join
925 joins << join
908 end
926 end
909 end
927 end
910 end
928 end
911
929
912 joins.any? ? joins.join(' ') : nil
930 joins.any? ? joins.join(' ') : nil
913 end
931 end
914 end
932 end
@@ -1,53 +1,27
1 <%= javascript_tag do %>
2 var operatorLabels = <%= raw Query.operators_labels.to_json %>;
3 var operatorByType = <%= raw Query.operators_by_filter_type.to_json %>;
4 var availableFilters = <%= raw query.available_filters_as_json.to_json %>;
5 var labelDayPlural = "<%= raw escape_javascript(l(:label_day_plural)) %>";
6 $(document).ready(function(){
7 initFilters();
8 <% query.filters.each do |field, options| %>
9 addFilter("<%= field %>", <%= raw query.operator_for(field).to_json %>, <%= raw query.values_for(field).to_json %>);
10 <% end %>
11 });
12 <% end %>
13
1 <table style="width:100%">
14 <table style="width:100%">
2 <tr>
15 <tr>
3 <td>
16 <td>
4 <table>
17 <table id="filters-table">
5 <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %>
6 <% field = filter[0]
7 options = filter[1] %>
8 <tr <%= 'style="display:none;"'.html_safe unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter">
9 <td class="field">
10 <%= check_box_tag 'f[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
11 <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
12 </td>
13 <td class="operator">
14 <%= label_tag "operators_#{field}", l(:description_filter), :class => "hidden-for-sighted" %>
15 <%= select_tag "op[#{field}]", options_for_select(operators_for_select(options[:type]),
16 query.operator_for(field)), :id => "operators_#{field}",
17 :onchange => "toggle_operator('#{field}');" %>
18 </td>
19 <td class="values">
20 <div id="div_values_<%= field %>" style="display:none;">
21 <% case options[:type]
22 when :list, :list_optional, :list_status, :list_subprojects %>
23 <span class="span_values_<%= field %>">
24 <%= select_tag "v[#{field}][]", options_for_select(options[:values], query.values_for(field)), :class => "values_#{field}", :id => "values_#{field}_1", :multiple => (query.values_for(field) && query.values_for(field).length > 1) %>
25 <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('values_#{field}_1');" %>
26 </span>
27 <% when :date, :date_past %>
28 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 10, :class => "values_#{field}", :id => "values_#{field}_1" %> <%= calendar_for "values_#{field}_1" %></span>
29 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :size => 10, :class => "values_#{field}", :id => "values_#{field}_2" %> <%= calendar_for "values_#{field}_2" %></span>
30 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :size => 3, :class => "values_#{field}" %> <%= l(:label_day_plural) %></span>
31 <% when :string, :text %>
32 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}", :size => 30 %></span>
33 <% when :integer, :float %>
34 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field), :class => "values_#{field}", :id => "values_#{field}_1", :size => 6 %></span>
35 <span class="span_values_<%= field %>"><%= text_field_tag "v[#{field}][]", query.value_for(field, 1), :class => "values_#{field}", :id => "values_#{field}_2", :size => 6 %></span>
36 <% end %>
37 </div>
38 <script type="text/javascript">toggle_filter('<%= field %>');</script>
39 </td>
40 </tr>
41 <% end %>
42 </table>
18 </table>
43 </td>
19 </td>
44 <td class="add-filter">
20 <td class="add-filter">
45 <%= label_tag('add_filter_select', l(:label_filter_add)) %>
21 <%= label_tag('add_filter_select', l(:label_filter_add)) %>
46 <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact),
22 <%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %>
47 :onchange => "add_filter();",
48 :name => nil %>
49 </td>
23 </td>
50 </tr>
24 </tr>
51 </table>
25 </table>
52 <%= hidden_field_tag 'f[]', '' %>
26 <%= hidden_field_tag 'f[]', '' %>
53 <%= javascript_tag '$(document).ready(function(){observeIssueFilters();});' %>
27 <% include_calendar_headers_tags %>
@@ -1,52 +1,52
1 <%= error_messages_for 'query' %>
1 <%= error_messages_for 'query' %>
2
2
3 <div class="box">
3 <div class="box">
4 <div class="tabular">
4 <div class="tabular">
5 <p><label for="query_name"><%=l(:field_name)%></label>
5 <p><label for="query_name"><%=l(:field_name)%></label>
6 <%= text_field 'query', 'name', :size => 80 %></p>
6 <%= text_field 'query', 'name', :size => 80 %></p>
7
7
8 <% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %>
8 <% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %>
9 <p><label for="query_is_public"><%=l(:field_is_public)%></label>
9 <p><label for="query_is_public"><%=l(:field_is_public)%></label>
10 <%= check_box 'query', 'is_public',
10 <%= check_box 'query', 'is_public',
11 :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %></p>
11 :onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %></p>
12 <% end %>
12 <% end %>
13
13
14 <p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label>
14 <p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label>
15 <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?,
15 <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?,
16 :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %></p>
16 :disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %></p>
17
17
18 <p><label for="query_default_columns"><%=l(:label_default_columns)%></label>
18 <p><label for="query_default_columns"><%=l(:label_default_columns)%></label>
19 <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
19 <%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
20 :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %></p>
20 :onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %></p>
21
21
22 <p><label for="query_group_by"><%= l(:field_group_by) %></label>
22 <p><label for="query_group_by"><%= l(:field_group_by) %></label>
23 <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
23 <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
24 </div>
24 </div>
25
25
26 <fieldset><legend><%= l(:label_filter_plural) %></legend>
26 <fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
27 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
27 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
28 </fieldset>
28 </fieldset>
29
29
30 <fieldset><legend><%= l(:label_sort) %></legend>
30 <fieldset><legend><%= l(:label_sort) %></legend>
31 <% 3.times do |i| %>
31 <% 3.times do |i| %>
32 <%= i+1 %>:
32 <%= i+1 %>:
33 <%= label_tag "query_sort_criteria_attribute_" + i.to_s,
33 <%= label_tag "query_sort_criteria_attribute_" + i.to_s,
34 l(:description_query_sort_criteria_attribute), :class => "hidden-for-sighted" %>
34 l(:description_query_sort_criteria_attribute), :class => "hidden-for-sighted" %>
35 <%= select_tag("query[sort_criteria][#{i}][]",
35 <%= select_tag("query[sort_criteria][#{i}][]",
36 options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i)),
36 options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i)),
37 :id => "query_sort_criteria_attribute_" + i.to_s)%>
37 :id => "query_sort_criteria_attribute_" + i.to_s)%>
38 <%= label_tag "query_sort_criteria_direction_" + i.to_s,
38 <%= label_tag "query_sort_criteria_direction_" + i.to_s,
39 l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %>
39 l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %>
40 <%= select_tag("query[sort_criteria][#{i}][]",
40 <%= select_tag("query[sort_criteria][#{i}][]",
41 options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)),
41 options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)),
42 :id => "query_sort_criteria_direction_" + i.to_s) %>
42 :id => "query_sort_criteria_direction_" + i.to_s) %>
43 <br />
43 <br />
44 <% end %>
44 <% end %>
45 </fieldset>
45 </fieldset>
46
46
47 <%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %>
47 <%= content_tag 'fieldset', :id => 'columns', :style => (query.has_default_columns? ? 'display:none;' : nil) do %>
48 <legend><%= l(:field_column_names) %></legend>
48 <legend><%= l(:field_column_names) %></legend>
49 <%= render :partial => 'queries/columns', :locals => {:query => query}%>
49 <%= render :partial => 'queries/columns', :locals => {:query => query}%>
50 <% end %>
50 <% end %>
51
51
52 </div>
52 </div>
@@ -1,499 +1,583
1 /* Redmine - project management software
1 /* Redmine - project management software
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
2 Copyright (C) 2006-2012 Jean-Philippe Lang */
3
3
4 function checkAll(id, checked) {
4 function checkAll(id, checked) {
5 if (checked) {
5 if (checked) {
6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
6 $('#'+id).find('input[type=checkbox]').attr('checked', true);
7 } else {
7 } else {
8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
8 $('#'+id).find('input[type=checkbox]').removeAttr('checked');
9 }
9 }
10 }
10 }
11
11
12 function toggleCheckboxesBySelector(selector) {
12 function toggleCheckboxesBySelector(selector) {
13 var all_checked = true;
13 var all_checked = true;
14 $(selector).each(function(index) {
14 $(selector).each(function(index) {
15 if (!$(this).is(':checked')) { all_checked = false; }
15 if (!$(this).is(':checked')) { all_checked = false; }
16 });
16 });
17 $(selector).attr('checked', !all_checked)
17 $(selector).attr('checked', !all_checked)
18 }
18 }
19
19
20 function showAndScrollTo(id, focus) {
20 function showAndScrollTo(id, focus) {
21 $('#'+id).show();
21 $('#'+id).show();
22 if (focus!=null) {
22 if (focus!=null) {
23 $('#'+focus).focus();
23 $('#'+focus).focus();
24 }
24 }
25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
25 $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
26 }
26 }
27
27
28 function toggleRowGroup(el) {
28 function toggleRowGroup(el) {
29 var tr = $(el).parents('tr').first();
29 var tr = $(el).parents('tr').first();
30 var n = tr.next();
30 var n = tr.next();
31 tr.toggleClass('open');
31 tr.toggleClass('open');
32 while (n.length && !n.hasClass('group')) {
32 while (n.length && !n.hasClass('group')) {
33 n.toggle();
33 n.toggle();
34 n = n.next('tr');
34 n = n.next('tr');
35 }
35 }
36 }
36 }
37
37
38 function collapseAllRowGroups(el) {
38 function collapseAllRowGroups(el) {
39 var tbody = $(el).parents('tbody').first();
39 var tbody = $(el).parents('tbody').first();
40 tbody.children('tr').each(function(index) {
40 tbody.children('tr').each(function(index) {
41 if ($(this).hasClass('group')) {
41 if ($(this).hasClass('group')) {
42 $(this).removeClass('open');
42 $(this).removeClass('open');
43 } else {
43 } else {
44 $(this).hide();
44 $(this).hide();
45 }
45 }
46 });
46 });
47 }
47 }
48
48
49 function expandAllRowGroups(el) {
49 function expandAllRowGroups(el) {
50 var tbody = $(el).parents('tbody').first();
50 var tbody = $(el).parents('tbody').first();
51 tbody.children('tr').each(function(index) {
51 tbody.children('tr').each(function(index) {
52 if ($(this).hasClass('group')) {
52 if ($(this).hasClass('group')) {
53 $(this).addClass('open');
53 $(this).addClass('open');
54 } else {
54 } else {
55 $(this).show();
55 $(this).show();
56 }
56 }
57 });
57 });
58 }
58 }
59
59
60 function toggleAllRowGroups(el) {
60 function toggleAllRowGroups(el) {
61 var tr = $(el).parents('tr').first();
61 var tr = $(el).parents('tr').first();
62 if (tr.hasClass('open')) {
62 if (tr.hasClass('open')) {
63 collapseAllRowGroups(el);
63 collapseAllRowGroups(el);
64 } else {
64 } else {
65 expandAllRowGroups(el);
65 expandAllRowGroups(el);
66 }
66 }
67 }
67 }
68
68
69 function toggleFieldset(el) {
69 function toggleFieldset(el) {
70 var fieldset = $(el).parents('fieldset').first();
70 var fieldset = $(el).parents('fieldset').first();
71 fieldset.toggleClass('collapsed');
71 fieldset.toggleClass('collapsed');
72 fieldset.children('div').toggle();
72 fieldset.children('div').toggle();
73 }
73 }
74
74
75 function hideFieldset(el) {
75 function hideFieldset(el) {
76 var fieldset = $(el).parents('fieldset').first();
76 var fieldset = $(el).parents('fieldset').first();
77 fieldset.toggleClass('collapsed');
77 fieldset.toggleClass('collapsed');
78 fieldset.children('div').hide();
78 fieldset.children('div').hide();
79 }
79 }
80
80
81 function add_filter() {
81 function initFilters(){
82 var select = $('#add_filter_select');
82 $('#add_filter_select').change(function(){
83 var field = select.val();
83 addFilter($(this).val(), '', []);
84 $('#tr_'+field).show();
84 });
85 var check_box = $('#cb_' + field);
85 $('#filters-table td.field input[type=checkbox]').each(function(){
86 check_box.attr('checked', true);
86 toggleFilter($(this).val());
87 toggle_filter(field);
87 });
88 select.val('');
88 $('#filters-table td.field input[type=checkbox]').live('click',function(){
89
89 toggleFilter($(this).val());
90 select.children('option').each(function(index) {
90 });
91 $('#filters-table .toggle-multiselect').live('click',function(){
92 toggleMultiSelect($(this).siblings('select'));
93 });
94 $('#filters-table input[type=text]').live('keypress', function(e){
95 if (e.keyCode == 13) submit_query_form("query_form");
96 });
97 }
98
99 function addFilter(field, operator, values) {
100 var fieldId = field.replace('.', '_');
101 var tr = $('#tr_'+fieldId);
102 if (tr.length > 0) {
103 tr.show();
104 } else {
105 buildFilterRow(field, operator, values);
106 }
107 $('#cb_'+fieldId).attr('checked', true);
108 toggleFilter(field);
109 $('#add_filter_select').val('').children('option').each(function(){
91 if ($(this).attr('value') == field) {
110 if ($(this).attr('value') == field) {
92 $(this).attr('disabled', true);
111 $(this).attr('disabled', true);
93 }
112 }
94 });
113 });
95 }
114 }
96
115
97 function toggle_filter(field) {
116 function buildFilterRow(field, operator, values) {
98 check_box = $('#cb_' + field);
117 var fieldId = field.replace('.', '_');
99 if (check_box.is(':checked')) {
118 var filterTable = $("#filters-table");
100 $("#operators_" + field).show().removeAttr('disabled');
119 var filterOptions = availableFilters[field];
101 toggle_operator(field);
120 var operators = operatorByType[filterOptions['type']];
121 var filterValues = filterOptions['values'];
122 var i, select;
123
124 var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
125 '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
126 '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
127 '<td class="values"></td>'
128 );
129 filterTable.append(tr);
130
131 select = tr.find('td.operator select');
132 for (i=0;i<operators.length;i++){
133 var option = $('<option>').val(operators[i]).html(operatorLabels[operators[i]]);
134 if (operators[i] == operator) {option.attr('selected', true)};
135 select.append(option);
136 }
137 select.change(function(){toggleOperator(field)});
138
139 switch (filterOptions['type']){
140 case "list":
141 case "list_optional":
142 case "list_status":
143 case "list_subprojects":
144 tr.find('td.values').append(
145 '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
146 ' <span class="toggle-multiselect">&nbsp;</span></span>'
147 );
148 select = tr.find('td.values select');
149 if (values.length > 1) {select.attr('multiple', true)};
150 for (i=0;i<filterValues.length;i++){
151 var filterValue = filterValues[i];
152 var option = $('<option>');
153 if ($.isArray(filterValue)) {
154 option.val(filterValue[1]).html(filterValue[0]);
155 } else {
156 option.val(filterValue).html(filterValue);
157 }
158 if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)};
159 select.append(option);
160 }
161 break;
162 case "date":
163 case "date_past":
164 tr.find('td.values').append(
165 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" value="'+values[0]+'" /></span>' +
166 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" value="'+values[1]+'" /></span>' +
167 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" value="'+values[0]+'" /> '+labelDayPlural+'</span>'
168 );
169 $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
170 $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
171 $('#values_'+fieldId).val(values[0]);
172 break;
173 case "string":
174 case "text":
175 tr.find('td.values').append(
176 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" value="'+values[0]+'" /></span>'
177 );
178 $('#values_'+fieldId).val(values[0]);
179 break;
180 case "integer":
181 case "float":
182 tr.find('td.values').append(
183 '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" value="'+values[0]+'" /></span>' +
184 ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" value="'+values[1]+'" /></span>'
185 );
186 $('#values_'+fieldId+'_1').val(values[0]);
187 $('#values_'+fieldId+'_2').val(values[1]);
188 break;
189 }
190 }
191
192 function toggleFilter(field) {
193 var fieldId = field.replace('.', '_');
194 if ($('#cb_' + fieldId).is(':checked')) {
195 $("#operators_" + fieldId).show().removeAttr('disabled');
196 toggleOperator(field);
102 } else {
197 } else {
103 $("#operators_" + field).hide().attr('disabled', true);
198 $("#operators_" + fieldId).hide().attr('disabled', true);
104 enableValues(field, []);
199 enableValues(field, []);
105 }
200 }
106 }
201 }
107
202
108 function enableValues(field, indexes) {
203 function enableValues(field, indexes) {
109 $(".values_" + field).each(function(index) {
204 var fieldId = field.replace('.', '_');
205 $('#tr_'+fieldId+' td.values .value').each(function(index) {
110 if (indexes.indexOf(index) >= 0) {
206 if (indexes.indexOf(index) >= 0) {
111 $(this).removeAttr('disabled');
207 $(this).removeAttr('disabled');
112 $(this).parents('span').first().show();
208 $(this).parents('span').first().show();
113 } else {
209 } else {
114 $(this).val('');
210 $(this).val('');
115 $(this).attr('disabled', true);
211 $(this).attr('disabled', true);
116 $(this).parents('span').first().hide();
212 $(this).parents('span').first().hide();
117 }
213 }
118
214
119 if ($(this).hasClass('group')) {
215 if ($(this).hasClass('group')) {
120 $(this).addClass('open');
216 $(this).addClass('open');
121 } else {
217 } else {
122 $(this).show();
218 $(this).show();
123 }
219 }
124 });
220 });
125
126 if (indexes.length > 0) {
127 $("#div_values_" + field).show();
128 } else {
129 $("#div_values_" + field).hide();
130 }
131 }
221 }
132
222
133 function toggle_operator(field) {
223 function toggleOperator(field) {
134 operator = $("#operators_" + field);
224 var fieldId = field.replace('.', '_');
225 var operator = $("#operators_" + fieldId);
135 switch (operator.val()) {
226 switch (operator.val()) {
136 case "!*":
227 case "!*":
137 case "*":
228 case "*":
138 case "t":
229 case "t":
139 case "w":
230 case "w":
140 case "o":
231 case "o":
141 case "c":
232 case "c":
142 enableValues(field, []);
233 enableValues(field, []);
143 break;
234 break;
144 case "><":
235 case "><":
145 enableValues(field, [0,1]);
236 enableValues(field, [0,1]);
146 break;
237 break;
147 case "<t+":
238 case "<t+":
148 case ">t+":
239 case ">t+":
149 case "t+":
240 case "t+":
150 case ">t-":
241 case ">t-":
151 case "<t-":
242 case "<t-":
152 case "t-":
243 case "t-":
153 enableValues(field, [2]);
244 enableValues(field, [2]);
154 break;
245 break;
155 default:
246 default:
156 enableValues(field, [0]);
247 enableValues(field, [0]);
157 break;
248 break;
158 }
249 }
159 }
250 }
160
251
161 function toggle_multi_select(id) {
252 function toggleMultiSelect(el) {
162 var select = $('#'+id);
253 if (el.attr('multiple')) {
163 if (select.attr('multiple')) {
254 el.removeAttr('multiple');
164 select.removeAttr('multiple');
165 } else {
255 } else {
166 select.attr('multiple', true);
256 el.attr('multiple', true);
167 }
257 }
168 }
258 }
169
259
170 function submit_query_form(id) {
260 function submit_query_form(id) {
171 selectAllOptions("selected_columns");
261 selectAllOptions("selected_columns");
172 $('#'+id).submit();
262 $('#'+id).submit();
173 }
263 }
174
264
175 function observeIssueFilters() {
176 $('#query_form input[type=text]').keypress(function(e){
177 if (e.keyCode == 13) submit_query_form("query_form");
178 });
179 }
180
181 var fileFieldCount = 1;
265 var fileFieldCount = 1;
182 function addFileField() {
266 function addFileField() {
183 var fields = $('#attachments_fields');
267 var fields = $('#attachments_fields');
184 if (fields.children().length >= 10) return false;
268 if (fields.children().length >= 10) return false;
185 fileFieldCount++;
269 fileFieldCount++;
186 var s = fields.children('span').first().clone();
270 var s = fields.children('span').first().clone();
187 s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
271 s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val('');
188 s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
272 s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val('');
189 fields.append(s);
273 fields.append(s);
190 }
274 }
191
275
192 function removeFileField(el) {
276 function removeFileField(el) {
193 var fields = $('#attachments_fields');
277 var fields = $('#attachments_fields');
194 var s = $(el).parents('span').first();
278 var s = $(el).parents('span').first();
195 if (fields.children().length > 1) {
279 if (fields.children().length > 1) {
196 s.remove();
280 s.remove();
197 } else {
281 } else {
198 s.children('input.file').val('');
282 s.children('input.file').val('');
199 s.children('input.description').val('');
283 s.children('input.description').val('');
200 }
284 }
201 }
285 }
202
286
203 function checkFileSize(el, maxSize, message) {
287 function checkFileSize(el, maxSize, message) {
204 var files = el.files;
288 var files = el.files;
205 if (files) {
289 if (files) {
206 for (var i=0; i<files.length; i++) {
290 for (var i=0; i<files.length; i++) {
207 if (files[i].size > maxSize) {
291 if (files[i].size > maxSize) {
208 alert(message);
292 alert(message);
209 el.value = "";
293 el.value = "";
210 }
294 }
211 }
295 }
212 }
296 }
213 }
297 }
214
298
215 function showTab(name) {
299 function showTab(name) {
216 $('div#content .tab-content').hide();
300 $('div#content .tab-content').hide();
217 $('div.tabs a').removeClass('selected');
301 $('div.tabs a').removeClass('selected');
218 $('#tab-content-' + name).show();
302 $('#tab-content-' + name).show();
219 $('#tab-' + name).addClass('selected');
303 $('#tab-' + name).addClass('selected');
220 return false;
304 return false;
221 }
305 }
222
306
223 function moveTabRight(el) {
307 function moveTabRight(el) {
224 var lis = $(el).parents('div.tabs').first().find('ul').children();
308 var lis = $(el).parents('div.tabs').first().find('ul').children();
225 var tabsWidth = 0;
309 var tabsWidth = 0;
226 var i = 0;
310 var i = 0;
227 lis.each(function(){
311 lis.each(function(){
228 if ($(this).is(':visible')) {
312 if ($(this).is(':visible')) {
229 tabsWidth += $(this).width() + 6;
313 tabsWidth += $(this).width() + 6;
230 }
314 }
231 });
315 });
232 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
316 if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; }
233 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
317 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
234 lis.eq(i).hide();
318 lis.eq(i).hide();
235 }
319 }
236
320
237 function moveTabLeft(el) {
321 function moveTabLeft(el) {
238 var lis = $(el).parents('div.tabs').first().find('ul').children();
322 var lis = $(el).parents('div.tabs').first().find('ul').children();
239 var i = 0;
323 var i = 0;
240 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
324 while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
241 if (i>0) {
325 if (i>0) {
242 lis.eq(i-1).show();
326 lis.eq(i-1).show();
243 }
327 }
244 }
328 }
245
329
246 function displayTabsButtons() {
330 function displayTabsButtons() {
247 var lis;
331 var lis;
248 var tabsWidth = 0;
332 var tabsWidth = 0;
249 var el;
333 var el;
250 $('div.tabs').each(function() {
334 $('div.tabs').each(function() {
251 el = $(this);
335 el = $(this);
252 lis = el.find('ul').children();
336 lis = el.find('ul').children();
253 lis.each(function(){
337 lis.each(function(){
254 if ($(this).is(':visible')) {
338 if ($(this).is(':visible')) {
255 tabsWidth += $(this).width() + 6;
339 tabsWidth += $(this).width() + 6;
256 }
340 }
257 });
341 });
258 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
342 if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) {
259 el.find('div.tabs-buttons').hide();
343 el.find('div.tabs-buttons').hide();
260 } else {
344 } else {
261 el.find('div.tabs-buttons').show();
345 el.find('div.tabs-buttons').show();
262 }
346 }
263 });
347 });
264 }
348 }
265
349
266 function setPredecessorFieldsVisibility() {
350 function setPredecessorFieldsVisibility() {
267 var relationType = $('#relation_relation_type');
351 var relationType = $('#relation_relation_type');
268 if (relationType.val() == "precedes" || relationType.val() == "follows") {
352 if (relationType.val() == "precedes" || relationType.val() == "follows") {
269 $('#predecessor_fields').show();
353 $('#predecessor_fields').show();
270 } else {
354 } else {
271 $('#predecessor_fields').hide();
355 $('#predecessor_fields').hide();
272 }
356 }
273 }
357 }
274
358
275 function showModal(id, width) {
359 function showModal(id, width) {
276 var el = $('#'+id).first();
360 var el = $('#'+id).first();
277 if (el.length == 0 || el.is(':visible')) {return;}
361 if (el.length == 0 || el.is(':visible')) {return;}
278 var title = el.find('h3.title').text();
362 var title = el.find('h3.title').text();
279 el.dialog({
363 el.dialog({
280 width: width,
364 width: width,
281 modal: true,
365 modal: true,
282 resizable: false,
366 resizable: false,
283 dialogClass: 'modal',
367 dialogClass: 'modal',
284 title: title
368 title: title
285 });
369 });
286 el.find("input[type=text], input[type=submit]").first().focus();
370 el.find("input[type=text], input[type=submit]").first().focus();
287 }
371 }
288
372
289 function hideModal(el) {
373 function hideModal(el) {
290 var modal;
374 var modal;
291 if (el) {
375 if (el) {
292 modal = $(el).parents('.ui-dialog-content');
376 modal = $(el).parents('.ui-dialog-content');
293 } else {
377 } else {
294 modal = $('#ajax-modal');
378 modal = $('#ajax-modal');
295 }
379 }
296 modal.dialog("close");
380 modal.dialog("close");
297 }
381 }
298
382
299 function submitPreview(url, form, target) {
383 function submitPreview(url, form, target) {
300 $.ajax({
384 $.ajax({
301 url: url,
385 url: url,
302 type: 'post',
386 type: 'post',
303 data: $('#'+form).serialize(),
387 data: $('#'+form).serialize(),
304 success: function(data){
388 success: function(data){
305 $('#'+target).html(data);
389 $('#'+target).html(data);
306 }
390 }
307 });
391 });
308 }
392 }
309
393
310 function collapseScmEntry(id) {
394 function collapseScmEntry(id) {
311 $('.'+id).each(function() {
395 $('.'+id).each(function() {
312 if ($(this).hasClass('open')) {
396 if ($(this).hasClass('open')) {
313 collapseScmEntry($(this).attr('id'));
397 collapseScmEntry($(this).attr('id'));
314 }
398 }
315 $(this).hide();
399 $(this).hide();
316 });
400 });
317 $('#'+id).removeClass('open');
401 $('#'+id).removeClass('open');
318 }
402 }
319
403
320 function expandScmEntry(id) {
404 function expandScmEntry(id) {
321 $('.'+id).each(function() {
405 $('.'+id).each(function() {
322 $(this).show();
406 $(this).show();
323 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
407 if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
324 expandScmEntry($(this).attr('id'));
408 expandScmEntry($(this).attr('id'));
325 }
409 }
326 });
410 });
327 $('#'+id).addClass('open');
411 $('#'+id).addClass('open');
328 }
412 }
329
413
330 function scmEntryClick(id, url) {
414 function scmEntryClick(id, url) {
331 el = $('#'+id);
415 el = $('#'+id);
332 if (el.hasClass('open')) {
416 if (el.hasClass('open')) {
333 collapseScmEntry(id);
417 collapseScmEntry(id);
334 el.addClass('collapsed');
418 el.addClass('collapsed');
335 return false;
419 return false;
336 } else if (el.hasClass('loaded')) {
420 } else if (el.hasClass('loaded')) {
337 expandScmEntry(id);
421 expandScmEntry(id);
338 el.removeClass('collapsed');
422 el.removeClass('collapsed');
339 return false;
423 return false;
340 }
424 }
341 if (el.hasClass('loading')) {
425 if (el.hasClass('loading')) {
342 return false;
426 return false;
343 }
427 }
344 el.addClass('loading');
428 el.addClass('loading');
345 $.ajax({
429 $.ajax({
346 url: url,
430 url: url,
347 success: function(data){
431 success: function(data){
348 el.after(data);
432 el.after(data);
349 el.addClass('open').addClass('loaded').removeClass('loading');
433 el.addClass('open').addClass('loaded').removeClass('loading');
350 }
434 }
351 });
435 });
352 return true;
436 return true;
353 }
437 }
354
438
355 function randomKey(size) {
439 function randomKey(size) {
356 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
440 var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
357 var key = '';
441 var key = '';
358 for (i = 0; i < size; i++) {
442 for (i = 0; i < size; i++) {
359 key += chars[Math.floor(Math.random() * chars.length)];
443 key += chars[Math.floor(Math.random() * chars.length)];
360 }
444 }
361 return key;
445 return key;
362 }
446 }
363
447
364 // Can't use Rails' remote select because we need the form data
448 // Can't use Rails' remote select because we need the form data
365 function updateIssueFrom(url) {
449 function updateIssueFrom(url) {
366 $.ajax({
450 $.ajax({
367 url: url,
451 url: url,
368 type: 'post',
452 type: 'post',
369 data: $('#issue-form').serialize()
453 data: $('#issue-form').serialize()
370 });
454 });
371 }
455 }
372
456
373 function updateBulkEditFrom(url) {
457 function updateBulkEditFrom(url) {
374 $.ajax({
458 $.ajax({
375 url: url,
459 url: url,
376 type: 'post',
460 type: 'post',
377 data: $('#bulk_edit_form').serialize()
461 data: $('#bulk_edit_form').serialize()
378 });
462 });
379 }
463 }
380
464
381 function observeAutocompleteField(fieldId, url) {
465 function observeAutocompleteField(fieldId, url) {
382 $('#'+fieldId).autocomplete({
466 $('#'+fieldId).autocomplete({
383 source: url,
467 source: url,
384 minLength: 2,
468 minLength: 2,
385 });
469 });
386 }
470 }
387
471
388 function observeSearchfield(fieldId, targetId, url) {
472 function observeSearchfield(fieldId, targetId, url) {
389 $('#'+fieldId).each(function() {
473 $('#'+fieldId).each(function() {
390 var $this = $(this);
474 var $this = $(this);
391 $this.attr('data-value-was', $this.val());
475 $this.attr('data-value-was', $this.val());
392 var check = function() {
476 var check = function() {
393 var val = $this.val();
477 var val = $this.val();
394 if ($this.attr('data-value-was') != val){
478 if ($this.attr('data-value-was') != val){
395 $this.attr('data-value-was', val);
479 $this.attr('data-value-was', val);
396 if (val != '') {
480 if (val != '') {
397 $.ajax({
481 $.ajax({
398 url: url,
482 url: url,
399 type: 'get',
483 type: 'get',
400 data: {q: $this.val()},
484 data: {q: $this.val()},
401 success: function(data){ $('#'+targetId).html(data); },
485 success: function(data){ $('#'+targetId).html(data); },
402 beforeSend: function(){ $this.addClass('ajax-loading'); },
486 beforeSend: function(){ $this.addClass('ajax-loading'); },
403 complete: function(){ $this.removeClass('ajax-loading'); }
487 complete: function(){ $this.removeClass('ajax-loading'); }
404 });
488 });
405 }
489 }
406 }
490 }
407 };
491 };
408 var reset = function() {
492 var reset = function() {
409 if (timer) {
493 if (timer) {
410 clearInterval(timer);
494 clearInterval(timer);
411 timer = setInterval(check, 300);
495 timer = setInterval(check, 300);
412 }
496 }
413 };
497 };
414 var timer = setInterval(check, 300);
498 var timer = setInterval(check, 300);
415 $this.bind('keyup click mousemove', reset);
499 $this.bind('keyup click mousemove', reset);
416 });
500 });
417 }
501 }
418
502
419 function observeProjectModules() {
503 function observeProjectModules() {
420 var f = function() {
504 var f = function() {
421 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
505 /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */
422 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
506 if ($('#project_enabled_module_names_issue_tracking').attr('checked')) {
423 $('#project_trackers').show();
507 $('#project_trackers').show();
424 }else{
508 }else{
425 $('#project_trackers').hide();
509 $('#project_trackers').hide();
426 }
510 }
427 };
511 };
428
512
429 $(window).load(f);
513 $(window).load(f);
430 $('#project_enabled_module_names_issue_tracking').change(f);
514 $('#project_enabled_module_names_issue_tracking').change(f);
431 }
515 }
432
516
433 function initMyPageSortable(list, url) {
517 function initMyPageSortable(list, url) {
434 $('#list-'+list).sortable({
518 $('#list-'+list).sortable({
435 connectWith: '.block-receiver',
519 connectWith: '.block-receiver',
436 tolerance: 'pointer',
520 tolerance: 'pointer',
437 update: function(){
521 update: function(){
438 $.ajax({
522 $.ajax({
439 url: url,
523 url: url,
440 type: 'post',
524 type: 'post',
441 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
525 data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})}
442 });
526 });
443 }
527 }
444 });
528 });
445 $("#list-top, #list-left, #list-right").disableSelection();
529 $("#list-top, #list-left, #list-right").disableSelection();
446 }
530 }
447
531
448 var warnLeavingUnsavedMessage;
532 var warnLeavingUnsavedMessage;
449 function warnLeavingUnsaved(message) {
533 function warnLeavingUnsaved(message) {
450 warnLeavingUnsavedMessage = message;
534 warnLeavingUnsavedMessage = message;
451
535
452 $('form').submit(function(){
536 $('form').submit(function(){
453 $('textarea').removeData('changed');
537 $('textarea').removeData('changed');
454 });
538 });
455 $('textarea').change(function(){
539 $('textarea').change(function(){
456 $(this).data('changed', 'changed');
540 $(this).data('changed', 'changed');
457 });
541 });
458 window.onbeforeunload = function(){
542 window.onbeforeunload = function(){
459 var warn = false;
543 var warn = false;
460 $('textarea').blur().each(function(){
544 $('textarea').blur().each(function(){
461 if ($(this).data('changed')) {
545 if ($(this).data('changed')) {
462 warn = true;
546 warn = true;
463 }
547 }
464 });
548 });
465 if (warn) {return warnLeavingUnsavedMessage;}
549 if (warn) {return warnLeavingUnsavedMessage;}
466 };
550 };
467 };
551 };
468
552
469 $(document).ready(function(){
553 $(document).ready(function(){
470 $('#ajax-indicator').bind('ajaxSend', function(){
554 $('#ajax-indicator').bind('ajaxSend', function(){
471 if ($('.ajax-loading').length == 0) {
555 if ($('.ajax-loading').length == 0) {
472 $('#ajax-indicator').show();
556 $('#ajax-indicator').show();
473 }
557 }
474 });
558 });
475 $('#ajax-indicator').bind('ajaxStop', function(){
559 $('#ajax-indicator').bind('ajaxStop', function(){
476 $('#ajax-indicator').hide();
560 $('#ajax-indicator').hide();
477 });
561 });
478 });
562 });
479
563
480 function hideOnLoad() {
564 function hideOnLoad() {
481 $('.hol').hide();
565 $('.hol').hide();
482 }
566 }
483
567
484 function addFormObserversForDoubleSubmit() {
568 function addFormObserversForDoubleSubmit() {
485 $('form[method=post]').each(function() {
569 $('form[method=post]').each(function() {
486 if (!$(this).hasClass('multiple-submit')) {
570 if (!$(this).hasClass('multiple-submit')) {
487 $(this).submit(function(form_submission) {
571 $(this).submit(function(form_submission) {
488 if ($(form_submission.target).attr('data-submitted')) {
572 if ($(form_submission.target).attr('data-submitted')) {
489 form_submission.preventDefault();
573 form_submission.preventDefault();
490 } else {
574 } else {
491 $(form_submission.target).attr('data-submitted', true);
575 $(form_submission.target).attr('data-submitted', true);
492 }
576 }
493 });
577 });
494 }
578 }
495 });
579 });
496 }
580 }
497
581
498 $(document).ready(hideOnLoad);
582 $(document).ready(hideOnLoad);
499 $(document).ready(addFormObserversForDoubleSubmit);
583 $(document).ready(addFormObserversForDoubleSubmit);
@@ -1,1124 +1,1125
1 html {overflow-y:scroll;}
1 html {overflow-y:scroll;}
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3
3
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
5 h1 {margin:0; padding:0; font-size: 24px;}
5 h1 {margin:0; padding:0; font-size: 24px;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; color: #444;}
8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
9
9
10 /***** Layout *****/
10 /***** Layout *****/
11 #wrapper {background: white;}
11 #wrapper {background: white;}
12
12
13 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
14 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu ul {margin: 0; padding: 0;}
15 #top-menu li {
15 #top-menu li {
16 float:left;
16 float:left;
17 list-style-type:none;
17 list-style-type:none;
18 margin: 0px 0px 0px 0px;
18 margin: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
20 white-space:nowrap;
20 white-space:nowrap;
21 }
21 }
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
24
24
25 #account {float:right;}
25 #account {float:right;}
26
26
27 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header {height:5.3em;margin:0;background-color:#628DB6;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
28 #header a {color:#f8f8f8;}
28 #header a {color:#f8f8f8;}
29 #header h1 a.ancestor { font-size: 80%; }
29 #header h1 a.ancestor { font-size: 80%; }
30 #quick-search {float:right;}
30 #quick-search {float:right;}
31
31
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
33 #main-menu ul {margin: 0; padding: 0;}
33 #main-menu ul {margin: 0; padding: 0;}
34 #main-menu li {
34 #main-menu li {
35 float:left;
35 float:left;
36 list-style-type:none;
36 list-style-type:none;
37 margin: 0px 2px 0px 0px;
37 margin: 0px 2px 0px 0px;
38 padding: 0px 0px 0px 0px;
38 padding: 0px 0px 0px 0px;
39 white-space:nowrap;
39 white-space:nowrap;
40 }
40 }
41 #main-menu li a {
41 #main-menu li a {
42 display: block;
42 display: block;
43 color: #fff;
43 color: #fff;
44 text-decoration: none;
44 text-decoration: none;
45 font-weight: bold;
45 font-weight: bold;
46 margin: 0;
46 margin: 0;
47 padding: 4px 10px 4px 10px;
47 padding: 4px 10px 4px 10px;
48 }
48 }
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
51
51
52 #admin-menu ul {margin: 0; padding: 0;}
52 #admin-menu ul {margin: 0; padding: 0;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
54
54
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
69
69
70 #main {background-color:#EEEEEE;}
70 #main {background-color:#EEEEEE;}
71
71
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
73 * html #sidebar{ width: 22%; }
73 * html #sidebar{ width: 22%; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
77 #sidebar .contextual { margin-right: 1em; }
77 #sidebar .contextual { margin-right: 1em; }
78
78
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
81 html>body #content { min-height: 600px; }
81 html>body #content { min-height: 600px; }
82 * html body #content { height: 600px; } /* IE */
82 * html body #content { height: 600px; } /* IE */
83
83
84 #main.nosidebar #sidebar{ display: none; }
84 #main.nosidebar #sidebar{ display: none; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
86
86
87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
88
88
89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
90 #login-form table td {padding: 6px;}
90 #login-form table td {padding: 6px;}
91 #login-form label {font-weight: bold;}
91 #login-form label {font-weight: bold;}
92 #login-form input#username, #login-form input#password { width: 300px; }
92 #login-form input#username, #login-form input#password { width: 300px; }
93
93
94 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
94 div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
95 div.modal h3.title {display:none;}
95 div.modal h3.title {display:none;}
96 div.modal p.buttons {text-align:right; margin-bottom:0;}
96 div.modal p.buttons {text-align:right; margin-bottom:0;}
97
97
98 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
98 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
99
99
100 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
100 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
101
101
102 /***** Links *****/
102 /***** Links *****/
103 a, a:link, a:visited{ color: #169; text-decoration: none; }
103 a, a:link, a:visited{ color: #169; text-decoration: none; }
104 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
104 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
105 a img{ border: 0; }
105 a img{ border: 0; }
106
106
107 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
107 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
108 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
108 a.project.closed, a.project.closed:link, a.project.closed:visited { color: #999; }
109
109
110 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
110 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px;}
111 #sidebar a.selected:hover {text-decoration:none;}
111 #sidebar a.selected:hover {text-decoration:none;}
112 #admin-menu a {line-height:1.7em;}
112 #admin-menu a {line-height:1.7em;}
113 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
113 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
114
114
115 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
115 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
116 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
116 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
117
117
118 a#toggle-completed-versions {color:#999;}
118 a#toggle-completed-versions {color:#999;}
119 /***** Tables *****/
119 /***** Tables *****/
120 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
120 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
121 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
121 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
122 table.list td { vertical-align: top; }
122 table.list td { vertical-align: top; }
123 table.list td.id { width: 2%; text-align: center;}
123 table.list td.id { width: 2%; text-align: center;}
124 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
124 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
125 table.list td.checkbox input {padding:0px;}
125 table.list td.checkbox input {padding:0px;}
126 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
126 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
127 table.list td.buttons a { padding-right: 0.6em; }
127 table.list td.buttons a { padding-right: 0.6em; }
128 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
128 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
129
129
130 tr.project td.name a { white-space:nowrap; }
130 tr.project td.name a { white-space:nowrap; }
131 tr.project.closed, tr.project.archived { color: #aaa; }
131 tr.project.closed, tr.project.archived { color: #aaa; }
132 tr.project.closed a, tr.project.archived a { color: #aaa; }
132 tr.project.closed a, tr.project.archived a { color: #aaa; }
133
133
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
144
144
145 tr.issue { text-align: center; white-space: nowrap; }
145 tr.issue { text-align: center; white-space: nowrap; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
147 tr.issue td.subject { text-align: left; }
147 tr.issue td.subject { text-align: left; }
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
149
149
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
160
160
161 tr.entry { border: 1px solid #f8f8f8; }
161 tr.entry { border: 1px solid #f8f8f8; }
162 tr.entry td { white-space: nowrap; }
162 tr.entry td { white-space: nowrap; }
163 tr.entry td.filename { width: 30%; }
163 tr.entry td.filename { width: 30%; }
164 tr.entry td.filename_no_report { width: 70%; }
164 tr.entry td.filename_no_report { width: 70%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
167 tr.entry td.age { text-align: right; }
167 tr.entry td.age { text-align: right; }
168 tr.entry.file td.filename a { margin-left: 16px; }
168 tr.entry.file td.filename a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
170
170
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
173
173
174 tr.changeset { height: 20px }
174 tr.changeset { height: 20px }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
179
179
180 table.files tr.file td { text-align: center; }
180 table.files tr.file td { text-align: center; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
182 table.files tr.file td.digest { font-size: 80%; }
182 table.files tr.file td.digest { font-size: 80%; }
183
183
184 table.members td.roles, table.memberships td.roles { width: 45%; }
184 table.members td.roles, table.memberships td.roles { width: 45%; }
185
185
186 tr.message { height: 2.6em; }
186 tr.message { height: 2.6em; }
187 tr.message td.subject { padding-left: 20px; }
187 tr.message td.subject { padding-left: 20px; }
188 tr.message td.created_on { white-space: nowrap; }
188 tr.message td.created_on { white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
192
192
193 tr.version.closed, tr.version.closed a { color: #999; }
193 tr.version.closed, tr.version.closed a { color: #999; }
194 tr.version td.name { padding-left: 20px; }
194 tr.version td.name { padding-left: 20px; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
197
197
198 tr.user td { width:13%; }
198 tr.user td { width:13%; }
199 tr.user td.email { width:18%; }
199 tr.user td.email { width:18%; }
200 tr.user td { white-space: nowrap; }
200 tr.user td { white-space: nowrap; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
203
203
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
205
205
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
207
207
208 tr.time-entry { text-align: center; white-space: nowrap; }
208 tr.time-entry { text-align: center; white-space: nowrap; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
211 td.hours .hours-dec { font-size: 0.9em; }
211 td.hours .hours-dec { font-size: 0.9em; }
212
212
213 table.plugins td { vertical-align: middle; }
213 table.plugins td { vertical-align: middle; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
218
218
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
222 tr.group:hover a.toggle-all { display:inline;}
222 tr.group:hover a.toggle-all { display:inline;}
223 a.toggle-all:hover {text-decoration:none;}
223 a.toggle-all:hover {text-decoration:none;}
224
224
225 table.list tbody tr:hover { background-color:#ffffdd; }
225 table.list tbody tr:hover { background-color:#ffffdd; }
226 table.list tbody tr.group:hover { background-color:inherit; }
226 table.list tbody tr.group:hover { background-color:inherit; }
227 table td {padding:2px;}
227 table td {padding:2px;}
228 table p {margin:0;}
228 table p {margin:0;}
229 .odd {background-color:#f6f7f8;}
229 .odd {background-color:#f6f7f8;}
230 .even {background-color: #fff;}
230 .even {background-color: #fff;}
231
231
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
235
235
236 table.attributes { width: 100% }
236 table.attributes { width: 100% }
237 table.attributes th { vertical-align: top; text-align: left; }
237 table.attributes th { vertical-align: top; text-align: left; }
238 table.attributes td { vertical-align: top; }
238 table.attributes td { vertical-align: top; }
239
239
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
241 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
241 table.boards td.topic-count, table.boards td.message-count {text-align:center;}
242 table.boards td.last-message {font-size:80%;}
242 table.boards td.last-message {font-size:80%;}
243
243
244 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
244 table.messages td.author, table.messages td.created_on, table.messages td.reply-count {text-align:center;}
245
245
246 table.query-columns {
246 table.query-columns {
247 border-collapse: collapse;
247 border-collapse: collapse;
248 border: 0;
248 border: 0;
249 }
249 }
250
250
251 table.query-columns td.buttons {
251 table.query-columns td.buttons {
252 vertical-align: middle;
252 vertical-align: middle;
253 text-align: center;
253 text-align: center;
254 }
254 }
255
255
256 td.center {text-align:center;}
256 td.center {text-align:center;}
257
257
258 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
258 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
259
259
260 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
260 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
261 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
261 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
262 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
262 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
263 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
263 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
264
264
265 #watchers ul {margin: 0; padding: 0;}
265 #watchers ul {margin: 0; padding: 0;}
266 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
266 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
267 #watchers select {width: 95%; display: block;}
267 #watchers select {width: 95%; display: block;}
268 #watchers a.delete {opacity: 0.4;}
268 #watchers a.delete {opacity: 0.4;}
269 #watchers a.delete:hover {opacity: 1;}
269 #watchers a.delete:hover {opacity: 1;}
270 #watchers img.gravatar {margin: 0 4px 2px 0;}
270 #watchers img.gravatar {margin: 0 4px 2px 0;}
271
271
272 span#watchers_inputs {overflow:auto; display:block;}
272 span#watchers_inputs {overflow:auto; display:block;}
273 span.search_for_watchers {display:block;}
273 span.search_for_watchers {display:block;}
274 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
274 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
275 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
275 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
276
276
277
277
278 .highlight { background-color: #FCFD8D;}
278 .highlight { background-color: #FCFD8D;}
279 .highlight.token-1 { background-color: #faa;}
279 .highlight.token-1 { background-color: #faa;}
280 .highlight.token-2 { background-color: #afa;}
280 .highlight.token-2 { background-color: #afa;}
281 .highlight.token-3 { background-color: #aaf;}
281 .highlight.token-3 { background-color: #aaf;}
282
282
283 .box{
283 .box{
284 padding:6px;
284 padding:6px;
285 margin-bottom: 10px;
285 margin-bottom: 10px;
286 background-color:#f6f6f6;
286 background-color:#f6f6f6;
287 color:#505050;
287 color:#505050;
288 line-height:1.5em;
288 line-height:1.5em;
289 border: 1px solid #e4e4e4;
289 border: 1px solid #e4e4e4;
290 }
290 }
291
291
292 div.square {
292 div.square {
293 border: 1px solid #999;
293 border: 1px solid #999;
294 float: left;
294 float: left;
295 margin: .3em .4em 0 .4em;
295 margin: .3em .4em 0 .4em;
296 overflow: hidden;
296 overflow: hidden;
297 width: .6em; height: .6em;
297 width: .6em; height: .6em;
298 }
298 }
299 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
299 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
300 .contextual input, .contextual select {font-size:0.9em;}
300 .contextual input, .contextual select {font-size:0.9em;}
301 .message .contextual { margin-top: 0; }
301 .message .contextual { margin-top: 0; }
302
302
303 .splitcontent {overflow:auto;}
303 .splitcontent {overflow:auto;}
304 .splitcontentleft{float:left; width:49%;}
304 .splitcontentleft{float:left; width:49%;}
305 .splitcontentright{float:right; width:49%;}
305 .splitcontentright{float:right; width:49%;}
306 form {display: inline;}
306 form {display: inline;}
307 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
307 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
308 fieldset {border: 1px solid #e4e4e4; margin:0;}
308 fieldset {border: 1px solid #e4e4e4; margin:0;}
309 legend {color: #484848;}
309 legend {color: #484848;}
310 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
310 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
311 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
311 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
312 blockquote blockquote { margin-left: 0;}
312 blockquote blockquote { margin-left: 0;}
313 acronym { border-bottom: 1px dotted; cursor: help; }
313 acronym { border-bottom: 1px dotted; cursor: help; }
314 textarea.wiki-edit { width: 99%; }
314 textarea.wiki-edit { width: 99%; }
315 li p {margin-top: 0;}
315 li p {margin-top: 0;}
316 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
316 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
317 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
317 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
318 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
318 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
319 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
319 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
320
320
321 div.issue div.subject div div { padding-left: 16px; }
321 div.issue div.subject div div { padding-left: 16px; }
322 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
322 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
323 div.issue div.subject>div>p { margin-top: 0.5em; }
323 div.issue div.subject>div>p { margin-top: 0.5em; }
324 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
324 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
325 div.issue 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;}
325 div.issue 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;}
326 div.issue .next-prev-links {color:#999;}
326 div.issue .next-prev-links {color:#999;}
327 div.issue table.attributes th {width:22%;}
327 div.issue table.attributes th {width:22%;}
328 div.issue table.attributes td {width:28%;}
328 div.issue table.attributes td {width:28%;}
329
329
330 #issue_tree table.issues, #relations table.issues { border: 0; }
330 #issue_tree table.issues, #relations table.issues { border: 0; }
331 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
331 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
332 #relations td.buttons {padding:0;}
332 #relations td.buttons {padding:0;}
333
333
334 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
334 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
335 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
335 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
336 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
336 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
337
337
338 fieldset#date-range p { margin: 2px 0 2px 0; }
338 fieldset#date-range p { margin: 2px 0 2px 0; }
339 fieldset#filters table { border-collapse: collapse; }
339 fieldset#filters table { border-collapse: collapse; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
341 fieldset#filters tr.filter { height: 2em; }
341 fieldset#filters tr.filter { height: 2.1em; }
342 fieldset#filters td.field { width:200px; }
342 fieldset#filters td.field { width:200px; }
343 fieldset#filters td.operator { width:170px; }
343 fieldset#filters td.operator { width:170px; }
344 fieldset#filters td.values { white-space:nowrap; }
344 fieldset#filters td.values { white-space:nowrap; }
345 fieldset#filters td.values select {min-width:130px;}
345 fieldset#filters td.values select {min-width:130px;}
346 fieldset#filters td.values img { vertical-align: middle; margin-left:1px; }
346 fieldset#filters td.values input {height:1em;}
347 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
347 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
348 .toggle-multiselect {background: url(../images/bullet_toggle_plus.png) no-repeat 0% 40%; padding-left:8px; margin-left:0; cursor:pointer;}
348 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
349 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
349
350
350 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
351 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
351 div#issue-changesets div.changeset { padding: 4px;}
352 div#issue-changesets div.changeset { padding: 4px;}
352 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
353 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
353 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
354 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
354
355
355 .journal ul.details img {margin:0 0 -3px 4px;}
356 .journal ul.details img {margin:0 0 -3px 4px;}
356
357
357 div#activity dl, #search-results { margin-left: 2em; }
358 div#activity dl, #search-results { margin-left: 2em; }
358 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
359 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
359 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
360 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
360 div#activity dt.me .time { border-bottom: 1px solid #999; }
361 div#activity dt.me .time { border-bottom: 1px solid #999; }
361 div#activity dt .time { color: #777; font-size: 80%; }
362 div#activity dt .time { color: #777; font-size: 80%; }
362 div#activity dd .description, #search-results dd .description { font-style: italic; }
363 div#activity dd .description, #search-results dd .description { font-style: italic; }
363 div#activity span.project:after, #search-results span.project:after { content: " -"; }
364 div#activity span.project:after, #search-results span.project:after { content: " -"; }
364 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
365 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
365
366
366 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
367 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
367
368
368 div#search-results-counts {float:right;}
369 div#search-results-counts {float:right;}
369 div#search-results-counts ul { margin-top: 0.5em; }
370 div#search-results-counts ul { margin-top: 0.5em; }
370 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
371 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
371
372
372 dt.issue { background-image: url(../images/ticket.png); }
373 dt.issue { background-image: url(../images/ticket.png); }
373 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
374 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
374 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
375 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
375 dt.issue-note { background-image: url(../images/ticket_note.png); }
376 dt.issue-note { background-image: url(../images/ticket_note.png); }
376 dt.changeset { background-image: url(../images/changeset.png); }
377 dt.changeset { background-image: url(../images/changeset.png); }
377 dt.news { background-image: url(../images/news.png); }
378 dt.news { background-image: url(../images/news.png); }
378 dt.message { background-image: url(../images/message.png); }
379 dt.message { background-image: url(../images/message.png); }
379 dt.reply { background-image: url(../images/comments.png); }
380 dt.reply { background-image: url(../images/comments.png); }
380 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
381 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
381 dt.attachment { background-image: url(../images/attachment.png); }
382 dt.attachment { background-image: url(../images/attachment.png); }
382 dt.document { background-image: url(../images/document.png); }
383 dt.document { background-image: url(../images/document.png); }
383 dt.project { background-image: url(../images/projects.png); }
384 dt.project { background-image: url(../images/projects.png); }
384 dt.time-entry { background-image: url(../images/time.png); }
385 dt.time-entry { background-image: url(../images/time.png); }
385
386
386 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
387 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
387
388
388 div#roadmap .related-issues { margin-bottom: 1em; }
389 div#roadmap .related-issues { margin-bottom: 1em; }
389 div#roadmap .related-issues td.checkbox { display: none; }
390 div#roadmap .related-issues td.checkbox { display: none; }
390 div#roadmap .wiki h1:first-child { display: none; }
391 div#roadmap .wiki h1:first-child { display: none; }
391 div#roadmap .wiki h1 { font-size: 120%; }
392 div#roadmap .wiki h1 { font-size: 120%; }
392 div#roadmap .wiki h2 { font-size: 110%; }
393 div#roadmap .wiki h2 { font-size: 110%; }
393 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
394 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
394
395
395 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
396 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
396 div#version-summary fieldset { margin-bottom: 1em; }
397 div#version-summary fieldset { margin-bottom: 1em; }
397 div#version-summary fieldset.time-tracking table { width:100%; }
398 div#version-summary fieldset.time-tracking table { width:100%; }
398 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
399 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
399
400
400 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
401 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
401 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
402 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
402 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
403 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
403 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
404 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
404 table#time-report .hours-dec { font-size: 0.9em; }
405 table#time-report .hours-dec { font-size: 0.9em; }
405
406
406 div.wiki-page .contextual a {opacity: 0.4}
407 div.wiki-page .contextual a {opacity: 0.4}
407 div.wiki-page .contextual a:hover {opacity: 1}
408 div.wiki-page .contextual a:hover {opacity: 1}
408
409
409 form .attributes select { width: 60%; }
410 form .attributes select { width: 60%; }
410 input#issue_subject { width: 99%; }
411 input#issue_subject { width: 99%; }
411 select#issue_done_ratio { width: 95px; }
412 select#issue_done_ratio { width: 95px; }
412
413
413 ul.projects { margin: 0; padding-left: 1em; }
414 ul.projects { margin: 0; padding-left: 1em; }
414 ul.projects.root { margin: 0; padding: 0; }
415 ul.projects.root { margin: 0; padding: 0; }
415 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
416 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
416 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
417 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
417 ul.projects li.child { list-style-type:none; margin-top: 1em;}
418 ul.projects li.child { list-style-type:none; margin-top: 1em;}
418 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
419 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
419 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
420 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
420
421
421 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
422 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
422 #tracker_project_ids li { list-style-type:none; }
423 #tracker_project_ids li { list-style-type:none; }
423
424
424 #related-issues li img {vertical-align:middle;}
425 #related-issues li img {vertical-align:middle;}
425
426
426 ul.properties {padding:0; font-size: 0.9em; color: #777;}
427 ul.properties {padding:0; font-size: 0.9em; color: #777;}
427 ul.properties li {list-style-type:none;}
428 ul.properties li {list-style-type:none;}
428 ul.properties li span {font-style:italic;}
429 ul.properties li span {font-style:italic;}
429
430
430 .total-hours { font-size: 110%; font-weight: bold; }
431 .total-hours { font-size: 110%; font-weight: bold; }
431 .total-hours span.hours-int { font-size: 120%; }
432 .total-hours span.hours-int { font-size: 120%; }
432
433
433 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
434 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
434 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
435 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
435
436
436 #workflow_copy_form select { width: 200px; }
437 #workflow_copy_form select { width: 200px; }
437 table.transitions td.enabled {background: #bfb;}
438 table.transitions td.enabled {background: #bfb;}
438 table.fields_permissions select {font-size:90%}
439 table.fields_permissions select {font-size:90%}
439 table.fields_permissions td.readonly {background:#ddd;}
440 table.fields_permissions td.readonly {background:#ddd;}
440 table.fields_permissions td.required {background:#d88;}
441 table.fields_permissions td.required {background:#d88;}
441
442
442 textarea#custom_field_possible_values {width: 99%}
443 textarea#custom_field_possible_values {width: 99%}
443 input#content_comments {width: 99%}
444 input#content_comments {width: 99%}
444
445
445 .pagination {font-size: 90%}
446 .pagination {font-size: 90%}
446 p.pagination {margin-top:8px;}
447 p.pagination {margin-top:8px;}
447
448
448 /***** Tabular forms ******/
449 /***** Tabular forms ******/
449 .tabular p{
450 .tabular p{
450 margin: 0;
451 margin: 0;
451 padding: 3px 0 3px 0;
452 padding: 3px 0 3px 0;
452 padding-left: 180px; /* width of left column containing the label elements */
453 padding-left: 180px; /* width of left column containing the label elements */
453 min-height: 1.8em;
454 min-height: 1.8em;
454 clear:left;
455 clear:left;
455 }
456 }
456
457
457 html>body .tabular p {overflow:hidden;}
458 html>body .tabular p {overflow:hidden;}
458
459
459 .tabular label{
460 .tabular label{
460 font-weight: bold;
461 font-weight: bold;
461 float: left;
462 float: left;
462 text-align: right;
463 text-align: right;
463 /* width of left column */
464 /* width of left column */
464 margin-left: -180px;
465 margin-left: -180px;
465 /* width of labels. Should be smaller than left column to create some right margin */
466 /* width of labels. Should be smaller than left column to create some right margin */
466 width: 175px;
467 width: 175px;
467 }
468 }
468
469
469 .tabular label.floating{
470 .tabular label.floating{
470 font-weight: normal;
471 font-weight: normal;
471 margin-left: 0px;
472 margin-left: 0px;
472 text-align: left;
473 text-align: left;
473 width: 270px;
474 width: 270px;
474 }
475 }
475
476
476 .tabular label.block{
477 .tabular label.block{
477 font-weight: normal;
478 font-weight: normal;
478 margin-left: 0px !important;
479 margin-left: 0px !important;
479 text-align: left;
480 text-align: left;
480 float: none;
481 float: none;
481 display: block;
482 display: block;
482 width: auto;
483 width: auto;
483 }
484 }
484
485
485 .tabular label.inline{
486 .tabular label.inline{
486 float:none;
487 float:none;
487 margin-left: 5px !important;
488 margin-left: 5px !important;
488 width: auto;
489 width: auto;
489 }
490 }
490
491
491 label.no-css {
492 label.no-css {
492 font-weight: inherit;
493 font-weight: inherit;
493 float:none;
494 float:none;
494 text-align:left;
495 text-align:left;
495 margin-left:0px;
496 margin-left:0px;
496 width:auto;
497 width:auto;
497 }
498 }
498 input#time_entry_comments { width: 90%;}
499 input#time_entry_comments { width: 90%;}
499
500
500 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
501 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
501
502
502 .tabular.settings p{ padding-left: 300px; }
503 .tabular.settings p{ padding-left: 300px; }
503 .tabular.settings label{ margin-left: -300px; width: 295px; }
504 .tabular.settings label{ margin-left: -300px; width: 295px; }
504 .tabular.settings textarea { width: 99%; }
505 .tabular.settings textarea { width: 99%; }
505
506
506 .settings.enabled_scm table {width:100%}
507 .settings.enabled_scm table {width:100%}
507 .settings.enabled_scm td.scm_name{ font-weight: bold; }
508 .settings.enabled_scm td.scm_name{ font-weight: bold; }
508
509
509 fieldset.settings label { display: block; }
510 fieldset.settings label { display: block; }
510 fieldset#notified_events .parent { padding-left: 20px; }
511 fieldset#notified_events .parent { padding-left: 20px; }
511
512
512 span.required {color: #bb0000;}
513 span.required {color: #bb0000;}
513 .summary {font-style: italic;}
514 .summary {font-style: italic;}
514
515
515 #attachments_fields input.description {margin-left: 8px; width:340px;}
516 #attachments_fields input.description {margin-left: 8px; width:340px;}
516 #attachments_fields span {display:block; white-space:nowrap;}
517 #attachments_fields span {display:block; white-space:nowrap;}
517 #attachments_fields img {vertical-align: middle;}
518 #attachments_fields img {vertical-align: middle;}
518
519
519 div.attachments { margin-top: 12px; }
520 div.attachments { margin-top: 12px; }
520 div.attachments p { margin:4px 0 2px 0; }
521 div.attachments p { margin:4px 0 2px 0; }
521 div.attachments img { vertical-align: middle; }
522 div.attachments img { vertical-align: middle; }
522 div.attachments span.author { font-size: 0.9em; color: #888; }
523 div.attachments span.author { font-size: 0.9em; color: #888; }
523
524
524 div.thumbnails {margin-top:0.6em;}
525 div.thumbnails {margin-top:0.6em;}
525 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
526 div.thumbnails div {background:#fff;border:2px solid #ddd;display:inline-block;margin-right:2px;}
526 div.thumbnails img {margin: 3px;}
527 div.thumbnails img {margin: 3px;}
527
528
528 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
529 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
529 .other-formats span + span:before { content: "| "; }
530 .other-formats span + span:before { content: "| "; }
530
531
531 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
532 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
532
533
533 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
534 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
534 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
535 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
535
536
536 textarea.text_cf {width:90%;}
537 textarea.text_cf {width:90%;}
537
538
538 /* Project members tab */
539 /* Project members tab */
539 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
540 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
540 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
541 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
541 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
542 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
542 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
543 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
543 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
544 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
544 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
545 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
545
546
546 #users_for_watcher {height: 200px; overflow:auto;}
547 #users_for_watcher {height: 200px; overflow:auto;}
547 #users_for_watcher label {display: block;}
548 #users_for_watcher label {display: block;}
548
549
549 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
550 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
550
551
551 input#principal_search, input#user_search {width:100%}
552 input#principal_search, input#user_search {width:100%}
552 input#principal_search, input#user_search {
553 input#principal_search, input#user_search {
553 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
554 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
554 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
555 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
555 }
556 }
556 input#principal_search.ajax-loading, input#user_search.ajax-loading {
557 input#principal_search.ajax-loading, input#user_search.ajax-loading {
557 background-image: url(../images/loading.gif);
558 background-image: url(../images/loading.gif);
558 }
559 }
559
560
560 * html div#tab-content-members fieldset div { height: 450px; }
561 * html div#tab-content-members fieldset div { height: 450px; }
561
562
562 /***** Flash & error messages ****/
563 /***** Flash & error messages ****/
563 #errorExplanation, div.flash, .nodata, .warning, .conflict {
564 #errorExplanation, div.flash, .nodata, .warning, .conflict {
564 padding: 4px 4px 4px 30px;
565 padding: 4px 4px 4px 30px;
565 margin-bottom: 12px;
566 margin-bottom: 12px;
566 font-size: 1.1em;
567 font-size: 1.1em;
567 border: 2px solid;
568 border: 2px solid;
568 }
569 }
569
570
570 div.flash {margin-top: 8px;}
571 div.flash {margin-top: 8px;}
571
572
572 div.flash.error, #errorExplanation {
573 div.flash.error, #errorExplanation {
573 background: url(../images/exclamation.png) 8px 50% no-repeat;
574 background: url(../images/exclamation.png) 8px 50% no-repeat;
574 background-color: #ffe3e3;
575 background-color: #ffe3e3;
575 border-color: #dd0000;
576 border-color: #dd0000;
576 color: #880000;
577 color: #880000;
577 }
578 }
578
579
579 div.flash.notice {
580 div.flash.notice {
580 background: url(../images/true.png) 8px 5px no-repeat;
581 background: url(../images/true.png) 8px 5px no-repeat;
581 background-color: #dfffdf;
582 background-color: #dfffdf;
582 border-color: #9fcf9f;
583 border-color: #9fcf9f;
583 color: #005f00;
584 color: #005f00;
584 }
585 }
585
586
586 div.flash.warning, .conflict {
587 div.flash.warning, .conflict {
587 background: url(../images/warning.png) 8px 5px no-repeat;
588 background: url(../images/warning.png) 8px 5px no-repeat;
588 background-color: #FFEBC1;
589 background-color: #FFEBC1;
589 border-color: #FDBF3B;
590 border-color: #FDBF3B;
590 color: #A6750C;
591 color: #A6750C;
591 text-align: left;
592 text-align: left;
592 }
593 }
593
594
594 .nodata, .warning {
595 .nodata, .warning {
595 text-align: center;
596 text-align: center;
596 background-color: #FFEBC1;
597 background-color: #FFEBC1;
597 border-color: #FDBF3B;
598 border-color: #FDBF3B;
598 color: #A6750C;
599 color: #A6750C;
599 }
600 }
600
601
601 #errorExplanation ul { font-size: 0.9em;}
602 #errorExplanation ul { font-size: 0.9em;}
602 #errorExplanation h2, #errorExplanation p { display: none; }
603 #errorExplanation h2, #errorExplanation p { display: none; }
603
604
604 .conflict-details {font-size:80%;}
605 .conflict-details {font-size:80%;}
605
606
606 /***** Ajax indicator ******/
607 /***** Ajax indicator ******/
607 #ajax-indicator {
608 #ajax-indicator {
608 position: absolute; /* fixed not supported by IE */
609 position: absolute; /* fixed not supported by IE */
609 background-color:#eee;
610 background-color:#eee;
610 border: 1px solid #bbb;
611 border: 1px solid #bbb;
611 top:35%;
612 top:35%;
612 left:40%;
613 left:40%;
613 width:20%;
614 width:20%;
614 font-weight:bold;
615 font-weight:bold;
615 text-align:center;
616 text-align:center;
616 padding:0.6em;
617 padding:0.6em;
617 z-index:100;
618 z-index:100;
618 opacity: 0.5;
619 opacity: 0.5;
619 }
620 }
620
621
621 html>body #ajax-indicator { position: fixed; }
622 html>body #ajax-indicator { position: fixed; }
622
623
623 #ajax-indicator span {
624 #ajax-indicator span {
624 background-position: 0% 40%;
625 background-position: 0% 40%;
625 background-repeat: no-repeat;
626 background-repeat: no-repeat;
626 background-image: url(../images/loading.gif);
627 background-image: url(../images/loading.gif);
627 padding-left: 26px;
628 padding-left: 26px;
628 vertical-align: bottom;
629 vertical-align: bottom;
629 }
630 }
630
631
631 /***** Calendar *****/
632 /***** Calendar *****/
632 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
633 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
633 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
634 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
634 table.cal thead th.week-number {width: auto;}
635 table.cal thead th.week-number {width: auto;}
635 table.cal tbody tr {height: 100px;}
636 table.cal tbody tr {height: 100px;}
636 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
637 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
637 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
638 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
638 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
639 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
639 table.cal td.odd p.day-num {color: #bbb;}
640 table.cal td.odd p.day-num {color: #bbb;}
640 table.cal td.today {background:#ffffdd;}
641 table.cal td.today {background:#ffffdd;}
641 table.cal td.today p.day-num {font-weight: bold;}
642 table.cal td.today p.day-num {font-weight: bold;}
642 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
643 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
643 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
644 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
644 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
645 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
645 p.cal.legend span {display:block;}
646 p.cal.legend span {display:block;}
646
647
647 /***** Tooltips ******/
648 /***** Tooltips ******/
648 .tooltip{position:relative;z-index:24;}
649 .tooltip{position:relative;z-index:24;}
649 .tooltip:hover{z-index:25;color:#000;}
650 .tooltip:hover{z-index:25;color:#000;}
650 .tooltip span.tip{display: none; text-align:left;}
651 .tooltip span.tip{display: none; text-align:left;}
651
652
652 div.tooltip:hover span.tip{
653 div.tooltip:hover span.tip{
653 display:block;
654 display:block;
654 position:absolute;
655 position:absolute;
655 top:12px; left:24px; width:270px;
656 top:12px; left:24px; width:270px;
656 border:1px solid #555;
657 border:1px solid #555;
657 background-color:#fff;
658 background-color:#fff;
658 padding: 4px;
659 padding: 4px;
659 font-size: 0.8em;
660 font-size: 0.8em;
660 color:#505050;
661 color:#505050;
661 }
662 }
662
663
663 img.ui-datepicker-trigger {
664 img.ui-datepicker-trigger {
664 cursor: pointer;
665 cursor: pointer;
665 vertical-align: middle;
666 vertical-align: middle;
666 margin-left: 4px;
667 margin-left: 4px;
667 }
668 }
668
669
669 /***** Progress bar *****/
670 /***** Progress bar *****/
670 table.progress {
671 table.progress {
671 border-collapse: collapse;
672 border-collapse: collapse;
672 border-spacing: 0pt;
673 border-spacing: 0pt;
673 empty-cells: show;
674 empty-cells: show;
674 text-align: center;
675 text-align: center;
675 float:left;
676 float:left;
676 margin: 1px 6px 1px 0px;
677 margin: 1px 6px 1px 0px;
677 }
678 }
678
679
679 table.progress td { height: 1em; }
680 table.progress td { height: 1em; }
680 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
681 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
681 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
682 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
682 table.progress td.todo { background: #eee none repeat scroll 0%; }
683 table.progress td.todo { background: #eee none repeat scroll 0%; }
683 p.pourcent {font-size: 80%;}
684 p.pourcent {font-size: 80%;}
684 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
685 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
685
686
686 #roadmap table.progress td { height: 1.2em; }
687 #roadmap table.progress td { height: 1.2em; }
687 /***** Tabs *****/
688 /***** Tabs *****/
688 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
689 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
689 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
690 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
690 #content .tabs ul li {
691 #content .tabs ul li {
691 float:left;
692 float:left;
692 list-style-type:none;
693 list-style-type:none;
693 white-space:nowrap;
694 white-space:nowrap;
694 margin-right:4px;
695 margin-right:4px;
695 background:#fff;
696 background:#fff;
696 position:relative;
697 position:relative;
697 margin-bottom:-1px;
698 margin-bottom:-1px;
698 }
699 }
699 #content .tabs ul li a{
700 #content .tabs ul li a{
700 display:block;
701 display:block;
701 font-size: 0.9em;
702 font-size: 0.9em;
702 text-decoration:none;
703 text-decoration:none;
703 line-height:1.3em;
704 line-height:1.3em;
704 padding:4px 6px 4px 6px;
705 padding:4px 6px 4px 6px;
705 border: 1px solid #ccc;
706 border: 1px solid #ccc;
706 border-bottom: 1px solid #bbbbbb;
707 border-bottom: 1px solid #bbbbbb;
707 background-color: #f6f6f6;
708 background-color: #f6f6f6;
708 color:#999;
709 color:#999;
709 font-weight:bold;
710 font-weight:bold;
710 border-top-left-radius:3px;
711 border-top-left-radius:3px;
711 border-top-right-radius:3px;
712 border-top-right-radius:3px;
712 }
713 }
713
714
714 #content .tabs ul li a:hover {
715 #content .tabs ul li a:hover {
715 background-color: #ffffdd;
716 background-color: #ffffdd;
716 text-decoration:none;
717 text-decoration:none;
717 }
718 }
718
719
719 #content .tabs ul li a.selected {
720 #content .tabs ul li a.selected {
720 background-color: #fff;
721 background-color: #fff;
721 border: 1px solid #bbbbbb;
722 border: 1px solid #bbbbbb;
722 border-bottom: 1px solid #fff;
723 border-bottom: 1px solid #fff;
723 color:#444;
724 color:#444;
724 }
725 }
725
726
726 #content .tabs ul li a.selected:hover {background-color: #fff;}
727 #content .tabs ul li a.selected:hover {background-color: #fff;}
727
728
728 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
729 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
729
730
730 button.tab-left, button.tab-right {
731 button.tab-left, button.tab-right {
731 font-size: 0.9em;
732 font-size: 0.9em;
732 cursor: pointer;
733 cursor: pointer;
733 height:24px;
734 height:24px;
734 border: 1px solid #ccc;
735 border: 1px solid #ccc;
735 border-bottom: 1px solid #bbbbbb;
736 border-bottom: 1px solid #bbbbbb;
736 position:absolute;
737 position:absolute;
737 padding:4px;
738 padding:4px;
738 width: 20px;
739 width: 20px;
739 bottom: -1px;
740 bottom: -1px;
740 }
741 }
741
742
742 button.tab-left {
743 button.tab-left {
743 right: 20px;
744 right: 20px;
744 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
745 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
745 border-top-left-radius:3px;
746 border-top-left-radius:3px;
746 }
747 }
747
748
748 button.tab-right {
749 button.tab-right {
749 right: 0;
750 right: 0;
750 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
751 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
751 border-top-right-radius:3px;
752 border-top-right-radius:3px;
752 }
753 }
753
754
754 /***** Diff *****/
755 /***** Diff *****/
755 .diff_out { background: #fcc; }
756 .diff_out { background: #fcc; }
756 .diff_out span { background: #faa; }
757 .diff_out span { background: #faa; }
757 .diff_in { background: #cfc; }
758 .diff_in { background: #cfc; }
758 .diff_in span { background: #afa; }
759 .diff_in span { background: #afa; }
759
760
760 .text-diff {
761 .text-diff {
761 padding: 1em;
762 padding: 1em;
762 background-color:#f6f6f6;
763 background-color:#f6f6f6;
763 color:#505050;
764 color:#505050;
764 border: 1px solid #e4e4e4;
765 border: 1px solid #e4e4e4;
765 }
766 }
766
767
767 /***** Wiki *****/
768 /***** Wiki *****/
768 div.wiki table {
769 div.wiki table {
769 border-collapse: collapse;
770 border-collapse: collapse;
770 margin-bottom: 1em;
771 margin-bottom: 1em;
771 }
772 }
772
773
773 div.wiki table, div.wiki td, div.wiki th {
774 div.wiki table, div.wiki td, div.wiki th {
774 border: 1px solid #bbb;
775 border: 1px solid #bbb;
775 padding: 4px;
776 padding: 4px;
776 }
777 }
777
778
778 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
779 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
779
780
780 div.wiki .external {
781 div.wiki .external {
781 background-position: 0% 60%;
782 background-position: 0% 60%;
782 background-repeat: no-repeat;
783 background-repeat: no-repeat;
783 padding-left: 12px;
784 padding-left: 12px;
784 background-image: url(../images/external.png);
785 background-image: url(../images/external.png);
785 }
786 }
786
787
787 div.wiki a.new {color: #b73535;}
788 div.wiki a.new {color: #b73535;}
788
789
789 div.wiki ul, div.wiki ol {margin-bottom:1em;}
790 div.wiki ul, div.wiki ol {margin-bottom:1em;}
790
791
791 div.wiki pre {
792 div.wiki pre {
792 margin: 1em 1em 1em 1.6em;
793 margin: 1em 1em 1em 1.6em;
793 padding: 8px;
794 padding: 8px;
794 background-color: #fafafa;
795 background-color: #fafafa;
795 border: 1px solid #e2e2e2;
796 border: 1px solid #e2e2e2;
796 width:auto;
797 width:auto;
797 overflow-x: auto;
798 overflow-x: auto;
798 overflow-y: hidden;
799 overflow-y: hidden;
799 }
800 }
800
801
801 div.wiki ul.toc {
802 div.wiki ul.toc {
802 background-color: #ffffdd;
803 background-color: #ffffdd;
803 border: 1px solid #e4e4e4;
804 border: 1px solid #e4e4e4;
804 padding: 4px;
805 padding: 4px;
805 line-height: 1.2em;
806 line-height: 1.2em;
806 margin-bottom: 12px;
807 margin-bottom: 12px;
807 margin-right: 12px;
808 margin-right: 12px;
808 margin-left: 0;
809 margin-left: 0;
809 display: table
810 display: table
810 }
811 }
811 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
812 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
812
813
813 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
814 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
814 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
815 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
815 div.wiki ul.toc ul { margin: 0; padding: 0; }
816 div.wiki ul.toc ul { margin: 0; padding: 0; }
816 div.wiki ul.toc li { list-style-type:none; margin: 0;}
817 div.wiki ul.toc li { list-style-type:none; margin: 0;}
817 div.wiki ul.toc li li { margin-left: 1.5em; }
818 div.wiki ul.toc li li { margin-left: 1.5em; }
818 div.wiki ul.toc li li li { font-size: 0.8em; }
819 div.wiki ul.toc li li li { font-size: 0.8em; }
819 div.wiki ul.toc a {
820 div.wiki ul.toc a {
820 font-size: 0.9em;
821 font-size: 0.9em;
821 font-weight: normal;
822 font-weight: normal;
822 text-decoration: none;
823 text-decoration: none;
823 color: #606060;
824 color: #606060;
824 }
825 }
825 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
826 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
826
827
827 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
828 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
828 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
829 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
829 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
830 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
830
831
831 div.wiki img { vertical-align: middle; }
832 div.wiki img { vertical-align: middle; }
832
833
833 /***** My page layout *****/
834 /***** My page layout *****/
834 .block-receiver {
835 .block-receiver {
835 border:1px dashed #c0c0c0;
836 border:1px dashed #c0c0c0;
836 margin-bottom: 20px;
837 margin-bottom: 20px;
837 padding: 15px 0 15px 0;
838 padding: 15px 0 15px 0;
838 }
839 }
839
840
840 .mypage-box {
841 .mypage-box {
841 margin:0 0 20px 0;
842 margin:0 0 20px 0;
842 color:#505050;
843 color:#505050;
843 line-height:1.5em;
844 line-height:1.5em;
844 }
845 }
845
846
846 .handle {cursor: move;}
847 .handle {cursor: move;}
847
848
848 a.close-icon {
849 a.close-icon {
849 display:block;
850 display:block;
850 margin-top:3px;
851 margin-top:3px;
851 overflow:hidden;
852 overflow:hidden;
852 width:12px;
853 width:12px;
853 height:12px;
854 height:12px;
854 background-repeat: no-repeat;
855 background-repeat: no-repeat;
855 cursor:pointer;
856 cursor:pointer;
856 background-image:url('../images/close.png');
857 background-image:url('../images/close.png');
857 }
858 }
858 a.close-icon:hover {background-image:url('../images/close_hl.png');}
859 a.close-icon:hover {background-image:url('../images/close_hl.png');}
859
860
860 /***** Gantt chart *****/
861 /***** Gantt chart *****/
861 .gantt_hdr {
862 .gantt_hdr {
862 position:absolute;
863 position:absolute;
863 top:0;
864 top:0;
864 height:16px;
865 height:16px;
865 border-top: 1px solid #c0c0c0;
866 border-top: 1px solid #c0c0c0;
866 border-bottom: 1px solid #c0c0c0;
867 border-bottom: 1px solid #c0c0c0;
867 border-right: 1px solid #c0c0c0;
868 border-right: 1px solid #c0c0c0;
868 text-align: center;
869 text-align: center;
869 overflow: hidden;
870 overflow: hidden;
870 }
871 }
871
872
872 .gantt_subjects { font-size: 0.8em; }
873 .gantt_subjects { font-size: 0.8em; }
873 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
874 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
874
875
875 .task {
876 .task {
876 position: absolute;
877 position: absolute;
877 height:8px;
878 height:8px;
878 font-size:0.8em;
879 font-size:0.8em;
879 color:#888;
880 color:#888;
880 padding:0;
881 padding:0;
881 margin:0;
882 margin:0;
882 line-height:16px;
883 line-height:16px;
883 white-space:nowrap;
884 white-space:nowrap;
884 }
885 }
885
886
886 .task.label {width:100%;}
887 .task.label {width:100%;}
887 .task.label.project, .task.label.version { font-weight: bold; }
888 .task.label.project, .task.label.version { font-weight: bold; }
888
889
889 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
890 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
890 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
891 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
891 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
892 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
892
893
893 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
894 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
894 .task_late.parent, .task_done.parent { height: 3px;}
895 .task_late.parent, .task_done.parent { height: 3px;}
895 .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;}
896 .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;}
896 .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;}
897 .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;}
897
898
898 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
899 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
899 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
900 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
900 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
901 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
901 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
902 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
902
903
903 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
904 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
904 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
905 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
905 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
906 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
906 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
907 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
907
908
908 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
909 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
909 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
910 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
910
911
911 /***** Icons *****/
912 /***** Icons *****/
912 .icon {
913 .icon {
913 background-position: 0% 50%;
914 background-position: 0% 50%;
914 background-repeat: no-repeat;
915 background-repeat: no-repeat;
915 padding-left: 20px;
916 padding-left: 20px;
916 padding-top: 2px;
917 padding-top: 2px;
917 padding-bottom: 3px;
918 padding-bottom: 3px;
918 }
919 }
919
920
920 .icon-add { background-image: url(../images/add.png); }
921 .icon-add { background-image: url(../images/add.png); }
921 .icon-edit { background-image: url(../images/edit.png); }
922 .icon-edit { background-image: url(../images/edit.png); }
922 .icon-copy { background-image: url(../images/copy.png); }
923 .icon-copy { background-image: url(../images/copy.png); }
923 .icon-duplicate { background-image: url(../images/duplicate.png); }
924 .icon-duplicate { background-image: url(../images/duplicate.png); }
924 .icon-del { background-image: url(../images/delete.png); }
925 .icon-del { background-image: url(../images/delete.png); }
925 .icon-move { background-image: url(../images/move.png); }
926 .icon-move { background-image: url(../images/move.png); }
926 .icon-save { background-image: url(../images/save.png); }
927 .icon-save { background-image: url(../images/save.png); }
927 .icon-cancel { background-image: url(../images/cancel.png); }
928 .icon-cancel { background-image: url(../images/cancel.png); }
928 .icon-multiple { background-image: url(../images/table_multiple.png); }
929 .icon-multiple { background-image: url(../images/table_multiple.png); }
929 .icon-folder { background-image: url(../images/folder.png); }
930 .icon-folder { background-image: url(../images/folder.png); }
930 .open .icon-folder { background-image: url(../images/folder_open.png); }
931 .open .icon-folder { background-image: url(../images/folder_open.png); }
931 .icon-package { background-image: url(../images/package.png); }
932 .icon-package { background-image: url(../images/package.png); }
932 .icon-user { background-image: url(../images/user.png); }
933 .icon-user { background-image: url(../images/user.png); }
933 .icon-projects { background-image: url(../images/projects.png); }
934 .icon-projects { background-image: url(../images/projects.png); }
934 .icon-help { background-image: url(../images/help.png); }
935 .icon-help { background-image: url(../images/help.png); }
935 .icon-attachment { background-image: url(../images/attachment.png); }
936 .icon-attachment { background-image: url(../images/attachment.png); }
936 .icon-history { background-image: url(../images/history.png); }
937 .icon-history { background-image: url(../images/history.png); }
937 .icon-time { background-image: url(../images/time.png); }
938 .icon-time { background-image: url(../images/time.png); }
938 .icon-time-add { background-image: url(../images/time_add.png); }
939 .icon-time-add { background-image: url(../images/time_add.png); }
939 .icon-stats { background-image: url(../images/stats.png); }
940 .icon-stats { background-image: url(../images/stats.png); }
940 .icon-warning { background-image: url(../images/warning.png); }
941 .icon-warning { background-image: url(../images/warning.png); }
941 .icon-fav { background-image: url(../images/fav.png); }
942 .icon-fav { background-image: url(../images/fav.png); }
942 .icon-fav-off { background-image: url(../images/fav_off.png); }
943 .icon-fav-off { background-image: url(../images/fav_off.png); }
943 .icon-reload { background-image: url(../images/reload.png); }
944 .icon-reload { background-image: url(../images/reload.png); }
944 .icon-lock { background-image: url(../images/locked.png); }
945 .icon-lock { background-image: url(../images/locked.png); }
945 .icon-unlock { background-image: url(../images/unlock.png); }
946 .icon-unlock { background-image: url(../images/unlock.png); }
946 .icon-checked { background-image: url(../images/true.png); }
947 .icon-checked { background-image: url(../images/true.png); }
947 .icon-details { background-image: url(../images/zoom_in.png); }
948 .icon-details { background-image: url(../images/zoom_in.png); }
948 .icon-report { background-image: url(../images/report.png); }
949 .icon-report { background-image: url(../images/report.png); }
949 .icon-comment { background-image: url(../images/comment.png); }
950 .icon-comment { background-image: url(../images/comment.png); }
950 .icon-summary { background-image: url(../images/lightning.png); }
951 .icon-summary { background-image: url(../images/lightning.png); }
951 .icon-server-authentication { background-image: url(../images/server_key.png); }
952 .icon-server-authentication { background-image: url(../images/server_key.png); }
952 .icon-issue { background-image: url(../images/ticket.png); }
953 .icon-issue { background-image: url(../images/ticket.png); }
953 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
954 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
954 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
955 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
955 .icon-passwd { background-image: url(../images/textfield_key.png); }
956 .icon-passwd { background-image: url(../images/textfield_key.png); }
956 .icon-test { background-image: url(../images/bullet_go.png); }
957 .icon-test { background-image: url(../images/bullet_go.png); }
957
958
958 .icon-file { background-image: url(../images/files/default.png); }
959 .icon-file { background-image: url(../images/files/default.png); }
959 .icon-file.text-plain { background-image: url(../images/files/text.png); }
960 .icon-file.text-plain { background-image: url(../images/files/text.png); }
960 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
961 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
961 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
962 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
962 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
963 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
963 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
964 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
964 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
965 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
965 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
966 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
966 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
967 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
967 .icon-file.text-css { background-image: url(../images/files/css.png); }
968 .icon-file.text-css { background-image: url(../images/files/css.png); }
968 .icon-file.text-html { background-image: url(../images/files/html.png); }
969 .icon-file.text-html { background-image: url(../images/files/html.png); }
969 .icon-file.image-gif { background-image: url(../images/files/image.png); }
970 .icon-file.image-gif { background-image: url(../images/files/image.png); }
970 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
971 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
971 .icon-file.image-png { background-image: url(../images/files/image.png); }
972 .icon-file.image-png { background-image: url(../images/files/image.png); }
972 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
973 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
973 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
974 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
974 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
975 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
975 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
976 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
976
977
977 img.gravatar {
978 img.gravatar {
978 padding: 2px;
979 padding: 2px;
979 border: solid 1px #d5d5d5;
980 border: solid 1px #d5d5d5;
980 background: #fff;
981 background: #fff;
981 vertical-align: middle;
982 vertical-align: middle;
982 }
983 }
983
984
984 div.issue img.gravatar {
985 div.issue img.gravatar {
985 float: left;
986 float: left;
986 margin: 0 6px 0 0;
987 margin: 0 6px 0 0;
987 padding: 5px;
988 padding: 5px;
988 }
989 }
989
990
990 div.issue table img.gravatar {
991 div.issue table img.gravatar {
991 height: 14px;
992 height: 14px;
992 width: 14px;
993 width: 14px;
993 padding: 2px;
994 padding: 2px;
994 float: left;
995 float: left;
995 margin: 0 0.5em 0 0;
996 margin: 0 0.5em 0 0;
996 }
997 }
997
998
998 h2 img.gravatar {margin: -2px 4px -4px 0;}
999 h2 img.gravatar {margin: -2px 4px -4px 0;}
999 h3 img.gravatar {margin: -4px 4px -4px 0;}
1000 h3 img.gravatar {margin: -4px 4px -4px 0;}
1000 h4 img.gravatar {margin: -6px 4px -4px 0;}
1001 h4 img.gravatar {margin: -6px 4px -4px 0;}
1001 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1002 td.username img.gravatar {margin: 0 0.5em 0 0; vertical-align: top;}
1002 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1003 #activity dt img.gravatar {float: left; margin: 0 1em 1em 0;}
1003 /* Used on 12px Gravatar img tags without the icon background */
1004 /* Used on 12px Gravatar img tags without the icon background */
1004 .icon-gravatar {float: left; margin-right: 4px;}
1005 .icon-gravatar {float: left; margin-right: 4px;}
1005
1006
1006 #activity dt, .journal {clear: left;}
1007 #activity dt, .journal {clear: left;}
1007
1008
1008 .journal-link {float: right;}
1009 .journal-link {float: right;}
1009
1010
1010 h2 img { vertical-align:middle; }
1011 h2 img { vertical-align:middle; }
1011
1012
1012 .hascontextmenu { cursor: context-menu; }
1013 .hascontextmenu { cursor: context-menu; }
1013
1014
1014 /************* CodeRay styles *************/
1015 /************* CodeRay styles *************/
1015 .syntaxhl div {display: inline;}
1016 .syntaxhl div {display: inline;}
1016 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1017 .syntaxhl .line-numbers {padding: 2px 4px 2px 4px; background-color: #eee; margin:0px 5px 0px 0px;}
1017 .syntaxhl .code pre { overflow: auto }
1018 .syntaxhl .code pre { overflow: auto }
1018 .syntaxhl .debug { color: white !important; background: blue !important; }
1019 .syntaxhl .debug { color: white !important; background: blue !important; }
1019
1020
1020 .syntaxhl .annotation { color:#007 }
1021 .syntaxhl .annotation { color:#007 }
1021 .syntaxhl .attribute-name { color:#b48 }
1022 .syntaxhl .attribute-name { color:#b48 }
1022 .syntaxhl .attribute-value { color:#700 }
1023 .syntaxhl .attribute-value { color:#700 }
1023 .syntaxhl .binary { color:#509 }
1024 .syntaxhl .binary { color:#509 }
1024 .syntaxhl .char .content { color:#D20 }
1025 .syntaxhl .char .content { color:#D20 }
1025 .syntaxhl .char .delimiter { color:#710 }
1026 .syntaxhl .char .delimiter { color:#710 }
1026 .syntaxhl .char { color:#D20 }
1027 .syntaxhl .char { color:#D20 }
1027 .syntaxhl .class { color:#258; font-weight:bold }
1028 .syntaxhl .class { color:#258; font-weight:bold }
1028 .syntaxhl .class-variable { color:#369 }
1029 .syntaxhl .class-variable { color:#369 }
1029 .syntaxhl .color { color:#0A0 }
1030 .syntaxhl .color { color:#0A0 }
1030 .syntaxhl .comment { color:#385 }
1031 .syntaxhl .comment { color:#385 }
1031 .syntaxhl .comment .char { color:#385 }
1032 .syntaxhl .comment .char { color:#385 }
1032 .syntaxhl .comment .delimiter { color:#385 }
1033 .syntaxhl .comment .delimiter { color:#385 }
1033 .syntaxhl .complex { color:#A08 }
1034 .syntaxhl .complex { color:#A08 }
1034 .syntaxhl .constant { color:#258; font-weight:bold }
1035 .syntaxhl .constant { color:#258; font-weight:bold }
1035 .syntaxhl .decorator { color:#B0B }
1036 .syntaxhl .decorator { color:#B0B }
1036 .syntaxhl .definition { color:#099; font-weight:bold }
1037 .syntaxhl .definition { color:#099; font-weight:bold }
1037 .syntaxhl .delimiter { color:black }
1038 .syntaxhl .delimiter { color:black }
1038 .syntaxhl .directive { color:#088; font-weight:bold }
1039 .syntaxhl .directive { color:#088; font-weight:bold }
1039 .syntaxhl .doc { color:#970 }
1040 .syntaxhl .doc { color:#970 }
1040 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1041 .syntaxhl .doc-string { color:#D42; font-weight:bold }
1041 .syntaxhl .doctype { color:#34b }
1042 .syntaxhl .doctype { color:#34b }
1042 .syntaxhl .entity { color:#800; font-weight:bold }
1043 .syntaxhl .entity { color:#800; font-weight:bold }
1043 .syntaxhl .error { color:#F00; background-color:#FAA }
1044 .syntaxhl .error { color:#F00; background-color:#FAA }
1044 .syntaxhl .escape { color:#666 }
1045 .syntaxhl .escape { color:#666 }
1045 .syntaxhl .exception { color:#C00; font-weight:bold }
1046 .syntaxhl .exception { color:#C00; font-weight:bold }
1046 .syntaxhl .float { color:#06D }
1047 .syntaxhl .float { color:#06D }
1047 .syntaxhl .function { color:#06B; font-weight:bold }
1048 .syntaxhl .function { color:#06B; font-weight:bold }
1048 .syntaxhl .global-variable { color:#d70 }
1049 .syntaxhl .global-variable { color:#d70 }
1049 .syntaxhl .hex { color:#02b }
1050 .syntaxhl .hex { color:#02b }
1050 .syntaxhl .imaginary { color:#f00 }
1051 .syntaxhl .imaginary { color:#f00 }
1051 .syntaxhl .include { color:#B44; font-weight:bold }
1052 .syntaxhl .include { color:#B44; font-weight:bold }
1052 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1053 .syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
1053 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1054 .syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
1054 .syntaxhl .instance-variable { color:#33B }
1055 .syntaxhl .instance-variable { color:#33B }
1055 .syntaxhl .integer { color:#06D }
1056 .syntaxhl .integer { color:#06D }
1056 .syntaxhl .key .char { color: #60f }
1057 .syntaxhl .key .char { color: #60f }
1057 .syntaxhl .key .delimiter { color: #404 }
1058 .syntaxhl .key .delimiter { color: #404 }
1058 .syntaxhl .key { color: #606 }
1059 .syntaxhl .key { color: #606 }
1059 .syntaxhl .keyword { color:#939; font-weight:bold }
1060 .syntaxhl .keyword { color:#939; font-weight:bold }
1060 .syntaxhl .label { color:#970; font-weight:bold }
1061 .syntaxhl .label { color:#970; font-weight:bold }
1061 .syntaxhl .local-variable { color:#963 }
1062 .syntaxhl .local-variable { color:#963 }
1062 .syntaxhl .namespace { color:#707; font-weight:bold }
1063 .syntaxhl .namespace { color:#707; font-weight:bold }
1063 .syntaxhl .octal { color:#40E }
1064 .syntaxhl .octal { color:#40E }
1064 .syntaxhl .operator { }
1065 .syntaxhl .operator { }
1065 .syntaxhl .predefined { color:#369; font-weight:bold }
1066 .syntaxhl .predefined { color:#369; font-weight:bold }
1066 .syntaxhl .predefined-constant { color:#069 }
1067 .syntaxhl .predefined-constant { color:#069 }
1067 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1068 .syntaxhl .predefined-type { color:#0a5; font-weight:bold }
1068 .syntaxhl .preprocessor { color:#579 }
1069 .syntaxhl .preprocessor { color:#579 }
1069 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1070 .syntaxhl .pseudo-class { color:#00C; font-weight:bold }
1070 .syntaxhl .regexp .content { color:#808 }
1071 .syntaxhl .regexp .content { color:#808 }
1071 .syntaxhl .regexp .delimiter { color:#404 }
1072 .syntaxhl .regexp .delimiter { color:#404 }
1072 .syntaxhl .regexp .modifier { color:#C2C }
1073 .syntaxhl .regexp .modifier { color:#C2C }
1073 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1074 .syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
1074 .syntaxhl .reserved { color:#080; font-weight:bold }
1075 .syntaxhl .reserved { color:#080; font-weight:bold }
1075 .syntaxhl .shell .content { color:#2B2 }
1076 .syntaxhl .shell .content { color:#2B2 }
1076 .syntaxhl .shell .delimiter { color:#161 }
1077 .syntaxhl .shell .delimiter { color:#161 }
1077 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1078 .syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
1078 .syntaxhl .string .char { color: #46a }
1079 .syntaxhl .string .char { color: #46a }
1079 .syntaxhl .string .content { color: #46a }
1080 .syntaxhl .string .content { color: #46a }
1080 .syntaxhl .string .delimiter { color: #46a }
1081 .syntaxhl .string .delimiter { color: #46a }
1081 .syntaxhl .string .modifier { color: #46a }
1082 .syntaxhl .string .modifier { color: #46a }
1082 .syntaxhl .symbol .content { color:#d33 }
1083 .syntaxhl .symbol .content { color:#d33 }
1083 .syntaxhl .symbol .delimiter { color:#d33 }
1084 .syntaxhl .symbol .delimiter { color:#d33 }
1084 .syntaxhl .symbol { color:#d33 }
1085 .syntaxhl .symbol { color:#d33 }
1085 .syntaxhl .tag { color:#070 }
1086 .syntaxhl .tag { color:#070 }
1086 .syntaxhl .type { color:#339; font-weight:bold }
1087 .syntaxhl .type { color:#339; font-weight:bold }
1087 .syntaxhl .value { color: #088; }
1088 .syntaxhl .value { color: #088; }
1088 .syntaxhl .variable { color:#037 }
1089 .syntaxhl .variable { color:#037 }
1089
1090
1090 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1091 .syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
1091 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1092 .syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
1092 .syntaxhl .change { color: #bbf; background: #007; }
1093 .syntaxhl .change { color: #bbf; background: #007; }
1093 .syntaxhl .head { color: #f8f; background: #505 }
1094 .syntaxhl .head { color: #f8f; background: #505 }
1094 .syntaxhl .head .filename { color: white; }
1095 .syntaxhl .head .filename { color: white; }
1095
1096
1096 .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; }
1097 .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; }
1097 .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; }
1098 .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; }
1098
1099
1099 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1100 .syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
1100 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1101 .syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
1101 .syntaxhl .change .change { color: #88f }
1102 .syntaxhl .change .change { color: #88f }
1102 .syntaxhl .head .head { color: #f4f }
1103 .syntaxhl .head .head { color: #f4f }
1103
1104
1104 /***** Media print specific styles *****/
1105 /***** Media print specific styles *****/
1105 @media print {
1106 @media print {
1106 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1107 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1107 #main { background: #fff; }
1108 #main { background: #fff; }
1108 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1109 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1109 #wiki_add_attachment { display:none; }
1110 #wiki_add_attachment { display:none; }
1110 .hide-when-print { display: none; }
1111 .hide-when-print { display: none; }
1111 .autoscroll {overflow-x: visible;}
1112 .autoscroll {overflow-x: visible;}
1112 table.list {margin-top:0.5em;}
1113 table.list {margin-top:0.5em;}
1113 table.list th, table.list td {border: 1px solid #aaa;}
1114 table.list th, table.list td {border: 1px solid #aaa;}
1114 }
1115 }
1115
1116
1116 /* Accessibility specific styles */
1117 /* Accessibility specific styles */
1117 .hidden-for-sighted {
1118 .hidden-for-sighted {
1118 position:absolute;
1119 position:absolute;
1119 left:-10000px;
1120 left:-10000px;
1120 top:auto;
1121 top:auto;
1121 width:1px;
1122 width:1px;
1122 height:1px;
1123 height:1px;
1123 overflow:hidden;
1124 overflow:hidden;
1124 }
1125 }
General Comments 0
You need to be logged in to leave comments. Login now