##// END OF EJS Templates
Add a time tracking block for 'My page' (#615)....
Jean-Philippe Lang -
r1245:a8fcf8487d41
parent child
Show More
@@ -0,0 +1,52
1 <h3><%=l(:label_spent_time)%> (<%= l(:label_last_n_days, 7) %>)</h3>
2 <%
3 entries = TimeEntry.find(:all,
4 :conditions => ["#{TimeEntry.table_name}.user_id = ? AND #{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", @user.id, Date.today - 6, Date.today],
5 :include => [:activity, :project, {:issue => [:tracker, :status]}],
6 :order => "#{TimeEntry.table_name}.spent_on DESC, #{Project.table_name}.name ASC, #{Tracker.table_name}.position ASC, #{Issue.table_name}.id ASC")
7 entries_by_day = entries.group_by(&:spent_on)
8 %>
9
10 <div class="total-hours">
11 <p><%= l(:label_total) %>: <%= html_hours("%.2f" % entries.sum(&:hours).to_f) %></p>
12 </div>
13
14 <% if entries.any? %>
15 <table class="list time-entries">
16 <thead>
17 <th><%= l(:label_activity) %></th>
18 <th><%= l(:label_project) %></th>
19 <th><%= l(:field_comments) %></th>
20 <th><%= l(:field_hours) %></th>
21 <th></th>
22 </thead>
23 <tbody>
24 <% entries_by_day.keys.sort.reverse.each do |day| %>
25 <tr class="odd">
26 <td><strong><%= day == Date.today ? l(:label_today).titleize : format_date(day) %></strong></td>
27 <td colspan="2"></td>
28 <td class="hours"><em><%= html_hours("%.2f" % entries_by_day[day].sum(&:hours).to_f) %></em></td>
29 <td></td>
30 </tr>
31 <% entries_by_day[day].each do |entry| -%>
32 <tr class="time-entry" style="border-bottom: 1px solid #f5f5f5;">
33 <td class="activity"><%=h entry.activity %></td>
34 <td class="subject"><%=h entry.project %> <%= ' - ' + link_to_issue(entry.issue, :title => h("#{entry.issue.subject} (#{entry.issue.status})")) if entry.issue %></td>
35 <td class="comments"><%=h entry.comments %></td>
36 <td class="hours"><%= html_hours("%.2f" % entry.hours) %></td>
37 <td align="center">
38 <% if entry.editable_by?(@user) -%>
39 <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry},
40 :title => l(:button_edit) %>
41 <%= link_to image_tag('delete.png'), {:controller => 'timelog', :action => 'destroy', :id => entry},
42 :confirm => l(:text_are_you_sure),
43 :method => :post,
44 :title => l(:button_delete) %>
45 <% end -%>
46 </td>
47 </tr>
48 <% end -%>
49 <% end -%>
50 </tbdoy>
51 </table>
52 <% end %>
@@ -1,160 +1,161
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 MyController < ApplicationController
18 class MyController < ApplicationController
19 helper :issues
19 helper :issues
20
20
21 layout 'base'
21 layout 'base'
22 before_filter :require_login
22 before_filter :require_login
23
23
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 'issuesreportedbyme' => :label_reported_issues,
25 'issuesreportedbyme' => :label_reported_issues,
26 'issueswatched' => :label_watched_issues,
26 'issueswatched' => :label_watched_issues,
27 'news' => :label_news_latest,
27 'news' => :label_news_latest,
28 'calendar' => :label_calendar,
28 'calendar' => :label_calendar,
29 'documents' => :label_document_plural
29 'documents' => :label_document_plural,
30 'timelog' => :label_spent_time
30 }.freeze
31 }.freeze
31
32
32 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
33 'right' => ['issuesreportedbyme']
34 'right' => ['issuesreportedbyme']
34 }.freeze
35 }.freeze
35
36
36 verify :xhr => true,
37 verify :xhr => true,
37 :session => :page_layout,
38 :session => :page_layout,
38 :only => [:add_block, :remove_block, :order_blocks]
39 :only => [:add_block, :remove_block, :order_blocks]
39
40
40 def index
41 def index
41 page
42 page
42 render :action => 'page'
43 render :action => 'page'
43 end
44 end
44
45
45 # Show user's page
46 # Show user's page
46 def page
47 def page
47 @user = User.current
48 @user = User.current
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 end
50 end
50
51
51 # Edit user's account
52 # Edit user's account
52 def account
53 def account
53 @user = User.current
54 @user = User.current
54 @pref = @user.pref
55 @pref = @user.pref
55 if request.post?
56 if request.post?
56 @user.attributes = params[:user]
57 @user.attributes = params[:user]
57 @user.mail_notification = (params[:notification_option] == 'all')
58 @user.mail_notification = (params[:notification_option] == 'all')
58 @user.pref.attributes = params[:pref]
59 @user.pref.attributes = params[:pref]
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 if @user.save
61 if @user.save
61 @user.pref.save
62 @user.pref.save
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 set_language_if_valid @user.language
64 set_language_if_valid @user.language
64 flash[:notice] = l(:notice_account_updated)
65 flash[:notice] = l(:notice_account_updated)
65 redirect_to :action => 'account'
66 redirect_to :action => 'account'
66 return
67 return
67 end
68 end
68 end
69 end
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
70 @notification_options = [[l(:label_user_mail_option_all), 'all'],
70 [l(:label_user_mail_option_none), 'none']]
71 [l(:label_user_mail_option_none), 'none']]
71 # Only users that belong to more than 1 project can select projects for which they are notified
72 # Only users that belong to more than 1 project can select projects for which they are notified
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
73 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
74 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
75 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
75 end
76 end
76
77
77 # Manage user's password
78 # Manage user's password
78 def password
79 def password
79 @user = User.current
80 @user = User.current
80 flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
81 flash[:error] = l(:notice_can_t_change_password) and redirect_to :action => 'account' and return if @user.auth_source_id
81 if request.post?
82 if request.post?
82 if @user.check_password?(params[:password])
83 if @user.check_password?(params[:password])
83 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
84 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
84 if @user.save
85 if @user.save
85 flash[:notice] = l(:notice_account_password_updated)
86 flash[:notice] = l(:notice_account_password_updated)
86 redirect_to :action => 'account'
87 redirect_to :action => 'account'
87 end
88 end
88 else
89 else
89 flash[:error] = l(:notice_account_wrong_password)
90 flash[:error] = l(:notice_account_wrong_password)
90 end
91 end
91 end
92 end
92 end
93 end
93
94
94 # Create a new feeds key
95 # Create a new feeds key
95 def reset_rss_key
96 def reset_rss_key
96 if request.post? && User.current.rss_token
97 if request.post? && User.current.rss_token
97 User.current.rss_token.destroy
98 User.current.rss_token.destroy
98 flash[:notice] = l(:notice_feeds_access_key_reseted)
99 flash[:notice] = l(:notice_feeds_access_key_reseted)
99 end
100 end
100 redirect_to :action => 'account'
101 redirect_to :action => 'account'
101 end
102 end
102
103
103 # User's page layout configuration
104 # User's page layout configuration
104 def page_layout
105 def page_layout
105 @user = User.current
106 @user = User.current
106 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
107 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
107 session[:page_layout] = @blocks
108 session[:page_layout] = @blocks
108 %w(top left right).each {|f| session[:page_layout][f] ||= [] }
109 %w(top left right).each {|f| session[:page_layout][f] ||= [] }
109 @block_options = []
110 @block_options = []
110 BLOCKS.each {|k, v| @block_options << [l(v), k]}
111 BLOCKS.each {|k, v| @block_options << [l(v), k]}
111 end
112 end
112
113
113 # Add a block to user's page
114 # Add a block to user's page
114 # The block is added on top of the page
115 # The block is added on top of the page
115 # params[:block] : id of the block to add
116 # params[:block] : id of the block to add
116 def add_block
117 def add_block
117 block = params[:block]
118 block = params[:block]
118 render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
119 render(:nothing => true) and return unless block && (BLOCKS.keys.include? block)
119 @user = User.current
120 @user = User.current
120 # remove if already present in a group
121 # remove if already present in a group
121 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
122 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
122 # add it on top
123 # add it on top
123 session[:page_layout]['top'].unshift block
124 session[:page_layout]['top'].unshift block
124 render :partial => "block", :locals => {:user => @user, :block_name => block}
125 render :partial => "block", :locals => {:user => @user, :block_name => block}
125 end
126 end
126
127
127 # Remove a block to user's page
128 # Remove a block to user's page
128 # params[:block] : id of the block to remove
129 # params[:block] : id of the block to remove
129 def remove_block
130 def remove_block
130 block = params[:block]
131 block = params[:block]
131 # remove block in all groups
132 # remove block in all groups
132 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
133 %w(top left right).each {|f| (session[:page_layout][f] ||= []).delete block }
133 render :nothing => true
134 render :nothing => true
134 end
135 end
135
136
136 # Change blocks order on user's page
137 # Change blocks order on user's page
137 # params[:group] : group to order (top, left or right)
138 # params[:group] : group to order (top, left or right)
138 # params[:list-(top|left|right)] : array of block ids of the group
139 # params[:list-(top|left|right)] : array of block ids of the group
139 def order_blocks
140 def order_blocks
140 group = params[:group]
141 group = params[:group]
141 group_items = params["list-#{group}"]
142 group_items = params["list-#{group}"]
142 if group_items and group_items.is_a? Array
143 if group_items and group_items.is_a? Array
143 # remove group blocks if they are presents in other groups
144 # remove group blocks if they are presents in other groups
144 %w(top left right).each {|f|
145 %w(top left right).each {|f|
145 session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
146 session[:page_layout][f] = (session[:page_layout][f] || []) - group_items
146 }
147 }
147 session[:page_layout][group] = group_items
148 session[:page_layout][group] = group_items
148 end
149 end
149 render :nothing => true
150 render :nothing => true
150 end
151 end
151
152
152 # Save user's page layout
153 # Save user's page layout
153 def page_layout_save
154 def page_layout_save
154 @user = User.current
155 @user = User.current
155 @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
156 @user.pref[:my_page_layout] = session[:page_layout] if session[:page_layout]
156 @user.pref.save
157 @user.pref.save
157 session[:page_layout] = nil
158 session[:page_layout] = nil
158 redirect_to :action => 'page'
159 redirect_to :action => 'page'
159 end
160 end
160 end
161 end
@@ -1,239 +1,241
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 layout 'base'
19 layout 'base'
20 menu_item :issues
20 menu_item :issues
21 before_filter :find_project, :authorize
21 before_filter :find_project, :authorize
22
22
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
23 verify :method => :post, :only => :destroy, :redirect_to => { :action => :details }
24
24
25 helper :sort
25 helper :sort
26 include SortHelper
26 include SortHelper
27 helper :issues
27 helper :issues
28 include TimelogHelper
28 include TimelogHelper
29
29
30 def report
30 def report
31 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
31 @available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
32 :klass => Project,
32 :klass => Project,
33 :label => :label_project},
33 :label => :label_project},
34 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
34 'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
35 :klass => Version,
35 :klass => Version,
36 :label => :label_version},
36 :label => :label_version},
37 'category' => {:sql => "#{Issue.table_name}.category_id",
37 'category' => {:sql => "#{Issue.table_name}.category_id",
38 :klass => IssueCategory,
38 :klass => IssueCategory,
39 :label => :field_category},
39 :label => :field_category},
40 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
40 'member' => {:sql => "#{TimeEntry.table_name}.user_id",
41 :klass => User,
41 :klass => User,
42 :label => :label_member},
42 :label => :label_member},
43 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
43 'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
44 :klass => Tracker,
44 :klass => Tracker,
45 :label => :label_tracker},
45 :label => :label_tracker},
46 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
46 'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
47 :klass => Enumeration,
47 :klass => Enumeration,
48 :label => :label_activity}
48 :label => :label_activity}
49 }
49 }
50
50
51 @criterias = params[:criterias] || []
51 @criterias = params[:criterias] || []
52 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
52 @criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
53 @criterias.uniq!
53 @criterias.uniq!
54 @criterias = @criterias[0,3]
54 @criterias = @criterias[0,3]
55
55
56 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
56 @columns = (params[:period] && %w(year month week).include?(params[:period])) ? params[:period] : 'month'
57
57
58 if params[:date_from]
58 if params[:date_from]
59 begin; @date_from = params[:date_from].to_date; rescue; end
59 begin; @date_from = params[:date_from].to_date; rescue; end
60 end
60 end
61 if params[:date_to]
61 if params[:date_to]
62 begin; @date_to = params[:date_to].to_date; rescue; end
62 begin; @date_to = params[:date_to].to_date; rescue; end
63 end
63 end
64 @date_from ||= Date.civil(Date.today.year, 1, 1)
64 @date_from ||= Date.civil(Date.today.year, 1, 1)
65 @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1
65 @date_to ||= (Date.civil(Date.today.year, Date.today.month, 1) >> 1) - 1
66
66
67 unless @criterias.empty?
67 unless @criterias.empty?
68 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
68 sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
69 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
69 sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
70
70
71 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
71 sql = "SELECT #{sql_select}, tyear, tmonth, tweek, SUM(hours) AS hours"
72 sql << " FROM #{TimeEntry.table_name}"
72 sql << " FROM #{TimeEntry.table_name}"
73 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
73 sql << " LEFT JOIN #{Issue.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id"
74 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
74 sql << " LEFT JOIN #{Project.table_name} ON #{TimeEntry.table_name}.project_id = #{Project.table_name}.id"
75 sql << " WHERE (#{Project.table_name}.id = %s OR #{Project.table_name}.parent_id = %s)" % [@project.id, @project.id]
75 sql << " WHERE (#{Project.table_name}.id = %s OR #{Project.table_name}.parent_id = %s)" % [@project.id, @project.id]
76 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
76 sql << " AND (%s)" % Project.allowed_to_condition(User.current, :view_time_entries)
77 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
77 sql << " AND spent_on BETWEEN '%s' AND '%s'" % [ActiveRecord::Base.connection.quoted_date(@date_from.to_time), ActiveRecord::Base.connection.quoted_date(@date_to.to_time)]
78 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
78 sql << " GROUP BY #{sql_group_by}, tyear, tmonth, tweek"
79
79
80 @hours = ActiveRecord::Base.connection.select_all(sql)
80 @hours = ActiveRecord::Base.connection.select_all(sql)
81
81
82 @hours.each do |row|
82 @hours.each do |row|
83 case @columns
83 case @columns
84 when 'year'
84 when 'year'
85 row['year'] = row['tyear']
85 row['year'] = row['tyear']
86 when 'month'
86 when 'month'
87 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
87 row['month'] = "#{row['tyear']}-#{row['tmonth']}"
88 when 'week'
88 when 'week'
89 row['week'] = "#{row['tyear']}-#{row['tweek']}"
89 row['week'] = "#{row['tyear']}-#{row['tweek']}"
90 end
90 end
91 end
91 end
92
92
93 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
93 @total_hours = @hours.inject(0) {|s,k| s = s + k['hours'].to_f}
94 end
94 end
95
95
96 @periods = []
96 @periods = []
97 date_from = @date_from
97 date_from = @date_from
98 # 100 columns max
98 # 100 columns max
99 while date_from < @date_to && @periods.length < 100
99 while date_from < @date_to && @periods.length < 100
100 case @columns
100 case @columns
101 when 'year'
101 when 'year'
102 @periods << "#{date_from.year}"
102 @periods << "#{date_from.year}"
103 date_from = date_from >> 12
103 date_from = date_from >> 12
104 when 'month'
104 when 'month'
105 @periods << "#{date_from.year}-#{date_from.month}"
105 @periods << "#{date_from.year}-#{date_from.month}"
106 date_from = date_from >> 1
106 date_from = date_from >> 1
107 when 'week'
107 when 'week'
108 @periods << "#{date_from.year}-#{date_from.cweek}"
108 @periods << "#{date_from.year}-#{date_from.cweek}"
109 date_from = date_from + 7
109 date_from = date_from + 7
110 end
110 end
111 end
111 end
112
112
113 render :layout => false if request.xhr?
113 render :layout => false if request.xhr?
114 end
114 end
115
115
116 def details
116 def details
117 sort_init 'spent_on', 'desc'
117 sort_init 'spent_on', 'desc'
118 sort_update
118 sort_update
119
119
120 @free_period = false
120 @free_period = false
121 @from, @to = nil, nil
121 @from, @to = nil, nil
122
122
123 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
123 if params[:period_type] == '1' || (params[:period_type].nil? && !params[:period].nil?)
124 case params[:period].to_s
124 case params[:period].to_s
125 when 'today'
125 when 'today'
126 @from = @to = Date.today
126 @from = @to = Date.today
127 when 'yesterday'
127 when 'yesterday'
128 @from = @to = Date.today - 1
128 @from = @to = Date.today - 1
129 when 'current_week'
129 when 'current_week'
130 @from = Date.today - (Date.today.cwday - 1)%7
130 @from = Date.today - (Date.today.cwday - 1)%7
131 @to = @from + 6
131 @to = @from + 6
132 when 'last_week'
132 when 'last_week'
133 @from = Date.today - 7 - (Date.today.cwday - 1)%7
133 @from = Date.today - 7 - (Date.today.cwday - 1)%7
134 @to = @from + 6
134 @to = @from + 6
135 when '7_days'
135 when '7_days'
136 @from = Date.today - 7
136 @from = Date.today - 7
137 @to = Date.today
137 @to = Date.today
138 when 'current_month'
138 when 'current_month'
139 @from = Date.civil(Date.today.year, Date.today.month, 1)
139 @from = Date.civil(Date.today.year, Date.today.month, 1)
140 @to = (@from >> 1) - 1
140 @to = (@from >> 1) - 1
141 when 'last_month'
141 when 'last_month'
142 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
142 @from = Date.civil(Date.today.year, Date.today.month, 1) << 1
143 @to = (@from >> 1) - 1
143 @to = (@from >> 1) - 1
144 when '30_days'
144 when '30_days'
145 @from = Date.today - 30
145 @from = Date.today - 30
146 @to = Date.today
146 @to = Date.today
147 when 'current_year'
147 when 'current_year'
148 @from = Date.civil(Date.today.year, 1, 1)
148 @from = Date.civil(Date.today.year, 1, 1)
149 @to = Date.civil(Date.today.year, 12, 31)
149 @to = Date.civil(Date.today.year, 12, 31)
150 end
150 end
151 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
151 elsif params[:period_type] == '2' || (params[:period_type].nil? && (!params[:from].nil? || !params[:to].nil?))
152 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
152 begin; @from = params[:from].to_s.to_date unless params[:from].blank?; rescue; end
153 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
153 begin; @to = params[:to].to_s.to_date unless params[:to].blank?; rescue; end
154 @free_period = true
154 @free_period = true
155 else
155 else
156 # default
156 # default
157 end
157 end
158
158
159 @from, @to = @to, @from if @from && @to && @from > @to
159 @from, @to = @to, @from if @from && @to && @from > @to
160
160
161 cond = ARCondition.new
161 cond = ARCondition.new
162 cond << (@issue.nil? ? ["(#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?)", @project.id, @project.id] :
162 cond << (@issue.nil? ? ["(#{Project.table_name}.id = ? OR #{Project.table_name}.parent_id = ?)", @project.id, @project.id] :
163 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
163 ["#{TimeEntry.table_name}.issue_id = ?", @issue.id])
164
164
165 if @from
165 if @from
166 if @to
166 if @to
167 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
167 cond << ['spent_on BETWEEN ? AND ?', @from, @to]
168 else
168 else
169 cond << ['spent_on >= ?', @from]
169 cond << ['spent_on >= ?', @from]
170 end
170 end
171 elsif @to
171 elsif @to
172 cond << ['spent_on <= ?', @to]
172 cond << ['spent_on <= ?', @to]
173 end
173 end
174
174
175 TimeEntry.visible_by(User.current) do
175 TimeEntry.visible_by(User.current) do
176 respond_to do |format|
176 respond_to do |format|
177 format.html {
177 format.html {
178 # Paginate results
178 # Paginate results
179 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
179 @entry_count = TimeEntry.count(:include => :project, :conditions => cond.conditions)
180 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
180 @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page']
181 @entries = TimeEntry.find(:all,
181 @entries = TimeEntry.find(:all,
182 :include => [:project, :activity, :user, {:issue => :tracker}],
182 :include => [:project, :activity, :user, {:issue => :tracker}],
183 :conditions => cond.conditions,
183 :conditions => cond.conditions,
184 :order => sort_clause,
184 :order => sort_clause,
185 :limit => @entry_pages.items_per_page,
185 :limit => @entry_pages.items_per_page,
186 :offset => @entry_pages.current.offset)
186 :offset => @entry_pages.current.offset)
187 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
187 @total_hours = TimeEntry.sum(:hours, :include => :project, :conditions => cond.conditions).to_f
188 render :layout => !request.xhr?
188 render :layout => !request.xhr?
189 }
189 }
190 format.csv {
190 format.csv {
191 # Export all entries
191 # Export all entries
192 @entries = TimeEntry.find(:all,
192 @entries = TimeEntry.find(:all,
193 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
193 :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
194 :conditions => cond.conditions,
194 :conditions => cond.conditions,
195 :order => sort_clause)
195 :order => sort_clause)
196 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
196 send_data(entries_to_csv(@entries).read, :type => 'text/csv; header=present', :filename => 'timelog.csv')
197 }
197 }
198 end
198 end
199 end
199 end
200 end
200 end
201
201
202 def edit
202 def edit
203 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
203 render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
204 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
204 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
205 @time_entry.attributes = params[:time_entry]
205 @time_entry.attributes = params[:time_entry]
206 if request.post? and @time_entry.save
206 if request.post? and @time_entry.save
207 flash[:notice] = l(:notice_successful_update)
207 flash[:notice] = l(:notice_successful_update)
208 redirect_to :action => 'details', :project_id => @time_entry.project
208 redirect_to :action => 'details', :project_id => @time_entry.project
209 return
209 return
210 end
210 end
211 @activities = Enumeration::get_values('ACTI')
211 @activities = Enumeration::get_values('ACTI')
212 end
212 end
213
213
214 def destroy
214 def destroy
215 render_404 and return unless @time_entry
215 render_404 and return unless @time_entry
216 render_403 and return unless @time_entry.editable_by?(User.current)
216 render_403 and return unless @time_entry.editable_by?(User.current)
217 @time_entry.destroy
217 @time_entry.destroy
218 flash[:notice] = l(:notice_successful_delete)
218 flash[:notice] = l(:notice_successful_delete)
219 redirect_to :back
220 rescue RedirectBackError
219 redirect_to :action => 'details', :project_id => @time_entry.project
221 redirect_to :action => 'details', :project_id => @time_entry.project
220 end
222 end
221
223
222 private
224 private
223 def find_project
225 def find_project
224 if params[:id]
226 if params[:id]
225 @time_entry = TimeEntry.find(params[:id])
227 @time_entry = TimeEntry.find(params[:id])
226 @project = @time_entry.project
228 @project = @time_entry.project
227 elsif params[:issue_id]
229 elsif params[:issue_id]
228 @issue = Issue.find(params[:issue_id])
230 @issue = Issue.find(params[:issue_id])
229 @project = @issue.project
231 @project = @issue.project
230 elsif params[:project_id]
232 elsif params[:project_id]
231 @project = Project.find(params[:project_id])
233 @project = Project.find(params[:project_id])
232 else
234 else
233 render_404
235 render_404
234 return false
236 return false
235 end
237 end
236 rescue ActiveRecord::RecordNotFound
238 rescue ActiveRecord::RecordNotFound
237 render_404
239 render_404
238 end
240 end
239 end
241 end
@@ -1,471 +1,471
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ApplicationHelper
18 module ApplicationHelper
19 include Redmine::WikiFormatting::Macros::Definitions
19 include Redmine::WikiFormatting::Macros::Definitions
20
20
21 def current_role
21 def current_role
22 @current_role ||= User.current.role_for_project(@project)
22 @current_role ||= User.current.role_for_project(@project)
23 end
23 end
24
24
25 # Return true if user is authorized for controller/action, otherwise false
25 # Return true if user is authorized for controller/action, otherwise false
26 def authorize_for(controller, action)
26 def authorize_for(controller, action)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 end
28 end
29
29
30 # Display a link if user is authorized
30 # Display a link if user is authorized
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 end
33 end
34
34
35 # Display a link to user's account page
35 # Display a link to user's account page
36 def link_to_user(user)
36 def link_to_user(user)
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
38 end
38 end
39
39
40 def link_to_issue(issue)
40 def link_to_issue(issue, options={})
41 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
42 end
42 end
43
43
44 def toggle_link(name, id, options={})
44 def toggle_link(name, id, options={})
45 onclick = "Element.toggle('#{id}'); "
45 onclick = "Element.toggle('#{id}'); "
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
47 onclick << "return false;"
47 onclick << "return false;"
48 link_to(name, "#", :onclick => onclick)
48 link_to(name, "#", :onclick => onclick)
49 end
49 end
50
50
51 def show_and_goto_link(name, id, options={})
51 def show_and_goto_link(name, id, options={})
52 onclick = "Element.show('#{id}'); "
52 onclick = "Element.show('#{id}'); "
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << "Element.scrollTo('#{id}'); "
54 onclick << "Element.scrollTo('#{id}'); "
55 onclick << "return false;"
55 onclick << "return false;"
56 link_to(name, "#", options.merge(:onclick => onclick))
56 link_to(name, "#", options.merge(:onclick => onclick))
57 end
57 end
58
58
59 def image_to_function(name, function, html_options = {})
59 def image_to_function(name, function, html_options = {})
60 html_options.symbolize_keys!
60 html_options.symbolize_keys!
61 tag(:input, html_options.merge({
61 tag(:input, html_options.merge({
62 :type => "image", :src => image_path(name),
62 :type => "image", :src => image_path(name),
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
64 }))
64 }))
65 end
65 end
66
66
67 def prompt_to_remote(name, text, param, url, html_options = {})
67 def prompt_to_remote(name, text, param, url, html_options = {})
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
69 link_to name, {}, html_options
69 link_to name, {}, html_options
70 end
70 end
71
71
72 def format_date(date)
72 def format_date(date)
73 return nil unless date
73 return nil unless date
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
76 date.strftime(@date_format)
76 date.strftime(@date_format)
77 end
77 end
78
78
79 def format_time(time, include_date = true)
79 def format_time(time, include_date = true)
80 return nil unless time
80 return nil unless time
81 time = time.to_time if time.is_a?(String)
81 time = time.to_time if time.is_a?(String)
82 zone = User.current.time_zone
82 zone = User.current.time_zone
83 if time.utc?
83 if time.utc?
84 local = zone ? zone.adjust(time) : time.getlocal
84 local = zone ? zone.adjust(time) : time.getlocal
85 else
85 else
86 local = zone ? zone.adjust(time.getutc) : time
86 local = zone ? zone.adjust(time.getutc) : time
87 end
87 end
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
91 end
91 end
92
92
93 def html_hours(text)
93 def html_hours(text)
94 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
94 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
95 end
95 end
96
96
97 def authoring(created, author)
97 def authoring(created, author)
98 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
98 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
99 l(:label_added_time_by, author || 'Anonymous', time_tag)
99 l(:label_added_time_by, author || 'Anonymous', time_tag)
100 end
100 end
101
101
102 def l_or_humanize(s)
102 def l_or_humanize(s)
103 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
103 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
104 end
104 end
105
105
106 def day_name(day)
106 def day_name(day)
107 l(:general_day_names).split(',')[day-1]
107 l(:general_day_names).split(',')[day-1]
108 end
108 end
109
109
110 def month_name(month)
110 def month_name(month)
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
112 end
112 end
113
113
114 def pagination_links_full(paginator, count=nil, options={})
114 def pagination_links_full(paginator, count=nil, options={})
115 page_param = options.delete(:page_param) || :page
115 page_param = options.delete(:page_param) || :page
116 url_param = params.dup
116 url_param = params.dup
117 # don't reuse params if filters are present
117 # don't reuse params if filters are present
118 url_param.clear if url_param.has_key?(:set_filter)
118 url_param.clear if url_param.has_key?(:set_filter)
119
119
120 html = ''
120 html = ''
121 html << link_to_remote(('&#171; ' + l(:label_previous)),
121 html << link_to_remote(('&#171; ' + l(:label_previous)),
122 {:update => 'content',
122 {:update => 'content',
123 :url => url_param.merge(page_param => paginator.current.previous),
123 :url => url_param.merge(page_param => paginator.current.previous),
124 :complete => 'window.scrollTo(0,0)'},
124 :complete => 'window.scrollTo(0,0)'},
125 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
125 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
126
126
127 html << (pagination_links_each(paginator, options) do |n|
127 html << (pagination_links_each(paginator, options) do |n|
128 link_to_remote(n.to_s,
128 link_to_remote(n.to_s,
129 {:url => {:params => url_param.merge(page_param => n)},
129 {:url => {:params => url_param.merge(page_param => n)},
130 :update => 'content',
130 :update => 'content',
131 :complete => 'window.scrollTo(0,0)'},
131 :complete => 'window.scrollTo(0,0)'},
132 {:href => url_for(:params => url_param.merge(page_param => n))})
132 {:href => url_for(:params => url_param.merge(page_param => n))})
133 end || '')
133 end || '')
134
134
135 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
135 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
136 {:update => 'content',
136 {:update => 'content',
137 :url => url_param.merge(page_param => paginator.current.next),
137 :url => url_param.merge(page_param => paginator.current.next),
138 :complete => 'window.scrollTo(0,0)'},
138 :complete => 'window.scrollTo(0,0)'},
139 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
139 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
140
140
141 unless count.nil?
141 unless count.nil?
142 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
142 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
143 end
143 end
144
144
145 html
145 html
146 end
146 end
147
147
148 def per_page_links(selected=nil)
148 def per_page_links(selected=nil)
149 url_param = params.dup
149 url_param = params.dup
150 url_param.clear if url_param.has_key?(:set_filter)
150 url_param.clear if url_param.has_key?(:set_filter)
151
151
152 links = Setting.per_page_options_array.collect do |n|
152 links = Setting.per_page_options_array.collect do |n|
153 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
153 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
154 {:href => url_for(url_param.merge(:per_page => n))})
154 {:href => url_for(url_param.merge(:per_page => n))})
155 end
155 end
156 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
156 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
157 end
157 end
158
158
159 def html_title(*args)
159 def html_title(*args)
160 if args.empty?
160 if args.empty?
161 title = []
161 title = []
162 title << @project.name if @project
162 title << @project.name if @project
163 title += @html_title if @html_title
163 title += @html_title if @html_title
164 title << Setting.app_title
164 title << Setting.app_title
165 title.compact.join(' - ')
165 title.compact.join(' - ')
166 else
166 else
167 @html_title ||= []
167 @html_title ||= []
168 @html_title += args
168 @html_title += args
169 end
169 end
170 end
170 end
171
171
172 def accesskey(s)
172 def accesskey(s)
173 Redmine::AccessKeys.key_for s
173 Redmine::AccessKeys.key_for s
174 end
174 end
175
175
176 # Formats text according to system settings.
176 # Formats text according to system settings.
177 # 2 ways to call this method:
177 # 2 ways to call this method:
178 # * with a String: textilizable(text, options)
178 # * with a String: textilizable(text, options)
179 # * with an object and one of its attribute: textilizable(issue, :description, options)
179 # * with an object and one of its attribute: textilizable(issue, :description, options)
180 def textilizable(*args)
180 def textilizable(*args)
181 options = args.last.is_a?(Hash) ? args.pop : {}
181 options = args.last.is_a?(Hash) ? args.pop : {}
182 case args.size
182 case args.size
183 when 1
183 when 1
184 obj = nil
184 obj = nil
185 text = args.shift
185 text = args.shift
186 when 2
186 when 2
187 obj = args.shift
187 obj = args.shift
188 text = obj.send(args.shift).to_s
188 text = obj.send(args.shift).to_s
189 else
189 else
190 raise ArgumentError, 'invalid arguments to textilizable'
190 raise ArgumentError, 'invalid arguments to textilizable'
191 end
191 end
192 return '' if text.blank?
192 return '' if text.blank?
193
193
194 only_path = options.delete(:only_path) == false ? false : true
194 only_path = options.delete(:only_path) == false ? false : true
195
195
196 # when using an image link, try to use an attachment, if possible
196 # when using an image link, try to use an attachment, if possible
197 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
197 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
198
198
199 if attachments
199 if attachments
200 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
200 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
201 style = $1
201 style = $1
202 filename = $6
202 filename = $6
203 rf = Regexp.new(filename, Regexp::IGNORECASE)
203 rf = Regexp.new(filename, Regexp::IGNORECASE)
204 # search for the picture in attachments
204 # search for the picture in attachments
205 if found = attachments.detect { |att| att.filename =~ rf }
205 if found = attachments.detect { |att| att.filename =~ rf }
206 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found.id
206 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found.id
207 "!#{style}#{image_url}!"
207 "!#{style}#{image_url}!"
208 else
208 else
209 "!#{style}#{filename}!"
209 "!#{style}#{filename}!"
210 end
210 end
211 end
211 end
212 end
212 end
213
213
214 text = (Setting.text_formatting == 'textile') ?
214 text = (Setting.text_formatting == 'textile') ?
215 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
215 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
216 simple_format(auto_link(h(text)))
216 simple_format(auto_link(h(text)))
217
217
218 # different methods for formatting wiki links
218 # different methods for formatting wiki links
219 case options[:wiki_links]
219 case options[:wiki_links]
220 when :local
220 when :local
221 # used for local links to html files
221 # used for local links to html files
222 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
222 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
223 when :anchor
223 when :anchor
224 # used for single-file wiki export
224 # used for single-file wiki export
225 format_wiki_link = Proc.new {|project, title| "##{title}" }
225 format_wiki_link = Proc.new {|project, title| "##{title}" }
226 else
226 else
227 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
227 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
228 end
228 end
229
229
230 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
230 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
231
231
232 # Wiki links
232 # Wiki links
233 #
233 #
234 # Examples:
234 # Examples:
235 # [[mypage]]
235 # [[mypage]]
236 # [[mypage|mytext]]
236 # [[mypage|mytext]]
237 # wiki links can refer other project wikis, using project name or identifier:
237 # wiki links can refer other project wikis, using project name or identifier:
238 # [[project:]] -> wiki starting page
238 # [[project:]] -> wiki starting page
239 # [[project:|mytext]]
239 # [[project:|mytext]]
240 # [[project:mypage]]
240 # [[project:mypage]]
241 # [[project:mypage|mytext]]
241 # [[project:mypage|mytext]]
242 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
242 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
243 link_project = project
243 link_project = project
244 esc, all, page, title = $1, $2, $3, $5
244 esc, all, page, title = $1, $2, $3, $5
245 if esc.nil?
245 if esc.nil?
246 if page =~ /^([^\:]+)\:(.*)$/
246 if page =~ /^([^\:]+)\:(.*)$/
247 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
247 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
248 page = $2
248 page = $2
249 title ||= $1 if page.blank?
249 title ||= $1 if page.blank?
250 end
250 end
251
251
252 if link_project && link_project.wiki
252 if link_project && link_project.wiki
253 # check if page exists
253 # check if page exists
254 wiki_page = link_project.wiki.find_page(page)
254 wiki_page = link_project.wiki.find_page(page)
255 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
255 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
256 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
256 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
257 else
257 else
258 # project or wiki doesn't exist
258 # project or wiki doesn't exist
259 title || page
259 title || page
260 end
260 end
261 else
261 else
262 all
262 all
263 end
263 end
264 end
264 end
265
265
266 # Redmine links
266 # Redmine links
267 #
267 #
268 # Examples:
268 # Examples:
269 # Issues:
269 # Issues:
270 # #52 -> Link to issue #52
270 # #52 -> Link to issue #52
271 # Changesets:
271 # Changesets:
272 # r52 -> Link to revision 52
272 # r52 -> Link to revision 52
273 # commit:a85130f -> Link to scmid starting with a85130f
273 # commit:a85130f -> Link to scmid starting with a85130f
274 # Documents:
274 # Documents:
275 # document#17 -> Link to document with id 17
275 # document#17 -> Link to document with id 17
276 # document:Greetings -> Link to the document with title "Greetings"
276 # document:Greetings -> Link to the document with title "Greetings"
277 # document:"Some document" -> Link to the document with title "Some document"
277 # document:"Some document" -> Link to the document with title "Some document"
278 # Versions:
278 # Versions:
279 # version#3 -> Link to version with id 3
279 # version#3 -> Link to version with id 3
280 # version:1.0.0 -> Link to version named "1.0.0"
280 # version:1.0.0 -> Link to version named "1.0.0"
281 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
281 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
282 # Attachments:
282 # Attachments:
283 # attachment:file.zip -> Link to the attachment of the current object named file.zip
283 # attachment:file.zip -> Link to the attachment of the current object named file.zip
284 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
284 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
285 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
285 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
286 link = nil
286 link = nil
287 if esc.nil?
287 if esc.nil?
288 if prefix.nil? && sep == 'r'
288 if prefix.nil? && sep == 'r'
289 if project && (changeset = project.changesets.find_by_revision(oid))
289 if project && (changeset = project.changesets.find_by_revision(oid))
290 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid},
290 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid},
291 :class => 'changeset',
291 :class => 'changeset',
292 :title => truncate(changeset.comments, 100))
292 :title => truncate(changeset.comments, 100))
293 end
293 end
294 elsif sep == '#'
294 elsif sep == '#'
295 oid = oid.to_i
295 oid = oid.to_i
296 case prefix
296 case prefix
297 when nil
297 when nil
298 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
298 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
299 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
299 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
300 :class => (issue.closed? ? 'issue closed' : 'issue'),
300 :class => (issue.closed? ? 'issue closed' : 'issue'),
301 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
301 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
302 link = content_tag('del', link) if issue.closed?
302 link = content_tag('del', link) if issue.closed?
303 end
303 end
304 when 'document'
304 when 'document'
305 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
305 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
306 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
306 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
307 :class => 'document'
307 :class => 'document'
308 end
308 end
309 when 'version'
309 when 'version'
310 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
310 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
311 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
311 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
312 :class => 'version'
312 :class => 'version'
313 end
313 end
314 end
314 end
315 elsif sep == ':'
315 elsif sep == ':'
316 # removes the double quotes if any
316 # removes the double quotes if any
317 name = oid.gsub(%r{^"(.*)"$}, "\\1")
317 name = oid.gsub(%r{^"(.*)"$}, "\\1")
318 case prefix
318 case prefix
319 when 'document'
319 when 'document'
320 if project && document = project.documents.find_by_title(name)
320 if project && document = project.documents.find_by_title(name)
321 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
321 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
322 :class => 'document'
322 :class => 'document'
323 end
323 end
324 when 'version'
324 when 'version'
325 if project && version = project.versions.find_by_name(name)
325 if project && version = project.versions.find_by_name(name)
326 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
326 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
327 :class => 'version'
327 :class => 'version'
328 end
328 end
329 when 'commit'
329 when 'commit'
330 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
330 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
331 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
331 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100)
332 end
332 end
333 when 'attachment'
333 when 'attachment'
334 if attachments && attachment = attachments.detect {|a| a.filename == name }
334 if attachments && attachment = attachments.detect {|a| a.filename == name }
335 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
335 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
336 :class => 'attachment'
336 :class => 'attachment'
337 end
337 end
338 end
338 end
339 end
339 end
340 end
340 end
341 leading + (link || "#{prefix}#{sep}#{oid}")
341 leading + (link || "#{prefix}#{sep}#{oid}")
342 end
342 end
343
343
344 text
344 text
345 end
345 end
346
346
347 # Same as Rails' simple_format helper without using paragraphs
347 # Same as Rails' simple_format helper without using paragraphs
348 def simple_format_without_paragraph(text)
348 def simple_format_without_paragraph(text)
349 text.to_s.
349 text.to_s.
350 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
350 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
351 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
351 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
352 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
352 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
353 end
353 end
354
354
355 def error_messages_for(object_name, options = {})
355 def error_messages_for(object_name, options = {})
356 options = options.symbolize_keys
356 options = options.symbolize_keys
357 object = instance_variable_get("@#{object_name}")
357 object = instance_variable_get("@#{object_name}")
358 if object && !object.errors.empty?
358 if object && !object.errors.empty?
359 # build full_messages here with controller current language
359 # build full_messages here with controller current language
360 full_messages = []
360 full_messages = []
361 object.errors.each do |attr, msg|
361 object.errors.each do |attr, msg|
362 next if msg.nil?
362 next if msg.nil?
363 msg = msg.first if msg.is_a? Array
363 msg = msg.first if msg.is_a? Array
364 if attr == "base"
364 if attr == "base"
365 full_messages << l(msg)
365 full_messages << l(msg)
366 else
366 else
367 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
367 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
368 end
368 end
369 end
369 end
370 # retrieve custom values error messages
370 # retrieve custom values error messages
371 if object.errors[:custom_values]
371 if object.errors[:custom_values]
372 object.custom_values.each do |v|
372 object.custom_values.each do |v|
373 v.errors.each do |attr, msg|
373 v.errors.each do |attr, msg|
374 next if msg.nil?
374 next if msg.nil?
375 msg = msg.first if msg.is_a? Array
375 msg = msg.first if msg.is_a? Array
376 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
376 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
377 end
377 end
378 end
378 end
379 end
379 end
380 content_tag("div",
380 content_tag("div",
381 content_tag(
381 content_tag(
382 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
382 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
383 ) +
383 ) +
384 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
384 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
385 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
385 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
386 )
386 )
387 else
387 else
388 ""
388 ""
389 end
389 end
390 end
390 end
391
391
392 def lang_options_for_select(blank=true)
392 def lang_options_for_select(blank=true)
393 (blank ? [["(auto)", ""]] : []) +
393 (blank ? [["(auto)", ""]] : []) +
394 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
394 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
395 end
395 end
396
396
397 def label_tag_for(name, option_tags = nil, options = {})
397 def label_tag_for(name, option_tags = nil, options = {})
398 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
398 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
399 content_tag("label", label_text)
399 content_tag("label", label_text)
400 end
400 end
401
401
402 def labelled_tabular_form_for(name, object, options, &proc)
402 def labelled_tabular_form_for(name, object, options, &proc)
403 options[:html] ||= {}
403 options[:html] ||= {}
404 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
404 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
405 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
405 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
406 end
406 end
407
407
408 def check_all_links(form_name)
408 def check_all_links(form_name)
409 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
409 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
410 " | " +
410 " | " +
411 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
411 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
412 end
412 end
413
413
414 def progress_bar(pcts, options={})
414 def progress_bar(pcts, options={})
415 pcts = [pcts, pcts] unless pcts.is_a?(Array)
415 pcts = [pcts, pcts] unless pcts.is_a?(Array)
416 pcts[1] = pcts[1] - pcts[0]
416 pcts[1] = pcts[1] - pcts[0]
417 pcts << (100 - pcts[1] - pcts[0])
417 pcts << (100 - pcts[1] - pcts[0])
418 width = options[:width] || '100px;'
418 width = options[:width] || '100px;'
419 legend = options[:legend] || ''
419 legend = options[:legend] || ''
420 content_tag('table',
420 content_tag('table',
421 content_tag('tr',
421 content_tag('tr',
422 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
422 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
423 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
423 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
424 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
424 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
425 ), :class => 'progress', :style => "width: #{width};") +
425 ), :class => 'progress', :style => "width: #{width};") +
426 content_tag('p', legend, :class => 'pourcent')
426 content_tag('p', legend, :class => 'pourcent')
427 end
427 end
428
428
429 def context_menu_link(name, url, options={})
429 def context_menu_link(name, url, options={})
430 options[:class] ||= ''
430 options[:class] ||= ''
431 if options.delete(:selected)
431 if options.delete(:selected)
432 options[:class] << ' icon-checked disabled'
432 options[:class] << ' icon-checked disabled'
433 options[:disabled] = true
433 options[:disabled] = true
434 end
434 end
435 if options.delete(:disabled)
435 if options.delete(:disabled)
436 options.delete(:method)
436 options.delete(:method)
437 options.delete(:confirm)
437 options.delete(:confirm)
438 options.delete(:onclick)
438 options.delete(:onclick)
439 options[:class] << ' disabled'
439 options[:class] << ' disabled'
440 url = '#'
440 url = '#'
441 end
441 end
442 link_to name, url, options
442 link_to name, url, options
443 end
443 end
444
444
445 def calendar_for(field_id)
445 def calendar_for(field_id)
446 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
446 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
447 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
447 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
448 end
448 end
449
449
450 def wikitoolbar_for(field_id)
450 def wikitoolbar_for(field_id)
451 return '' unless Setting.text_formatting == 'textile'
451 return '' unless Setting.text_formatting == 'textile'
452
452
453 help_link = l(:setting_text_formatting) + ': ' +
453 help_link = l(:setting_text_formatting) + ': ' +
454 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
454 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
455 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
455 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
456
456
457 javascript_include_tag('jstoolbar/jstoolbar') +
457 javascript_include_tag('jstoolbar/jstoolbar') +
458 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
458 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
459 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
459 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
460 end
460 end
461
461
462 def content_for(name, content = nil, &block)
462 def content_for(name, content = nil, &block)
463 @has_content ||= {}
463 @has_content ||= {}
464 @has_content[name] = true
464 @has_content[name] = true
465 super(name, content, &block)
465 super(name, content, &block)
466 end
466 end
467
467
468 def has_content?(name)
468 def has_content?(name)
469 (@has_content && @has_content[name]) || false
469 (@has_content && @has_content[name]) || false
470 end
470 end
471 end
471 end
@@ -1,576 +1,576
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #wrapper {background: white;}
10 #wrapper {background: white;}
11
11
12 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
12 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
13 #top-menu ul {margin: 0; padding: 0;}
13 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu li {
14 #top-menu li {
15 float:left;
15 float:left;
16 list-style-type:none;
16 list-style-type:none;
17 margin: 0px 0px 0px 0px;
17 margin: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
19 white-space:nowrap;
19 white-space:nowrap;
20 }
20 }
21 #top-menu a {color: #fff; padding-right: 4px;}
21 #top-menu a {color: #fff; padding-right: 4px;}
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23
23
24 #account {float:right;}
24 #account {float:right;}
25
25
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header a {color:#f8f8f8;}
27 #header a {color:#f8f8f8;}
28 #quick-search {float:right;}
28 #quick-search {float:right;}
29
29
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
31 #main-menu ul {margin: 0; padding: 0;}
31 #main-menu ul {margin: 0; padding: 0;}
32 #main-menu li {
32 #main-menu li {
33 float:left;
33 float:left;
34 list-style-type:none;
34 list-style-type:none;
35 margin: 0px 10px 0px 0px;
35 margin: 0px 10px 0px 0px;
36 padding: 0px 0px 0px 0px;
36 padding: 0px 0px 0px 0px;
37 white-space:nowrap;
37 white-space:nowrap;
38 }
38 }
39 #main-menu li a {
39 #main-menu li a {
40 display: block;
40 display: block;
41 color: #fff;
41 color: #fff;
42 text-decoration: none;
42 text-decoration: none;
43 margin: 0;
43 margin: 0;
44 padding: 4px 4px 4px 4px;
44 padding: 4px 4px 4px 4px;
45 background: #2C4056;
45 background: #2C4056;
46 }
46 }
47 #main-menu li a:hover, #main-menu li a.selected {background:#759FCF;}
47 #main-menu li a:hover, #main-menu li a.selected {background:#759FCF;}
48
48
49 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
49 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
50
50
51 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
51 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
52 * html #sidebar{ width: 17%; }
52 * html #sidebar{ width: 17%; }
53 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
53 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
54 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
54 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
55 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
55 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
56
56
57 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
57 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
58 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
58 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
59 html>body #content {
59 html>body #content {
60 height: auto;
60 height: auto;
61 min-height: 600px;
61 min-height: 600px;
62 }
62 }
63
63
64 #main.nosidebar #sidebar{ display: none; }
64 #main.nosidebar #sidebar{ display: none; }
65 #main.nosidebar #content{ width: auto; border-right: 0; }
65 #main.nosidebar #content{ width: auto; border-right: 0; }
66
66
67 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
67 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
68
68
69 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
69 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
70 #login-form table td {padding: 6px;}
70 #login-form table td {padding: 6px;}
71 #login-form label {font-weight: bold;}
71 #login-form label {font-weight: bold;}
72
72
73 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
73 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
74
74
75 /***** Links *****/
75 /***** Links *****/
76 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
76 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
77 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
77 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
78 a img{ border: 0; }
78 a img{ border: 0; }
79
79
80 a.issue.closed, .issue.closed a { text-decoration: line-through; }
80 a.issue.closed, .issue.closed a { text-decoration: line-through; }
81
81
82 /***** Tables *****/
82 /***** Tables *****/
83 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
83 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
84 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
84 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
85 table.list td { overflow: hidden; vertical-align: top;}
85 table.list td { overflow: hidden; vertical-align: top;}
86 table.list td.id { width: 2%; text-align: center;}
86 table.list td.id { width: 2%; text-align: center;}
87 table.list td.checkbox { width: 15px; padding: 0px;}
87 table.list td.checkbox { width: 15px; padding: 0px;}
88
88
89 tr.issue { text-align: center; white-space: nowrap; }
89 tr.issue { text-align: center; white-space: nowrap; }
90 tr.issue td.subject, tr.issue td.category { white-space: normal; }
90 tr.issue td.subject, tr.issue td.category { white-space: normal; }
91 tr.issue td.subject { text-align: left; }
91 tr.issue td.subject { text-align: left; }
92 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
92 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
93
93
94 tr.entry { border: 1px solid #f8f8f8; }
94 tr.entry { border: 1px solid #f8f8f8; }
95 tr.entry td { white-space: nowrap; }
95 tr.entry td { white-space: nowrap; }
96 tr.entry td.filename { width: 30%; }
96 tr.entry td.filename { width: 30%; }
97 tr.entry td.size { text-align: right; font-size: 90%; }
97 tr.entry td.size { text-align: right; font-size: 90%; }
98 tr.entry td.revision, tr.entry td.author { text-align: center; }
98 tr.entry td.revision, tr.entry td.author { text-align: center; }
99 tr.entry td.age { text-align: right; }
99 tr.entry td.age { text-align: right; }
100
100
101 tr.changeset td.author { text-align: center; width: 15%; }
101 tr.changeset td.author { text-align: center; width: 15%; }
102 tr.changeset td.committed_on { text-align: center; width: 15%; }
102 tr.changeset td.committed_on { text-align: center; width: 15%; }
103
103
104 tr.message { height: 2.6em; }
104 tr.message { height: 2.6em; }
105 tr.message td.last_message { font-size: 80%; }
105 tr.message td.last_message { font-size: 80%; }
106 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
106 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
107 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
107 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
108
108
109 tr.user td { width:13%; }
109 tr.user td { width:13%; }
110 tr.user td.email { width:18%; }
110 tr.user td.email { width:18%; }
111 tr.user td { white-space: nowrap; }
111 tr.user td { white-space: nowrap; }
112 tr.user.locked, tr.user.registered { color: #aaa; }
112 tr.user.locked, tr.user.registered { color: #aaa; }
113 tr.user.locked a, tr.user.registered a { color: #aaa; }
113 tr.user.locked a, tr.user.registered a { color: #aaa; }
114
114
115 tr.time-entry { text-align: center; white-space: nowrap; }
115 tr.time-entry { text-align: center; white-space: nowrap; }
116 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; }
116 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
117 tr.time-entry td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
117 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
118 tr.time-entry .hours-dec { font-size: 0.9em; }
118 td.hours .hours-dec { font-size: 0.9em; }
119
119
120 table.list tbody tr:hover { background-color:#ffffdd; }
120 table.list tbody tr:hover { background-color:#ffffdd; }
121 table td {padding:2px;}
121 table td {padding:2px;}
122 table p {margin:0;}
122 table p {margin:0;}
123 .odd {background-color:#f6f7f8;}
123 .odd {background-color:#f6f7f8;}
124 .even {background-color: #fff;}
124 .even {background-color: #fff;}
125
125
126 .highlight { background-color: #FCFD8D;}
126 .highlight { background-color: #FCFD8D;}
127 .highlight.token-1 { background-color: #faa;}
127 .highlight.token-1 { background-color: #faa;}
128 .highlight.token-2 { background-color: #afa;}
128 .highlight.token-2 { background-color: #afa;}
129 .highlight.token-3 { background-color: #aaf;}
129 .highlight.token-3 { background-color: #aaf;}
130
130
131 .box{
131 .box{
132 padding:6px;
132 padding:6px;
133 margin-bottom: 10px;
133 margin-bottom: 10px;
134 background-color:#f6f6f6;
134 background-color:#f6f6f6;
135 color:#505050;
135 color:#505050;
136 line-height:1.5em;
136 line-height:1.5em;
137 border: 1px solid #e4e4e4;
137 border: 1px solid #e4e4e4;
138 }
138 }
139
139
140 div.square {
140 div.square {
141 border: 1px solid #999;
141 border: 1px solid #999;
142 float: left;
142 float: left;
143 margin: .3em .4em 0 .4em;
143 margin: .3em .4em 0 .4em;
144 overflow: hidden;
144 overflow: hidden;
145 width: .6em; height: .6em;
145 width: .6em; height: .6em;
146 }
146 }
147
147
148 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
148 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
149 .contextual input {font-size:0.9em;}
149 .contextual input {font-size:0.9em;}
150
150
151 .splitcontentleft{float:left; width:49%;}
151 .splitcontentleft{float:left; width:49%;}
152 .splitcontentright{float:right; width:49%;}
152 .splitcontentright{float:right; width:49%;}
153 form {display: inline;}
153 form {display: inline;}
154 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
154 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
155 fieldset {border: 1px solid #e4e4e4; margin:0;}
155 fieldset {border: 1px solid #e4e4e4; margin:0;}
156 legend {color: #484848;}
156 legend {color: #484848;}
157 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
157 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
158 textarea.wiki-edit { width: 99%; }
158 textarea.wiki-edit { width: 99%; }
159 li p {margin-top: 0;}
159 li p {margin-top: 0;}
160 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
160 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
161
161
162 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
162 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
163 div#issue-changesets .changeset { padding: 4px;}
163 div#issue-changesets .changeset { padding: 4px;}
164 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
164 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
165 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
165 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
166
166
167 div#activity dl { margin-left: 2em; }
167 div#activity dl { margin-left: 2em; }
168 div#activity dd { margin-bottom: 1em; }
168 div#activity dd { margin-bottom: 1em; }
169 div#activity dt { margin-bottom: 1px; }
169 div#activity dt { margin-bottom: 1px; }
170 div#activity dt .time { color: #777; font-size: 80%; }
170 div#activity dt .time { color: #777; font-size: 80%; }
171 div#activity dd .description { font-style: italic; }
171 div#activity dd .description { font-style: italic; }
172 div#activity span.project:after { content: " -"; }
172 div#activity span.project:after { content: " -"; }
173
173
174 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
174 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
175 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
175 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
176 div#roadmap .wiki h1:first-child { display: none; }
176 div#roadmap .wiki h1:first-child { display: none; }
177 div#roadmap .wiki h1 { font-size: 120%; }
177 div#roadmap .wiki h1 { font-size: 120%; }
178 div#roadmap .wiki h2 { font-size: 110%; }
178 div#roadmap .wiki h2 { font-size: 110%; }
179
179
180 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
180 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
181 div#version-summary fieldset { margin-bottom: 1em; }
181 div#version-summary fieldset { margin-bottom: 1em; }
182 div#version-summary .total-hours { text-align: right; }
182 div#version-summary .total-hours { text-align: right; }
183
183
184 table#time-report td.hours { text-align: right; padding-right: 0.5em; }
184 table#time-report td.hours { text-align: right; padding-right: 0.5em; }
185 table#time-report tbody tr { font-style: italic; color: #777; }
185 table#time-report tbody tr { font-style: italic; color: #777; }
186 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
186 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
187 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
187 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
188 table#time-report .hours-dec { font-size: 0.9em; }
188 table#time-report .hours-dec { font-size: 0.9em; }
189
189
190 .total-hours { font-size: 110%; font-weight: bold; }
190 .total-hours { font-size: 110%; font-weight: bold; }
191 .total-hours span.hours-int { font-size: 120%; }
191 .total-hours span.hours-int { font-size: 120%; }
192
192
193 .autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;}
193 .autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;}
194 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
194 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
195
195
196 .pagination {font-size: 90%}
196 .pagination {font-size: 90%}
197 p.pagination {margin-top:8px;}
197 p.pagination {margin-top:8px;}
198
198
199 /***** Tabular forms ******/
199 /***** Tabular forms ******/
200 .tabular p{
200 .tabular p{
201 margin: 0;
201 margin: 0;
202 padding: 5px 0 8px 0;
202 padding: 5px 0 8px 0;
203 padding-left: 180px; /*width of left column containing the label elements*/
203 padding-left: 180px; /*width of left column containing the label elements*/
204 height: 1%;
204 height: 1%;
205 clear:left;
205 clear:left;
206 }
206 }
207
207
208 .tabular label{
208 .tabular label{
209 font-weight: bold;
209 font-weight: bold;
210 float: left;
210 float: left;
211 text-align: right;
211 text-align: right;
212 margin-left: -180px; /*width of left column*/
212 margin-left: -180px; /*width of left column*/
213 width: 175px; /*width of labels. Should be smaller than left column to create some right
213 width: 175px; /*width of labels. Should be smaller than left column to create some right
214 margin*/
214 margin*/
215 }
215 }
216
216
217 .tabular label.floating{
217 .tabular label.floating{
218 font-weight: normal;
218 font-weight: normal;
219 margin-left: 0px;
219 margin-left: 0px;
220 text-align: left;
220 text-align: left;
221 width: 200px;
221 width: 200px;
222 }
222 }
223
223
224 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
224 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
225
225
226 .tabular.settings p{ padding-left: 300px; }
226 .tabular.settings p{ padding-left: 300px; }
227 .tabular.settings label{ margin-left: -300px; width: 295px; }
227 .tabular.settings label{ margin-left: -300px; width: 295px; }
228
228
229 .required {color: #bb0000;}
229 .required {color: #bb0000;}
230 .summary {font-style: italic;}
230 .summary {font-style: italic;}
231
231
232 #attachments_fields input[type=text] {margin-left: 8px; }
232 #attachments_fields input[type=text] {margin-left: 8px; }
233
233
234 div.attachments p { margin:4px 0 2px 0; }
234 div.attachments p { margin:4px 0 2px 0; }
235 div.attachments img { vertical-align: middle; }
235 div.attachments img { vertical-align: middle; }
236 div.attachments span.author { font-size: 0.9em; color: #888; }
236 div.attachments span.author { font-size: 0.9em; color: #888; }
237
237
238 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
238 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
239 .other-formats span + span:before { content: "| "; }
239 .other-formats span + span:before { content: "| "; }
240
240
241 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
241 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
242
242
243 /***** Flash & error messages ****/
243 /***** Flash & error messages ****/
244 #errorExplanation, div.flash, .nodata {
244 #errorExplanation, div.flash, .nodata {
245 padding: 4px 4px 4px 30px;
245 padding: 4px 4px 4px 30px;
246 margin-bottom: 12px;
246 margin-bottom: 12px;
247 font-size: 1.1em;
247 font-size: 1.1em;
248 border: 2px solid;
248 border: 2px solid;
249 }
249 }
250
250
251 div.flash {margin-top: 8px;}
251 div.flash {margin-top: 8px;}
252
252
253 div.flash.error, #errorExplanation {
253 div.flash.error, #errorExplanation {
254 background: url(../images/false.png) 8px 5px no-repeat;
254 background: url(../images/false.png) 8px 5px no-repeat;
255 background-color: #ffe3e3;
255 background-color: #ffe3e3;
256 border-color: #dd0000;
256 border-color: #dd0000;
257 color: #550000;
257 color: #550000;
258 }
258 }
259
259
260 div.flash.notice {
260 div.flash.notice {
261 background: url(../images/true.png) 8px 5px no-repeat;
261 background: url(../images/true.png) 8px 5px no-repeat;
262 background-color: #dfffdf;
262 background-color: #dfffdf;
263 border-color: #9fcf9f;
263 border-color: #9fcf9f;
264 color: #005f00;
264 color: #005f00;
265 }
265 }
266
266
267 .nodata {
267 .nodata {
268 text-align: center;
268 text-align: center;
269 background-color: #FFEBC1;
269 background-color: #FFEBC1;
270 border-color: #FDBF3B;
270 border-color: #FDBF3B;
271 color: #A6750C;
271 color: #A6750C;
272 }
272 }
273
273
274 #errorExplanation ul { font-size: 0.9em;}
274 #errorExplanation ul { font-size: 0.9em;}
275
275
276 /***** Ajax indicator ******/
276 /***** Ajax indicator ******/
277 #ajax-indicator {
277 #ajax-indicator {
278 position: absolute; /* fixed not supported by IE */
278 position: absolute; /* fixed not supported by IE */
279 background-color:#eee;
279 background-color:#eee;
280 border: 1px solid #bbb;
280 border: 1px solid #bbb;
281 top:35%;
281 top:35%;
282 left:40%;
282 left:40%;
283 width:20%;
283 width:20%;
284 font-weight:bold;
284 font-weight:bold;
285 text-align:center;
285 text-align:center;
286 padding:0.6em;
286 padding:0.6em;
287 z-index:100;
287 z-index:100;
288 filter:alpha(opacity=50);
288 filter:alpha(opacity=50);
289 opacity: 0.5;
289 opacity: 0.5;
290 }
290 }
291
291
292 html>body #ajax-indicator { position: fixed; }
292 html>body #ajax-indicator { position: fixed; }
293
293
294 #ajax-indicator span {
294 #ajax-indicator span {
295 background-position: 0% 40%;
295 background-position: 0% 40%;
296 background-repeat: no-repeat;
296 background-repeat: no-repeat;
297 background-image: url(../images/loading.gif);
297 background-image: url(../images/loading.gif);
298 padding-left: 26px;
298 padding-left: 26px;
299 vertical-align: bottom;
299 vertical-align: bottom;
300 }
300 }
301
301
302 /***** Calendar *****/
302 /***** Calendar *****/
303 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
303 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
304 table.cal thead th {width: 14%;}
304 table.cal thead th {width: 14%;}
305 table.cal tbody tr {height: 100px;}
305 table.cal tbody tr {height: 100px;}
306 table.cal th { background-color:#EEEEEE; padding: 4px; }
306 table.cal th { background-color:#EEEEEE; padding: 4px; }
307 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
307 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
308 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
308 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
309 table.cal td.odd p.day-num {color: #bbb;}
309 table.cal td.odd p.day-num {color: #bbb;}
310 table.cal td.today {background:#ffffdd;}
310 table.cal td.today {background:#ffffdd;}
311 table.cal td.today p.day-num {font-weight: bold;}
311 table.cal td.today p.day-num {font-weight: bold;}
312
312
313 /***** Tooltips ******/
313 /***** Tooltips ******/
314 .tooltip{position:relative;z-index:24;}
314 .tooltip{position:relative;z-index:24;}
315 .tooltip:hover{z-index:25;color:#000;}
315 .tooltip:hover{z-index:25;color:#000;}
316 .tooltip span.tip{display: none; text-align:left;}
316 .tooltip span.tip{display: none; text-align:left;}
317
317
318 div.tooltip:hover span.tip{
318 div.tooltip:hover span.tip{
319 display:block;
319 display:block;
320 position:absolute;
320 position:absolute;
321 top:12px; left:24px; width:270px;
321 top:12px; left:24px; width:270px;
322 border:1px solid #555;
322 border:1px solid #555;
323 background-color:#fff;
323 background-color:#fff;
324 padding: 4px;
324 padding: 4px;
325 font-size: 0.8em;
325 font-size: 0.8em;
326 color:#505050;
326 color:#505050;
327 }
327 }
328
328
329 /***** Progress bar *****/
329 /***** Progress bar *****/
330 table.progress {
330 table.progress {
331 border: 1px solid #D7D7D7;
331 border: 1px solid #D7D7D7;
332 border-collapse: collapse;
332 border-collapse: collapse;
333 border-spacing: 0pt;
333 border-spacing: 0pt;
334 empty-cells: show;
334 empty-cells: show;
335 text-align: center;
335 text-align: center;
336 float:left;
336 float:left;
337 margin: 1px 6px 1px 0px;
337 margin: 1px 6px 1px 0px;
338 }
338 }
339
339
340 table.progress td { height: 0.9em; }
340 table.progress td { height: 0.9em; }
341 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
341 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
342 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
342 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
343 table.progress td.open { background: #FFF none repeat scroll 0%; }
343 table.progress td.open { background: #FFF none repeat scroll 0%; }
344 p.pourcent {font-size: 80%;}
344 p.pourcent {font-size: 80%;}
345 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
345 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
346
346
347 /***** Tabs *****/
347 /***** Tabs *****/
348 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
348 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
349 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
349 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
350 #content .tabs>ul { bottom:-1px; } /* others */
350 #content .tabs>ul { bottom:-1px; } /* others */
351 #content .tabs ul li {
351 #content .tabs ul li {
352 float:left;
352 float:left;
353 list-style-type:none;
353 list-style-type:none;
354 white-space:nowrap;
354 white-space:nowrap;
355 margin-right:8px;
355 margin-right:8px;
356 background:#fff;
356 background:#fff;
357 }
357 }
358 #content .tabs ul li a{
358 #content .tabs ul li a{
359 display:block;
359 display:block;
360 font-size: 0.9em;
360 font-size: 0.9em;
361 text-decoration:none;
361 text-decoration:none;
362 line-height:1.3em;
362 line-height:1.3em;
363 padding:4px 6px 4px 6px;
363 padding:4px 6px 4px 6px;
364 border: 1px solid #ccc;
364 border: 1px solid #ccc;
365 border-bottom: 1px solid #bbbbbb;
365 border-bottom: 1px solid #bbbbbb;
366 background-color: #eeeeee;
366 background-color: #eeeeee;
367 color:#777;
367 color:#777;
368 font-weight:bold;
368 font-weight:bold;
369 }
369 }
370
370
371 #content .tabs ul li a:hover {
371 #content .tabs ul li a:hover {
372 background-color: #ffffdd;
372 background-color: #ffffdd;
373 text-decoration:none;
373 text-decoration:none;
374 }
374 }
375
375
376 #content .tabs ul li a.selected {
376 #content .tabs ul li a.selected {
377 background-color: #fff;
377 background-color: #fff;
378 border: 1px solid #bbbbbb;
378 border: 1px solid #bbbbbb;
379 border-bottom: 1px solid #fff;
379 border-bottom: 1px solid #fff;
380 }
380 }
381
381
382 #content .tabs ul li a.selected:hover {
382 #content .tabs ul li a.selected:hover {
383 background-color: #fff;
383 background-color: #fff;
384 }
384 }
385
385
386 /***** Diff *****/
386 /***** Diff *****/
387 .diff_out { background: #fcc; }
387 .diff_out { background: #fcc; }
388 .diff_in { background: #cfc; }
388 .diff_in { background: #cfc; }
389
389
390 /***** Wiki *****/
390 /***** Wiki *****/
391 div.wiki table {
391 div.wiki table {
392 border: 1px solid #505050;
392 border: 1px solid #505050;
393 border-collapse: collapse;
393 border-collapse: collapse;
394 margin-bottom: 1em;
394 margin-bottom: 1em;
395 }
395 }
396
396
397 div.wiki table, div.wiki td, div.wiki th {
397 div.wiki table, div.wiki td, div.wiki th {
398 border: 1px solid #bbb;
398 border: 1px solid #bbb;
399 padding: 4px;
399 padding: 4px;
400 }
400 }
401
401
402 div.wiki .external {
402 div.wiki .external {
403 background-position: 0% 60%;
403 background-position: 0% 60%;
404 background-repeat: no-repeat;
404 background-repeat: no-repeat;
405 padding-left: 12px;
405 padding-left: 12px;
406 background-image: url(../images/external.png);
406 background-image: url(../images/external.png);
407 }
407 }
408
408
409 div.wiki a.new {
409 div.wiki a.new {
410 color: #b73535;
410 color: #b73535;
411 }
411 }
412
412
413 div.wiki pre {
413 div.wiki pre {
414 margin: 1em 1em 1em 1.6em;
414 margin: 1em 1em 1em 1.6em;
415 padding: 2px;
415 padding: 2px;
416 background-color: #fafafa;
416 background-color: #fafafa;
417 border: 1px solid #dadada;
417 border: 1px solid #dadada;
418 width:95%;
418 width:95%;
419 overflow-x: auto;
419 overflow-x: auto;
420 }
420 }
421
421
422 div.wiki div.toc {
422 div.wiki div.toc {
423 background-color: #ffffdd;
423 background-color: #ffffdd;
424 border: 1px solid #e4e4e4;
424 border: 1px solid #e4e4e4;
425 padding: 4px;
425 padding: 4px;
426 line-height: 1.2em;
426 line-height: 1.2em;
427 margin-bottom: 12px;
427 margin-bottom: 12px;
428 margin-right: 12px;
428 margin-right: 12px;
429 display: table
429 display: table
430 }
430 }
431 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
431 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
432
432
433 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
433 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
434 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
434 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
435
435
436 div.wiki div.toc a {
436 div.wiki div.toc a {
437 display: block;
437 display: block;
438 font-size: 0.9em;
438 font-size: 0.9em;
439 font-weight: normal;
439 font-weight: normal;
440 text-decoration: none;
440 text-decoration: none;
441 color: #606060;
441 color: #606060;
442 }
442 }
443 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
443 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
444
444
445 div.wiki div.toc a.heading2 { margin-left: 6px; }
445 div.wiki div.toc a.heading2 { margin-left: 6px; }
446 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
446 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
447
447
448 /***** My page layout *****/
448 /***** My page layout *****/
449 .block-receiver {
449 .block-receiver {
450 border:1px dashed #c0c0c0;
450 border:1px dashed #c0c0c0;
451 margin-bottom: 20px;
451 margin-bottom: 20px;
452 padding: 15px 0 15px 0;
452 padding: 15px 0 15px 0;
453 }
453 }
454
454
455 .mypage-box {
455 .mypage-box {
456 margin:0 0 20px 0;
456 margin:0 0 20px 0;
457 color:#505050;
457 color:#505050;
458 line-height:1.5em;
458 line-height:1.5em;
459 }
459 }
460
460
461 .handle {
461 .handle {
462 cursor: move;
462 cursor: move;
463 }
463 }
464
464
465 a.close-icon {
465 a.close-icon {
466 display:block;
466 display:block;
467 margin-top:3px;
467 margin-top:3px;
468 overflow:hidden;
468 overflow:hidden;
469 width:12px;
469 width:12px;
470 height:12px;
470 height:12px;
471 background-repeat: no-repeat;
471 background-repeat: no-repeat;
472 cursor:pointer;
472 cursor:pointer;
473 background-image:url('../images/close.png');
473 background-image:url('../images/close.png');
474 }
474 }
475
475
476 a.close-icon:hover {
476 a.close-icon:hover {
477 background-image:url('../images/close_hl.png');
477 background-image:url('../images/close_hl.png');
478 }
478 }
479
479
480 /***** Gantt chart *****/
480 /***** Gantt chart *****/
481 .gantt_hdr {
481 .gantt_hdr {
482 position:absolute;
482 position:absolute;
483 top:0;
483 top:0;
484 height:16px;
484 height:16px;
485 border-top: 1px solid #c0c0c0;
485 border-top: 1px solid #c0c0c0;
486 border-bottom: 1px solid #c0c0c0;
486 border-bottom: 1px solid #c0c0c0;
487 border-right: 1px solid #c0c0c0;
487 border-right: 1px solid #c0c0c0;
488 text-align: center;
488 text-align: center;
489 overflow: hidden;
489 overflow: hidden;
490 }
490 }
491
491
492 .task {
492 .task {
493 position: absolute;
493 position: absolute;
494 height:8px;
494 height:8px;
495 font-size:0.8em;
495 font-size:0.8em;
496 color:#888;
496 color:#888;
497 padding:0;
497 padding:0;
498 margin:0;
498 margin:0;
499 line-height:0.8em;
499 line-height:0.8em;
500 }
500 }
501
501
502 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
502 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
503 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
503 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
504 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
504 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
505 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
505 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
506
506
507 /***** Icons *****/
507 /***** Icons *****/
508 .icon {
508 .icon {
509 background-position: 0% 40%;
509 background-position: 0% 40%;
510 background-repeat: no-repeat;
510 background-repeat: no-repeat;
511 padding-left: 20px;
511 padding-left: 20px;
512 padding-top: 2px;
512 padding-top: 2px;
513 padding-bottom: 3px;
513 padding-bottom: 3px;
514 }
514 }
515
515
516 .icon22 {
516 .icon22 {
517 background-position: 0% 40%;
517 background-position: 0% 40%;
518 background-repeat: no-repeat;
518 background-repeat: no-repeat;
519 padding-left: 26px;
519 padding-left: 26px;
520 line-height: 22px;
520 line-height: 22px;
521 vertical-align: middle;
521 vertical-align: middle;
522 }
522 }
523
523
524 .icon-add { background-image: url(../images/add.png); }
524 .icon-add { background-image: url(../images/add.png); }
525 .icon-edit { background-image: url(../images/edit.png); }
525 .icon-edit { background-image: url(../images/edit.png); }
526 .icon-copy { background-image: url(../images/copy.png); }
526 .icon-copy { background-image: url(../images/copy.png); }
527 .icon-del { background-image: url(../images/delete.png); }
527 .icon-del { background-image: url(../images/delete.png); }
528 .icon-move { background-image: url(../images/move.png); }
528 .icon-move { background-image: url(../images/move.png); }
529 .icon-save { background-image: url(../images/save.png); }
529 .icon-save { background-image: url(../images/save.png); }
530 .icon-cancel { background-image: url(../images/cancel.png); }
530 .icon-cancel { background-image: url(../images/cancel.png); }
531 .icon-file { background-image: url(../images/file.png); }
531 .icon-file { background-image: url(../images/file.png); }
532 .icon-folder { background-image: url(../images/folder.png); }
532 .icon-folder { background-image: url(../images/folder.png); }
533 .open .icon-folder { background-image: url(../images/folder_open.png); }
533 .open .icon-folder { background-image: url(../images/folder_open.png); }
534 .icon-package { background-image: url(../images/package.png); }
534 .icon-package { background-image: url(../images/package.png); }
535 .icon-home { background-image: url(../images/home.png); }
535 .icon-home { background-image: url(../images/home.png); }
536 .icon-user { background-image: url(../images/user.png); }
536 .icon-user { background-image: url(../images/user.png); }
537 .icon-mypage { background-image: url(../images/user_page.png); }
537 .icon-mypage { background-image: url(../images/user_page.png); }
538 .icon-admin { background-image: url(../images/admin.png); }
538 .icon-admin { background-image: url(../images/admin.png); }
539 .icon-projects { background-image: url(../images/projects.png); }
539 .icon-projects { background-image: url(../images/projects.png); }
540 .icon-logout { background-image: url(../images/logout.png); }
540 .icon-logout { background-image: url(../images/logout.png); }
541 .icon-help { background-image: url(../images/help.png); }
541 .icon-help { background-image: url(../images/help.png); }
542 .icon-attachment { background-image: url(../images/attachment.png); }
542 .icon-attachment { background-image: url(../images/attachment.png); }
543 .icon-index { background-image: url(../images/index.png); }
543 .icon-index { background-image: url(../images/index.png); }
544 .icon-history { background-image: url(../images/history.png); }
544 .icon-history { background-image: url(../images/history.png); }
545 .icon-time { background-image: url(../images/time.png); }
545 .icon-time { background-image: url(../images/time.png); }
546 .icon-stats { background-image: url(../images/stats.png); }
546 .icon-stats { background-image: url(../images/stats.png); }
547 .icon-warning { background-image: url(../images/warning.png); }
547 .icon-warning { background-image: url(../images/warning.png); }
548 .icon-fav { background-image: url(../images/fav.png); }
548 .icon-fav { background-image: url(../images/fav.png); }
549 .icon-fav-off { background-image: url(../images/fav_off.png); }
549 .icon-fav-off { background-image: url(../images/fav_off.png); }
550 .icon-reload { background-image: url(../images/reload.png); }
550 .icon-reload { background-image: url(../images/reload.png); }
551 .icon-lock { background-image: url(../images/locked.png); }
551 .icon-lock { background-image: url(../images/locked.png); }
552 .icon-unlock { background-image: url(../images/unlock.png); }
552 .icon-unlock { background-image: url(../images/unlock.png); }
553 .icon-checked { background-image: url(../images/true.png); }
553 .icon-checked { background-image: url(../images/true.png); }
554 .icon-details { background-image: url(../images/zoom_in.png); }
554 .icon-details { background-image: url(../images/zoom_in.png); }
555 .icon-report { background-image: url(../images/report.png); }
555 .icon-report { background-image: url(../images/report.png); }
556
556
557 .icon22-projects { background-image: url(../images/22x22/projects.png); }
557 .icon22-projects { background-image: url(../images/22x22/projects.png); }
558 .icon22-users { background-image: url(../images/22x22/users.png); }
558 .icon22-users { background-image: url(../images/22x22/users.png); }
559 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
559 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
560 .icon22-role { background-image: url(../images/22x22/role.png); }
560 .icon22-role { background-image: url(../images/22x22/role.png); }
561 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
561 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
562 .icon22-options { background-image: url(../images/22x22/options.png); }
562 .icon22-options { background-image: url(../images/22x22/options.png); }
563 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
563 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
564 .icon22-authent { background-image: url(../images/22x22/authent.png); }
564 .icon22-authent { background-image: url(../images/22x22/authent.png); }
565 .icon22-info { background-image: url(../images/22x22/info.png); }
565 .icon22-info { background-image: url(../images/22x22/info.png); }
566 .icon22-comment { background-image: url(../images/22x22/comment.png); }
566 .icon22-comment { background-image: url(../images/22x22/comment.png); }
567 .icon22-package { background-image: url(../images/22x22/package.png); }
567 .icon22-package { background-image: url(../images/22x22/package.png); }
568 .icon22-settings { background-image: url(../images/22x22/settings.png); }
568 .icon22-settings { background-image: url(../images/22x22/settings.png); }
569 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
569 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
570
570
571 /***** Media print specific styles *****/
571 /***** Media print specific styles *****/
572 @media print {
572 @media print {
573 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
573 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
574 #main { background: #fff; }
574 #main { background: #fff; }
575 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
575 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
576 }
576 }
General Comments 0
You need to be logged in to leave comments. Login now